From afc73edeec950932c71632663005816689188c76 Mon Sep 17 00:00:00 2001 From: Thomson Thomas Date: Thu, 5 Mar 2026 14:01:12 -0500 Subject: [PATCH] fix(ios): prevent app crash in Release builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TurboModule bridge passes a null C++ reference when JS omits the optional roktConfig parameter. The existing guard `&roktConfig != nullptr` is undefined behavior in C++ — the compiler is permitted to assume a reference is never null and remove the check entirely. In Debug builds (-O0) the check survives by accident, so selectPlacements works when run from Xcode. In Release builds (-O2) the compiler optimizes the check away, and the subsequent call to roktConfig.cacheConfig() dereferences address 0x0, producing EXC_BAD_ACCESS (SIGSEGV) on the com.meta.react.turbomodulemanager.queue thread. Because the crash occurs on a background dispatch queue, the placement silently fails to appear with no visible error — partners report "selectPlacements does nothing" rather than a crash. Extracted the roktConfig dictionary conversion into a standalone static function annotated with __attribute__((optnone)) to prevent the compiler from applying UB-based optimizations, ensuring the null reference check is preserved in all build configurations. --- ios/RNMParticle/RNMPRokt.mm | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/ios/RNMParticle/RNMPRokt.mm b/ios/RNMParticle/RNMPRokt.mm index 32c154c..5260e14 100644 --- a/ios/RNMParticle/RNMPRokt.mm +++ b/ios/RNMParticle/RNMPRokt.mm @@ -58,18 +58,17 @@ - (void)setMethodQueue:(dispatch_queue_t)methodQueue } #ifdef RCT_NEW_ARCH_ENABLED -// New Architecture Implementation -- (void)selectPlacements:(NSString *)identifer - attributes:(NSDictionary *)attributes - placeholders:(NSDictionary *)placeholders - roktConfig:(JS::NativeMPRokt::RoktConfigType &)roktConfig - fontFilesMap:(NSDictionary *)fontFilesMap -{ - NSMutableDictionary *finalAttributes = [self convertToMutableDictionaryOfStrings:attributes]; - - // Convert JS struct to NSDictionary for internal use +// Extracts roktConfig fields into an NSDictionary, returning nil when the +// TurboModule bridge passes a null C++ reference for an omitted optional param. +// __attribute__((optnone)) is required: &ref != nullptr is UB in C++ and the +// compiler removes the check at -O2, causing a SIGSEGV in Release builds. +static NSDictionary * __attribute__((optnone)) safeExtractRoktConfigDict( + JS::NativeMPRokt::RoktConfigType &roktConfig) { + if (&roktConfig == nullptr) { + return nil; + } NSMutableDictionary *roktConfigDict = [[NSMutableDictionary alloc] init]; - if (&roktConfig != nullptr && roktConfig.cacheConfig().has_value()) { + if (roktConfig.cacheConfig().has_value()) { NSMutableDictionary *cacheConfigDict = [[NSMutableDictionary alloc] init]; auto cacheConfig = roktConfig.cacheConfig().value(); if (cacheConfig.cacheDurationInSeconds().has_value()) { @@ -80,7 +79,19 @@ - (void)selectPlacements:(NSString *)identifer } roktConfigDict[@"cacheConfig"] = cacheConfigDict; } + return roktConfigDict; +} + +// New Architecture Implementation +- (void)selectPlacements:(NSString *)identifer + attributes:(NSDictionary *)attributes + placeholders:(NSDictionary *)placeholders + roktConfig:(JS::NativeMPRokt::RoktConfigType &)roktConfig + fontFilesMap:(NSDictionary *)fontFilesMap +{ + NSMutableDictionary *finalAttributes = [self convertToMutableDictionaryOfStrings:attributes]; + NSDictionary *roktConfigDict = safeExtractRoktConfigDict(roktConfig); MPRoktConfig *config = [self buildRoktConfigFromDict:roktConfigDict]; #else // Old Architecture Implementation