Skip to content

Commit 4c5f3c3

Browse files
committed
OSX/MACOS/COCOATOUCH: Normalise MRR safety in ui_cocoatouch.m
Applies the same treatment to ui_cocoatouch.m that e9279a8 and bd45086 applied to ui_cocoa.m / cocoa_gl_ctx.m: make the file correct under both ARC and MRR via the RARCH_*/#if __has_feature(objc_arc) dance the rest of the Cocoa code uses. Every shipping iOS Xcode project sets CLANG_ENABLE_OBJC_ARC=YES, so none of the MRR leaks listed below bite any current build; this is future-proofing / consistency with ui_cocoa.m rather than bug-fixing today. The one thing that does change in practice is that the file now compiles under MRR at all - the previous (nonatomic, strong) on keyboardTextField was an ARC-only keyword that failed to parse under the pre-ARC compiler. Header changes (apple_platform.h): The RetroArch_iOS properties were spelled with bare (nonatomic), relying on ARC's implicit 'strong' default for object-typed properties. Under MRR the default is 'assign', which is silently wrong - the referent is released at the next autorelease-pool drain after assignment. Spell the ownership out: window and bgDate become (nonatomic, retain), documentsDirectory becomes (nonatomic, copy) to match the NSString convention. menu_count (int) stays bare (nonatomic) since 'assign' is correct for scalars. Under ARC these qualifiers are accepted and behave identically to 'strong' / defaults. Implementation changes (ui_cocoatouch.m): - Include <defines/cocoa_defines.h> for the RARCH_* macros. - The private category's (nonatomic, strong) UITextField *keyboardTextField becomes (nonatomic, retain). Identical behaviour under ARC; parseable under MRR. - -setViewType: adopts the +1-invariant pattern e9279a8 introduced for ui_cocoa.m. _renderView is released via RARCH_RELEASE before the ivar is nilled on teardown; the VULKAN and METAL branches take their +1 directly from +new; the OPENGL_ES branch takes it via RARCH_RETAIN of the glkitview_init() singleton so the ivar's ownership is uniform across all three paths. UIPointerInteraction's alloc+init is autoreleased to balance -addInteraction:'s internal retain. - Every other local +1 producer (+[KSCrashConfiguration new], +[KSCrashReportStoreConfiguration new], -mutableCopy, [[NSString alloc] initWithData:...] in three places, [[NSDateFormatter alloc] init], [[UIWindow alloc] initWithFrame:...], [[NSURLComponents alloc] initWithURL:...], [[UITextField alloc] initWithFrame:...]) is paired with a RARCH_AUTORELEASE on the following line. The two sites that assign to retain properties (self.window and self.keyboardTextField) use an explicit temp so the alloc can be autoreleased before the setter takes its own retain - this is the classic pre-ARC [[[Class alloc] init...] autorelease] -> self.prop = ... idiom, spelled out across three statements so the macro only ever appears in statement form. (RARCH_AUTORELEASE expands to ((void)0) under ARC - it is statement-only, not expression-form. See aed4543.) - Adds an MRR-only -dealloc that RARCH_RELEASEs every retained ivar / property and calls RARCH_SUPER_DEALLOC(), mirroring ui_cocoa.m. RetroArch_iOS is the UIApplication delegate so this dealloc effectively never runs in practice, but it completes the ownership picture and keeps both files in step. cocoa_vk_ctx.m was already clean (audited in the same pass as bd45086); no changes there.
1 parent 1ab5e95 commit 4c5f3c3

2 files changed

Lines changed: 116 additions & 11 deletions

File tree

ui/drivers/cocoa/apple_platform.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,17 @@ UINavigationControllerDelegate> {
102102
apple_view_type_t _vt;
103103
}
104104

105-
@property (nonatomic) UIWindow* window;
106-
@property (nonatomic) NSString* documentsDirectory;
107-
@property (nonatomic) int menu_count;
108-
@property (nonatomic) NSDate *bgDate;
105+
/* Explicit retain / copy qualifiers so these properties are correct
106+
* under MRR as well as ARC. Under ARC the object-typed property
107+
* default is 'strong', which behaves identically to 'retain' here;
108+
* under MRR the default is 'assign', which would silently drop the
109+
* retain and release the referent at the next autorelease pool
110+
* drain. Spelling the ownership out keeps ui_cocoatouch.m
111+
* MRR-buildable in the same spirit as ui_cocoa.m. */
112+
@property (nonatomic, retain) UIWindow *window;
113+
@property (nonatomic, copy) NSString *documentsDirectory;
114+
@property (nonatomic) int menu_count;
115+
@property (nonatomic, retain) NSDate *bgDate;
109116

110117
+ (RetroArch_iOS*)get;
111118

ui/drivers/ui_cocoatouch.m

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <queues/task_queue.h>
2525
#include <string/stdstring.h>
2626
#include <retro_timers.h>
27+
#include <defines/cocoa_defines.h>
2728

2829
#include "cocoa/cocoa_common.h"
2930
#include "cocoa/apple_platform.h"
@@ -537,7 +538,10 @@ @interface RetroArch_iOS () <MXMetricManagerSubscriber, UIPointerInteractionDele
537538
#endif
538539

539540
@interface RetroArch_iOS () <UITextFieldDelegate>
540-
@property (nonatomic, strong) UITextField *keyboardTextField;
541+
/* 'retain' works identically to 'strong' under ARC but unlike 'strong'
542+
* is also accepted by the pre-ARC compiler - so the file remains
543+
* buildable under MRR without a separate code path. */
544+
@property (nonatomic, retain) UITextField *keyboardTextField;
541545
@property (nonatomic, copy) void(^keyboardCompletionCallback)(const char *);
542546
@property (nonatomic, assign) char **keyboardBufferPtr;
543547
@property (nonatomic, assign) size_t *keyboardSizePtr;
@@ -563,13 +567,24 @@ - (void)setViewType:(apple_view_type_t)vt
563567
if (_renderView != nil)
564568
{
565569
[_renderView removeFromSuperview];
570+
/* _renderView holds a +1 retain regardless of which path below
571+
* created it (the Metal / Vulkan branches take +1 directly from
572+
* +new; the OPENGL_ES branch retains the singleton returned by
573+
* glkitview_init()). Release it here so the ownership invariant
574+
* is balanced before we nil the ivar. Under ARC this is a
575+
* no-op and the implicit __strong ivar handles the release when
576+
* _renderView is assigned nil. */
577+
RARCH_RELEASE(_renderView);
566578
_renderView = nil;
567579
}
568580

569581
switch (vt)
570582
{
571583
#ifdef HAVE_COCOA_METAL
572584
case APPLE_VIEW_TYPE_VULKAN:
585+
/* +new returns a +1 object; that retain transfers into
586+
* _renderView and satisfies the ivar's ownership invariant
587+
* directly. No extra RARCH_RETAIN needed. */
573588
_renderView = [MetalLayerView new];
574589
#if TARGET_OS_IOS
575590
_renderView.multipleTouchEnabled = YES;
@@ -588,7 +603,12 @@ - (void)setViewType:(apple_view_type_t)vt
588603
break;
589604
#endif
590605
case APPLE_VIEW_TYPE_OPENGL_ES:
591-
_renderView = (BRIDGE GLKView*)glkitview_init();
606+
/* glkitview_init() returns an unretained pointer to the
607+
* cocoa_gl_ctx.m singleton. Retain explicitly so _renderView
608+
* matches the +1 invariant the Metal / Vulkan paths get from
609+
* +new. Under ARC RARCH_RETAIN is a no-op and the implicit
610+
* __strong ivar assignment takes the retain via objc_storeStrong. */
611+
_renderView = RARCH_RETAIN((BRIDGE GLKView*)glkitview_init());
592612
break;
593613

594614
case APPLE_VIEW_TYPE_NONE:
@@ -602,7 +622,16 @@ - (void)setViewType:(apple_view_type_t)vt
602622
#if TARGET_OS_IOS
603623
if (@available(iOS 13.4, *))
604624
{
605-
[_renderView addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
625+
/* +[UIPointerInteraction alloc] initWithDelegate: returns +1.
626+
* -addInteraction: retains internally, so autorelease our own
627+
* +1 to balance under MRR. ARC already releases on scope
628+
* exit; the macro is a no-op there. RARCH_AUTORELEASE is a
629+
* statement-only macro (it expands to ((void)0) under ARC)
630+
* so it must appear on its own line rather than wrapping the
631+
* rvalue. */
632+
UIPointerInteraction *interaction = [[UIPointerInteraction alloc] initWithDelegate:self];
633+
RARCH_AUTORELEASE(interaction);
634+
[_renderView addInteraction:interaction];
606635
_renderView.userInteractionEnabled = YES;
607636
}
608637
#endif
@@ -700,10 +729,18 @@ - (void)initKSCrash
700729
}
701730
}
702731

703-
/* Configure KSCrash for local storage only */
732+
/* Configure KSCrash for local storage only.
733+
* Autorelease the +1 from +new: -installWithConfiguration: keeps
734+
* its own reference via config.reportStoreConfiguration retain and
735+
* KSCrash's own retain of the config, so our local can be released
736+
* at autorelease-pool drain without dangling any of those.
737+
* RARCH_AUTORELEASE is a statement-only macro; call it on its own
738+
* line after the assignment. No-op under ARC. */
704739
KSCrashConfiguration *config = [KSCrashConfiguration new];
740+
RARCH_AUTORELEASE(config);
705741
config.installPath = crashReportsPath;
706742
KSCrashReportStoreConfiguration *storeConfig = [KSCrashReportStoreConfiguration new];
743+
RARCH_AUTORELEASE(storeConfig);
707744
storeConfig.reportsPath = crashReportsPath;
708745
storeConfig.appName = @"RetroArch";
709746
storeConfig.maxReportCount = 10; /* Keep last 10 crash reports */
@@ -751,7 +788,12 @@ - (void)processKSCrashReports
751788
if (!report)
752789
continue;
753790

791+
/* -mutableCopy returns +1. Inside a for-loop that's a
792+
* per-iteration leak under MRR; autorelease so it is cleaned up
793+
* when the pool drains at the next run-loop iteration.
794+
* Statement-only macro, so on its own line. No-op under ARC. */
754795
NSMutableDictionary *mutableReport = [report.value mutableCopy];
796+
RARCH_AUTORELEASE(mutableReport);
755797

756798
/* Remove binary_images to reduce file size */
757799
if ([mutableReport objectForKey:@"binary_images"])
@@ -776,8 +818,12 @@ - (void)processKSCrashReports
776818
error:nil];
777819
if (minifiedData)
778820
{
821+
/* +1 from alloc+init; per-iteration leak inside the for-loop
822+
* under MRR without an autorelease. Statement-only macro, so
823+
* on its own line. No-op under ARC. */
779824
NSString *jsonString = [[NSString alloc] initWithData:minifiedData
780825
encoding:NSUTF8StringEncoding];
826+
RARCH_AUTORELEASE(jsonString);
781827
if (jsonString)
782828
{
783829
/* Log with a unique marker that can be extracted with grep/sed */
@@ -817,7 +863,11 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application
817863

818864
// Define the original and new file paths
819865
NSString *originalPath = [cachesDirectory stringByAppendingPathComponent:@"RetroArch/config/retroarch.cfg"];
866+
/* +1 from alloc+init; autorelease so scope-exit cleans up under
867+
* MRR the same way ARC does. Statement-only macro, so on its
868+
* own line. No-op under ARC. */
820869
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
870+
RARCH_AUTORELEASE(dateFormatter);
821871
[dateFormatter setDateFormat:@"HHmm-yyMMdd"];
822872
NSString *timestamp = [dateFormatter stringFromDate:[NSDate date]];
823873
NSString *newPath = [cachesDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"RetroArch/config/RetroArch-%@.cfg", timestamp]];
@@ -840,8 +890,16 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application
840890

841891
[self setDelegate:self];
842892

843-
/* Setup window */
844-
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
893+
/* Setup window.
894+
* self.window is a retain property (see apple_platform.h); the
895+
* setter takes its own retain. Autorelease the +1 from alloc+init
896+
* via a temp so the setter's retain is the sole owner under MRR.
897+
* Under ARC the strong setter retains and ARC scope-releases the
898+
* temp. Statement-only macro, so RARCH_AUTORELEASE goes on its
899+
* own line. */
900+
UIWindow *win = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
901+
RARCH_AUTORELEASE(win);
902+
self.window = win;
845903
[self.window makeKeyAndVisible];
846904

847905
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
@@ -1056,7 +1114,11 @@ -(BOOL)openRetroArchURL:(NSURL *)url
10561114
// Handle topshelf URLs: retroarch://topshelf?path=...&core_path=...
10571115
if ([url.host isEqualToString:@"topshelf"])
10581116
{
1117+
/* +1 from alloc+init; autorelease so scope-exit balances under
1118+
* MRR. Statement-only macro, so on its own line. No-op under
1119+
* ARC. */
10591120
NSURLComponents *comp = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
1121+
RARCH_AUTORELEASE(comp);
10601122
NSString *ns_path, *ns_core_path;
10611123
char path[PATH_MAX_LENGTH];
10621124
char core_path[PATH_MAX_LENGTH];
@@ -1152,7 +1214,14 @@ - (void)showGameView
11521214
/* Initialize hidden keyboard text field for iOS native keyboard support */
11531215
if (!self.keyboardTextField)
11541216
{
1155-
self.keyboardTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, -100, 1, 1)];
1217+
/* self.keyboardTextField is a retain property (see private
1218+
* category above); the setter takes its own retain. Autorelease
1219+
* the +1 from alloc+init via a temp so the setter's retain is
1220+
* the sole owner under MRR. Statement-only macro, so
1221+
* RARCH_AUTORELEASE goes on its own line. No-op under ARC. */
1222+
UITextField *tf = [[UITextField alloc] initWithFrame:CGRectMake(0, -100, 1, 1)];
1223+
RARCH_AUTORELEASE(tf);
1224+
self.keyboardTextField = tf;
11561225
self.keyboardTextField.delegate = self;
11571226
self.keyboardTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
11581227
self.keyboardTextField.autocorrectionType = UITextAutocorrectionTypeNo;
@@ -1176,7 +1245,11 @@ - (void)didReceiveMetricPayloads:(NSArray<MXMetricPayload *> *)payloads API_AVAI
11761245
{
11771246
for (MXMetricPayload *payload in payloads)
11781247
{
1248+
/* +1 from alloc+init; per-iteration leak inside the loop under
1249+
* MRR without an autorelease. Statement-only macro, so on its
1250+
* own line. No-op under ARC. */
11791251
NSString *json = [[NSString alloc] initWithData:[payload JSONRepresentation] encoding:kCFStringEncodingUTF8];
1252+
RARCH_AUTORELEASE(json);
11801253
RARCH_LOG("[Cocoa] Got Metric Payload:\n%s\n", [json cStringUsingEncoding:kCFStringEncodingUTF8]);
11811254
}
11821255
}
@@ -1185,7 +1258,11 @@ - (void)didReceiveDiagnosticPayloads:(NSArray<MXDiagnosticPayload *> *)payloads
11851258
{
11861259
for (MXDiagnosticPayload *payload in payloads)
11871260
{
1261+
/* +1 from alloc+init; per-iteration leak inside the loop under
1262+
* MRR without an autorelease. Statement-only macro, so on its
1263+
* own line. No-op under ARC. */
11881264
NSString *json = [[NSString alloc] initWithData:[payload JSONRepresentation] encoding:kCFStringEncodingUTF8];
1265+
RARCH_AUTORELEASE(json);
11891266
RARCH_LOG("[Cocoa] Got Diagnostic Payload:\n%s\n", [json cStringUsingEncoding:kCFStringEncodingUTF8]);
11901267
}
11911268
}
@@ -1314,6 +1391,27 @@ - (void)textFieldDidEndEditing:(UITextField *)textField
13141391
}
13151392
}
13161393

1394+
#if !__has_feature(objc_arc)
1395+
/* RetroArch_iOS is the UIApplication delegate and therefore a
1396+
* process-lifetime singleton - this dealloc effectively never runs in
1397+
* practice. Keeping it for symmetry with ui_cocoa.m's dealloc and so
1398+
* the ownership picture is complete for anyone reading the file: every
1399+
* retained ivar / property has a paired release here, and _renderView
1400+
* is released by -setViewType: whenever it is reassigned (see above).
1401+
* No-op under ARC where retained ivars/properties are released
1402+
* automatically. */
1403+
- (void)dealloc
1404+
{
1405+
RARCH_RELEASE(_renderView);
1406+
RARCH_RELEASE(_window);
1407+
RARCH_RELEASE(_documentsDirectory);
1408+
RARCH_RELEASE(_bgDate);
1409+
RARCH_RELEASE(_keyboardTextField);
1410+
RARCH_RELEASE(_keyboardCompletionCallback);
1411+
RARCH_SUPER_DEALLOC();
1412+
}
1413+
#endif
1414+
13171415
@end
13181416

13191417
ui_companion_driver_t ui_companion_cocoatouch = {

0 commit comments

Comments
 (0)