callstack / callstack/react-native-brownfield
iOS: emitOnBrownfieldMessage: can crash with std::bad_function_call when native posts before JS requires the TurboModule (cold-start race)
- Vorherrschende Sprache
- TypeScript
- Sterne
- 547
- Forks
- 52
- Ø Merge
- 3 T. 16 Std.
- Gemergte PRs (30 T.)
- 5
Beschreibung
`ReactNativeBrownfieldModule.mm`'s `+emitMessageFromNative:` only checks that the Obj-C singleton exists before calling `emitOnBrownfieldMessage:`:
```objc
+ (void)emitMessageFromNative:(NSString *)message {
if (_sharedInstance) {
[_sharedInstance emitOnBrownfieldMessage:@{ @"text": message }];
} else {
NSLog(@"ReactNativeBrownfieldModule is not initialized, dropping message");
}
}
```
`_sharedInstance` is set in `-init`, which runs as soon as the bridge/TurboModuleManager instantiates the Obj-C module. But the codegen `SpecBase`'s `_eventEmitterCallback` (the `std::function` that `emitOnBrownfieldMessage:` invokes) is only populated inside the C++ `SpecJSI` constructor, which only runs when `getTurboModule:` fires — i.e. when JS actually evaluates `TurboModuleRegistry.getEnforcing('ReactNativeBrownfield')`.
If a host app calls `ReactNativeBrownfield.shared.postMessage(...)` (→ posts `BrownfieldMessageToJSNotification` → `handleNativeToJSMessage:` → `emitMessageFromNative:`) after the Obj-C module exists but before JS has required the module — e.g. right as a hosting view controller's `viewDidAppear` fires on a cold app launch, racing bundle evaluation — `_eventEmitterCallback` is still empty, and calling it throws `std::bad_function_call`, crashing the whole app (uncaught C++ exception).
Confirmed still present on `main` (4.0.0) as of 2026-07-06 — `emitMessageFromNative:`'s guard is unchanged from what's below (also reproduced against 3.3.0 in our own app):
```objc
- (void)handleNativeToJSMessage:(NSNotification *)notification {
NSString *message = notification.userInfo[@"message"];
if (message) {
[ReactNativeBrownfieldModule emitMessageFromNative:message];
}
}
```
### Suggested fixes
- Wrap the `_eventEmitterCallback` invocation in a readiness check (or try/catch) inside the generated `emitOnBrownfieldMessage:`, or
- Expose a callback/promise so host apps can gate native→JS `postMessage` calls on "TurboModule ready" rather than just "bundle loaded" (the closest signal `startReactNative(onBundleLoaded:)` currently offers).
### Repro
Timing-dependent — we haven't produced a deterministic repro case, but the code path is unambiguous from source, and we hit it in production (Crashlytics, `std::__1::bad_function_call: std::exception`, 100% of occurrences within the first second of a session). Happy to share a sanitized stack trace if useful.
### Environment
- `@callstack/react-native-brownfield`: 3.3.0 (also read against `main`/4.0.0 source)
- iOS 26.x, React Native new architecture (TurboModules/Fabric)
Beitragsleitfaden
Rechercherichtung
Beginne in ReactNativeBrownfieldModule.mm mit -handleNativeToJSMessage: und +emitMessageFromNative:, und verfolge dann emitOnBrownfieldMessage: bis zur Initialisierung des Callbacks in der generierten SpecBase. Reproduziere einen nativen Post oder instrumentiere ihn, bevor JS das TurboModule anfordert, und überprüfe, dass dieser Kaltstartpfad nicht mehr abstürzt, wenn der Callback nicht verfügbar ist.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- cpp, ios, objective-c, react-native
- Bereich
- api, mobile-dev
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Ruhig
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 48/100