Description
Summary
Release builds crash with SIGSEGV (fault address 0x18) on com.apple.main-thread in
-[RCTComponentViewFactory createComponentViewWithComponentHandle:] during Fabric mounting.
Observed in production on iOS 26.0.1, RN 0.86.2, New Architecture enabled, legacy interop layer in use.
The affected code is unchanged in 0.86.3 and 0.87.1 (I diffed
React/Fabric/Mounting/RCTComponentViewFactory.mm across tags — 0.86.3 is byte-identical;
0.87.1 only adds #ifdef RCT_REMOVE_LEGACY_COMPONENT_INTEROP around the interop fallback),
so this reproduces on latest stable.
Root cause
Two unsynchronized shared-state mutations, plus a release-only hard-crash path:
1. _registerComponentIfPossible: writes _componentViewClasses without holding _mutex.
It runs on the JS thread (via the hasComponentProvider binding installed by
RCTInstallNativeComponentRegistryBinding, and via the provider-request callback).
Fallbacks 1/2 are safe (they go through registerComponentViewClass:, which takes
std::unique_lock), but Fallback 3 (interop) and Fallback 4 (UnimplementedView) write
directly into _componentViewClasses and _registrationStatusMap with no lock.
Meanwhile the main thread reads the same map in createComponentViewWithComponentHandle:
under a shared_lock — which protects nothing against a writer that never locks.
A concurrent insert can rehash the unordered_map mid-find.
2. On a lookup miss, release builds dereference the end() iterator.
RCTAssert(iterator != _componentViewClasses.end(), ...) is compiled out in release, and the
next line does iterator->second. libc++'s end() iterator has a null node pointer, so this
reads nullptr + 0x18 — matching the crash address exactly.
3. Same pattern in RCTLegacyViewManagerInteropComponentView isSupported: — Step 3
mutates the static NSMutableDictionary cache (_supportedLegacyViewComponents) from the
JS thread with no synchronization; NSMutableDictionary is not thread-safe.
Being a data race, it fires probabilistically — for us the rate jumped visibly on iOS 26
(changed thread timing), but the defect is version-independent.
Reproducer
- Deterministic for the crash site: make
_registerComponentIfPossible: skip one component
name and mount it — release config crashes at the same address; debug shows the assert.
- The race itself: Thread Sanitizer flags the unsynchronized
_componentViewClasses access
during cold start + navigation through interop-using screens.
- Possibly related community reproducer (same RN version, same crash site during mounting):
https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash
Fix we are shipping via patch-package
_registerComponentIfPossible: takes std::unique_lock lock(_mutex) once at the top,
covering the early-return check and Fallback 3/4 writes.
- To avoid self-deadlock (
std::shared_mutex is non-recursive), registerComponentViewClass:
is split into the public locking wrapper + a private _registerComponentViewClassLocked:
body; Fallbacks 1/2 call the locked variant.
_wasComponentRegistered: reads under a shared_lock.
- Release-build guard at the crash site: on
end(), log via RCTLogError and mount
RCTUnimplementedViewComponentView instead of dereferencing.
isSupported: Step 3 wrapped in @synchronized on the dictionary it mutates.
Full patch attached below. Happy to open a PR if the approach looks right to maintainers.
Full patch (react-native+0.86.2, applied via patch-package)
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm
index 226781f..522a7cc 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm
@@ -122,27 +122,30 @@ + (BOOL)isSupported:(NSString *)componentName
NSArray<Class> *registeredModules = RCTGetModuleClasses();
NSMutableDictionary<NSString *, Class> *supportedLegacyViewComponents =
[RCTLegacyViewManagerInteropComponentView _supportedLegacyViewComponents];
- if (supportedLegacyViewComponents[componentName] != NULL) {
- return YES;
- }
+ // Fix: static NSMutableDictionary mutated from multiple threads.
+ @synchronized(supportedLegacyViewComponents) {
+ if (supportedLegacyViewComponents[componentName] != NULL) {
+ return YES;
+ }
- for (Class moduleClass in registeredModules) {
- id<RCTBridgeModule> bridgeModule = (id<RCTBridgeModule>)moduleClass;
- NSString *moduleName = [[bridgeModule moduleName] isEqualToString:@""]
- ? [NSStringFromClass(moduleClass) stringByReplacingOccurrencesOfString:@"Manager" withString:@""]
- : [bridgeModule moduleName];
+ for (Class moduleClass in registeredModules) {
+ id<RCTBridgeModule> bridgeModule = (id<RCTBridgeModule>)moduleClass;
+ NSString *moduleName = [[bridgeModule moduleName] isEqualToString:@""]
+ ? [NSStringFromClass(moduleClass) stringByReplacingOccurrencesOfString:@"Manager" withString:@""]
+ : [bridgeModule moduleName];
- if (supportedLegacyViewComponents[moduleName] == NULL) {
- supportedLegacyViewComponents[moduleName] = moduleClass;
- }
+ if (supportedLegacyViewComponents[moduleName] == NULL) {
+ supportedLegacyViewComponents[moduleName] = moduleClass;
+ }
- if ([moduleName isEqualToString:componentName] ||
- [moduleName isEqualToString:[@"RCT" stringByAppendingString:componentName]]) {
- return YES;
+ if ([moduleName isEqualToString:componentName] ||
+ [moduleName isEqualToString:[@"RCT" stringByAppendingString:componentName]]) {
+ return YES;
+ }
}
- }
- return NO;
+ return NO;
+ }
}
+ (void)supportLegacyViewManagersWithPrefix:(NSString *)prefix
diff --git a/node_modules/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm b/node_modules/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm
index 608d75c..d9f4c3b 100644
--- a/node_modules/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm
@@ -48,6 +48,7 @@
@interface RCTComponentViewFactory ()
- (void)_registerComponentIfPossible:(const std::string &)name;
- (BOOL)_wasComponentRegistered:(const std::string &)name;
+- (void)_registerComponentViewClassLocked:(Class<RCTComponentViewProtocol>)componentViewClass;
@end
// Allow JS runtime to register native components as needed. For static view configs.
@@ -115,12 +116,15 @@ - (RCTComponentViewClassDescriptor)_componentViewClassDescriptorFromClass:(Class
- (BOOL)_wasComponentRegistered:(const std::string &)name
{
+ std::shared_lock lock(_mutex);
auto registrationResult = _registrationStatusMap.find(name);
return registrationResult != _registrationStatusMap.end() && (registrationResult->second);
}
- (void)_registerComponentIfPossible:(const std::string &)name
{
+ // Fix: fallback 3/4 below mutated the maps unguarded while the main thread reads them.
+ std::unique_lock lock(_mutex);
if (_registrationStatusMap.find(name) != _registrationStatusMap.end()) {
return;
}
@@ -133,7 +137,7 @@ - (void)_registerComponentIfPossible:(const std::string &)name
// Fallback 1: Call provider function for component view class.
Class<RCTComponentViewProtocol> klass = RCTComponentViewClassWithName(name.c_str());
if (klass != nullptr) {
- [self registerComponentViewClass:klass];
+ [self _registerComponentViewClassLocked:klass];
return;
}
@@ -144,7 +148,7 @@ - (void)_registerComponentIfPossible:(const std::string &)name
NSString *objcName = [NSString stringWithCString:name.c_str() encoding:NSUTF8StringEncoding];
klass = self.thirdPartyFabricComponentsProvider.thirdPartyFabricComponents[objcName];
if (klass != nullptr) {
- [self registerComponentViewClass:klass];
+ [self _registerComponentViewClassLocked:klass];
return;
}
}
@@ -185,7 +189,12 @@ - (void)registerComponentViewClass:(Class<RCTComponentViewProtocol>)componentVie
{
RCTAssert(componentViewClass, @"RCTComponentViewFactory: Provided `componentViewClass` is `nil`.");
std::unique_lock lock(_mutex);
+ [self _registerComponentViewClassLocked:componentViewClass];
+}
+// Body of registerComponentViewClass:, callable by paths already holding _mutex
+// (std::shared_mutex is not recursive).
+- (void)_registerComponentViewClassLocked:(Class<RCTComponentViewProtocol>)componentViewClass
+{
auto provider = [componentViewClass componentDescriptorProvider];
_componentViewClasses[provider.handle] = [self _componentViewClassDescriptorFromClass:componentViewClass];
_providerRegistry.add(provider);
@@ -209,6 +218,18 @@ - (RCTComponentViewDescriptor)createComponentViewWithComponentHandle:(facebook::
@"ComponentView with componentHandle `%lli` (`%s`) not found.",
componentHandle,
(char *)componentHandle);
+ // Fix: RCTAssert is compiled out in release; dereferencing end() here was a SIGSEGV.
+ if (iterator == _componentViewClasses.end()) {
+ RCTLogError(@"ComponentView with componentHandle `%lli` not found, mounting fallback view.", componentHandle);
+ auto fallbackDescriptor = [self _componentViewClassDescriptorFromClass:[RCTUnimplementedViewComponentView class]];
+ Class fallbackViewClass = fallbackDescriptor.viewClass;
+ return RCTComponentViewDescriptor{
+ .view = [fallbackViewClass new],
+ .observesMountingTransactionWillMount = fallbackDescriptor.observesMountingTransactionWillMount,
+ .observesMountingTransactionDidMount = fallbackDescriptor.observesMountingTransactionDidMount,
+ .shouldBeRecycled = fallbackDescriptor.shouldBeRecycled,
+ };
+ }
auto componentViewClassDescriptor = iterator->second;
Class viewClass = componentViewClassDescriptor.viewClass;
Environment
- react-native: 0.86.2 (code verified unchanged in 0.87.1)
- New Architecture: enabled; legacy interop layer in use (~29 Paper view managers)
- iOS 26.0.1, physical devices, release builds
- Crash: SIGSEGV, fault address 0x0000000000000018, com.apple.main-thread
Steps to reproduce
This is a data race, so there is no tap-this-button repro. Three ways to observe it:
-
Crash site, deterministic: in RCTComponentViewFactory.mm, make
_registerComponentIfPossible: return early for one component name used by the app
(simulating a lost race), build in Release configuration, and mount a screen using that
component → SIGSEGV at fault address 0x18 in createComponentViewWithComponentHandle:.
In Debug the RCTAssert fires instead, confirming the release/debug divergence.
-
The race itself: enable Thread Sanitizer, cold-start an app that uses the legacy
interop layer, and navigate quickly through screens during startup. TSan reports the
unsynchronized read/write on _componentViewClasses (Fallback 3/4 of
_registerComponentIfPossible: write it with _mutex not held, while the main thread
reads it in createComponentViewWithComponentHandle:).
-
In production: occurs spontaneously at low rate; frequency increased noticeably on
iOS 26.0.x (changed thread timing). Possibly related community reproducer:
https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash
React Native Version
0.86.2
Affected Platforms
Runtime - iOS
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 26.5.2
CPU: (12) arm64 Apple M4 Pro
Memory: 135.67 MB / 24.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 25.6.1
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.22
path: /opt/homebrew/bin/yarn
npm:
version: 11.9.0
path: /opt/homebrew/bin/npm
Watchman:
version: 2026.01.12.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 25.5
- iOS 26.5
- macOS 26.5
- tvOS 26.5
- visionOS 26.5
- watchOS 26.5
Android SDK:
API Levels:
- "31"
- "34"
- "35"
- "36"
Build Tools:
- 35.0.0
- 36.0.0
System Images:
- android-30 | Google APIs ARM 64 v8a
- android-30 | Google APIs Intel x86 Atom
- android-30 | Google APIs Intel x86_64 Atom
- android-35 | Google APIs ARM 64 v8a
- android-37.2-beta3 | 16 KB Page Size Google Play ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2025.3 AI-253.29346.138.2531.14876573
Xcode:
version: 26.6/17F113
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.18
path: /usr/bin/javac
Ruby:
version: 4.0.1
path: /opt/homebrew/opt/ruby/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.1.0
wanted: 20.1.0
react:
installed: 19.2.3
wanted: 19.2.3
react-native:
installed: 0.86.2
wanted: 0.86.2
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000018
Triggered by Thread: 0 (com.apple.main-thread)
iOS 26.0.1, Release build, RN 0.86.2 (New Architecture, interop layer enabled)
0 Nubra -[RCTComponentViewFactory createComponentViewWithComponentHandle:] (RCTComponentViewFactory.mm:212)
1 Nubra -[RCTComponentViewRegistry _dequeueComponentViewWithComponentHandle:] (RCTComponentViewRegistry.mm:96)
2 Nubra -[RCTComponentViewRegistry dequeueComponentViewWithComponentHandle:tag:] (RCTComponentViewRegistry.mm:52)
3 Nubra std::__1::__function::__func<-[RCTMountingManager performTransaction:]::$_2, ...>::operator() (RCTMountingManager.mm:56)
4 Nubra facebook::react::TelemetryController::pullTransaction(...) const (TelemetryController.cpp:40)
5 Nubra -[RCTMountingManager performTransaction:] (function.h:241)
6 Nubra -[RCTMountingManager initiateTransaction:] (RCTMountingManager.mm:248)
7 libdispatch.dylib _dispatch_call_block_and_release
...
19 Nubra main (AppDelegate.swift)
Frame 0 is the `iterator->second` dereference after
`_componentViewClasses.find(componentHandle)` returns `end()` —
the RCTAssert above it is compiled out in Release, and libc++'s
end() iterator holds a null node pointer, hence fault address 0x18.
MANDATORY Reproducer
https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash
Screenshots and Videos

Description
Summary
Release builds crash with SIGSEGV (fault address
0x18) oncom.apple.main-threadin-[RCTComponentViewFactory createComponentViewWithComponentHandle:]during Fabric mounting.Observed in production on iOS 26.0.1, RN 0.86.2, New Architecture enabled, legacy interop layer in use.
The affected code is unchanged in 0.86.3 and 0.87.1 (I diffed
React/Fabric/Mounting/RCTComponentViewFactory.mmacross tags — 0.86.3 is byte-identical;0.87.1 only adds
#ifdef RCT_REMOVE_LEGACY_COMPONENT_INTEROParound the interop fallback),so this reproduces on latest stable.
Root cause
Two unsynchronized shared-state mutations, plus a release-only hard-crash path:
1.
_registerComponentIfPossible:writes_componentViewClasseswithout holding_mutex.It runs on the JS thread (via the
hasComponentProviderbinding installed byRCTInstallNativeComponentRegistryBinding, and via the provider-request callback).Fallbacks 1/2 are safe (they go through
registerComponentViewClass:, which takesstd::unique_lock), but Fallback 3 (interop) and Fallback 4 (UnimplementedView) writedirectly into
_componentViewClassesand_registrationStatusMapwith no lock.Meanwhile the main thread reads the same map in
createComponentViewWithComponentHandle:under a
shared_lock— which protects nothing against a writer that never locks.A concurrent insert can rehash the
unordered_mapmid-find.2. On a lookup miss, release builds dereference the
end()iterator.RCTAssert(iterator != _componentViewClasses.end(), ...)is compiled out in release, and thenext line does
iterator->second. libc++'send()iterator has a null node pointer, so thisreads
nullptr + 0x18— matching the crash address exactly.3. Same pattern in
RCTLegacyViewManagerInteropComponentView isSupported:— Step 3mutates the static
NSMutableDictionarycache (_supportedLegacyViewComponents) from theJS thread with no synchronization;
NSMutableDictionaryis not thread-safe.Being a data race, it fires probabilistically — for us the rate jumped visibly on iOS 26
(changed thread timing), but the defect is version-independent.
Reproducer
_registerComponentIfPossible:skip one componentname and mount it — release config crashes at the same address; debug shows the assert.
_componentViewClassesaccessduring cold start + navigation through interop-using screens.
https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash
Fix we are shipping via patch-package
_registerComponentIfPossible:takesstd::unique_lock lock(_mutex)once at the top,covering the early-return check and Fallback 3/4 writes.
std::shared_mutexis non-recursive),registerComponentViewClass:is split into the public locking wrapper + a private
_registerComponentViewClassLocked:body; Fallbacks 1/2 call the locked variant.
_wasComponentRegistered:reads under ashared_lock.end(), log viaRCTLogErrorand mountRCTUnimplementedViewComponentViewinstead of dereferencing.isSupported:Step 3 wrapped in@synchronizedon the dictionary it mutates.Full patch attached below. Happy to open a PR if the approach looks right to maintainers.
Full patch (react-native+0.86.2, applied via patch-package)
Environment
Steps to reproduce
This is a data race, so there is no tap-this-button repro. Three ways to observe it:
Crash site, deterministic: in
RCTComponentViewFactory.mm, make_registerComponentIfPossible:return early for one component name used by the app(simulating a lost race), build in Release configuration, and mount a screen using that
component → SIGSEGV at fault address 0x18 in
createComponentViewWithComponentHandle:.In Debug the
RCTAssertfires instead, confirming the release/debug divergence.The race itself: enable Thread Sanitizer, cold-start an app that uses the legacy
interop layer, and navigate quickly through screens during startup. TSan reports the
unsynchronized read/write on
_componentViewClasses(Fallback 3/4 of_registerComponentIfPossible:write it with_mutexnot held, while the main threadreads it in
createComponentViewWithComponentHandle:).In production: occurs spontaneously at low rate; frequency increased noticeably on
iOS 26.0.x (changed thread timing). Possibly related community reproducer:
https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash
React Native Version
0.86.2
Affected Platforms
Runtime - iOS
Areas
Fabric - The New Renderer
Output of
npx @react-native-community/cli infoStacktrace or Logs
MANDATORY Reproducer
https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash
Screenshots and Videos