OSX/MACOS: Fix crash when toggling Suspend Screensaver

-[RetroArch_OSX setDisableDisplaySleep:] stored the NSActivity
token returned by -beginActivityWithOptions:reason: in a raw-id
ivar with no retain.  The token is autoreleased and owned by the
system, so under MRR (used by RetroArch_PPC.xcodeproj and the
qb/make build - ARC is not universal in this file) it was
deallocated as soon as the surrounding autorelease pool drained,
leaving _sleepActivity as a dangling pointer.  Toggling the
setting back off then sent -endActivity: to freed memory, which
crashed in objc_opt_isKindOfClass at offset 0x1c while checking
-isKindOfClass:_NSActivityAssertion.

Fixes (macOS 12.3, x86_64, 1.22.2):

  0  libobjc           objc_opt_isKindOfClass + 27
  1  Foundation        -[NSProcessInfo endActivity:] + 29
  2  RetroArch         -[RetroArch_OSX setDisableDisplaySleep:] + 125
  3  RetroArch         general_write_handler + 1646
  4  RetroArch         setting_bool_action_ok_default + 34
  ...

- Retain the token (RARCH_RETAIN, no-op under ARC, -retain under
  MRR) so it survives across the begin/end pair.
- Capture + nil-out the ivar before calling -endActivity: to make
  a re-entrant call from inside AppKit's menu write path unable
  to observe a stale pointer and double-end.
- Release any outstanding activity in -dealloc (MRR branch) so
  quitting while the screensaver is suspended does not leak the
  system-wide display-sleep assertion.
- Guard the whole method with MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
  so 10.5-era SDKs (Xcode 3.1 / GCC 4.0, which predate both App
  Nap and ARC) still compile, and with -respondsToSelector: so a
  binary built on a newer SDK but deployed to 10.5-10.8 no-ops
  instead of crashing on unrecognized selector.

Also adds RARCH_RETAIN to cocoa_defines.h next to the existing
RARCH_RELEASE / RARCH_AUTORELEASE / RARCH_SUPER_DEALLOC family.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-21 18:36:45 +02:00
1 parent b0f6690073
commit 9743d1e1c3
2 files changed
+54 -2

No files matched your search

@@ -93,10 +93,12 @@
#endif
#if __has_feature(objc_arc)
#define RARCH_RETAIN(x) (x)
#define RARCH_RELEASE(x) ((void)0)
#define RARCH_AUTORELEASE(x) ((void)0)
#define RARCH_SUPER_DEALLOC() ((void)0)
#else
#define RARCH_RETAIN(x) [(x) retain]
#define RARCH_RELEASE(x) [(x) release]
#define RARCH_AUTORELEASE(x) [(x) autorelease]
#define RARCH_SUPER_DEALLOC() [super dealloc]
+52 -2
View File
@@ -615,6 +615,22 @@ static ui_application_t ui_application_cocoa = {
* _window manually. See cocoa_defines.h for the ARC/MRR macro story. */
- (void)dealloc
{
/* Make sure any outstanding NSProcessInfo activity is ended and
* released. Without this, quitting while the screensaver is
* suspended would leak both the activity assertion (leaving
* display-sleep disabled system-wide until reboot) and the
* retained token itself. */
#if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
if (_sleepActivity)
{
NSProcessInfo *pi = [NSProcessInfo processInfo];
id token = _sleepActivity;
_sleepActivity = nil;
if ([pi respondsToSelector:@selector(endActivity:)])
[pi endActivity:token];
RARCH_RELEASE(token);
}
#endif
RARCH_RELEASE(_window);
RARCH_SUPER_DEALLOC();
}
@@ -808,20 +824,54 @@ static ui_application_t ui_application_cocoa = {
- (bool)setDisableDisplaySleep:(bool)disable
{
/* beginActivityWithOptions:reason: / endActivity: were added in
* macOS 10.9. Guard at compile time so builds against older SDKs
* (notably 10.5 via Xcode 3.1 / GCC 4.0, which predate both App
* Nap and ARC) still compile, and guard at runtime so a binary
* built against a 10.9+ SDK but deployed onto 10.5-10.8 gracefully
* no-ops instead of crashing on an unrecognized selector. */
#if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
NSProcessInfo *pi = [NSProcessInfo processInfo];
if (![pi respondsToSelector:@selector(beginActivityWithOptions:reason:)])
return NO;
if (disable)
{
if (_sleepActivity == nil)
_sleepActivity = [NSProcessInfo.processInfo beginActivityWithOptions:NSActivityIdleDisplaySleepDisabled reason:@"disable screen saver"];
{
/* The token returned by beginActivityWithOptions:reason: is
* autoreleased and owned by the system. We MUST retain it
* or, under MRR, it is deallocated as soon as the current
* autorelease pool drains - leaving _sleepActivity as a
* dangling pointer. The next call to endActivity: then
* sends isKindOfClass: to freed memory and crashes in
* objc_opt_isKindOfClass. Under ARC a plain `id` ivar is
* implicitly __strong so RARCH_RETAIN is a no-op; under MRR
* it expands to an explicit -retain. */
id token = [pi beginActivityWithOptions:NSActivityIdleDisplaySleepDisabled
reason:@"disable screen saver"];
_sleepActivity = RARCH_RETAIN(token);
}
}
else
{
if (_sleepActivity)
{
[NSProcessInfo.processInfo endActivity:_sleepActivity];
/* Capture and nil-out BEFORE calling endActivity: so that a
* re-entrant call (for example from a menu write handler
* triggered while AppKit is dispatching) cannot observe a
* stale pointer and double-end the same activity. */
id token = _sleepActivity;
_sleepActivity = nil;
[pi endActivity:token];
RARCH_RELEASE(token);
}
}
return YES;
#else
(void)disable;
return NO;
#endif
}
#endif