Skip to content

[iOS][Fabric] 0.86.2 Release builds crash on cold launch with heap corruption when mounting an Expo native view; framework-swap bisection pins it to the prebuilt core binary #57916

Description

@SpiGAndromeda

Description

A minimal app that mounts a single native view from expo-symbols crashes on most cold launches under React Native 0.86.2, and never under 0.86.0. The reproduction repository is at https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash, created from the reproducer-react-native template. Each test below launches the installed app ten times on an iOS simulator, terminating it between launches and waiting 6 seconds per launch; a launch counts as crashed when the process is no longer running after the wait:

Configuration Crashed launches
Template reproducer (HEAD), 0.86.2 prebuilt core 10/10
Template reproducer at HEAD~1, react-native 0.86.0 0/10
Expo-prebuild reproducer, 0.86.2 prebuilt core 9/10
Same built binary, only React.framework swapped to 0.86.0-release 0/10
Production app the trigger was isolated from, 0.86.2 7/10 to 10/10
Production app with only React.framework swapped to 0.86.0-release 0/10

The two Expo-prebuild reproducer runs used the same built .app, so the only difference between 9/10 and 0/10 is the core framework binary. Because React.framework and hermesvm.framework are separate dylibs in the bundle, they can be swapped in a built app (plus ad-hoc re-signing) without recompiling anything. Swapping only the Hermes VM (250829098.0.16 back to 250829098.0.14) changes nothing, so the Hermes V1 bump is ruled out. The defect therefore lives in the react-native-artifacts 0.86.2 reactnative-core binary.

I verified by UUID (dwarfdump --uuid against the Maven tarballs cached in Pods/*-artifacts/) that each app carried the release flavor of React.framework and hermesvm.framework, so this is not the debug/release swap issue from #57803.

The trigger was isolated from a production app by removing one launch ingredient at a time: the full app crashes, the app without a single mounted SymbolView (the SF Symbols view from expo-symbols) does not, and a trivial app containing nothing but a <View>, a <Text> and that one SymbolView crashes again. The production app's remaining native modules were then removed without changing the outcome, so the reproducer links only expo and expo-symbols.

Three configuration facts, each load-bearing for the reproduction:

  • The crash requires Expo's precompiled module binaries: with EXPO_USE_PRECOMPILED_MODULES=1 in the Podfile (the expo prebuild default, but not the CocoaPods default), ExpoModulesCore and its sibling pods arrive as prebuilt dynamic xcframeworks and the app crashes 10/10; with the same pods compiled from source and linked statically, the same pin set on the same 0.86.2 core is clean 0/10. Whether the discriminator is the dynamic linkage or the shipped binaries is not determined. A reproduction attempt from a bare @react-native-community/cli init app without this flag concludes the bug does not exist.
  • The trigger depends on the expo-modules-core version, with an exact boundary: at expo-modules-core 57.0.7 (together with the ExpoModulesWorklets it ships and versions in lockstep) the app crashes 10/10; raising only expo-modules-core to 57.0.8 or 57.0.10, everything else pinned unchanged, drops that to 0/10. This masks the crash rather than provably fixing it: the framework swap above localises the defect to the 0.86.2 reactnative-core binary, and a consumer-side change that stops triggering the defect is consistent with the defect remaining. The floated SDK 57 patch set current today (expo 57.0.12, expo-symbols 57.0.2, expo-modules-core 57.0.10) is accordingly clean over twenty launches.
  • The repository pins the full expo module set and commits its package-lock.json, because ~ ranges across expo native modules resolve to ABI-mismatched sibling sets that abort at dyld before any React code runs. That failure inflates naive crash counts and has nothing to do with this bug.

The crashes are heap corruption, not a single bad dereference. The crash reports show the same two variants across the reproducers and the production app: EXC_BREAKPOINT raised inside libsystem_malloc (xzone malloc detecting corrupted heap state) during allocations on the JavaScript thread (under RawPropsParser::at and under CoreText font-name resolution through a corrupted CFDictionary), and EXC_BAD_ACCESS on the main thread in -[RCTComponentViewFactory createComponentViewWithComponentHandle:] during a mounting transaction.

Narrowing the source delta: v0.86.0...v0.86.2 contains four functional commits (the 0.86.2 release notes omit what shipped in the unpublished 0.86.1). Two are Android-only (821045a, cdfba52). The Hermes V1 bump (9553811) is ruled out by the Hermes-swap result above. That leaves the Yoga display: contents fix (36f69ef) and 0b941bc, "Add guards around nativeProps usage to prevent race conditions" (#52646), which reworks ShadowNode::clone and UIManager. Which of the two corrupts the heap is not established. The reproducer contains no setNativeProps call and no display: 'contents' node, so I can offer the binary-level localisation and the trigger, not the commit. 0.87.0 carries both commits with no follow-up fixes to them.

Environment: iOS 26.5 simulator on macOS 26.5.2, Xcode 26.6, Hermes, New Architecture (bridgeless).

Steps to reproduce

  1. Clone https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash and follow its README: npm ci, cd ios && pod install, then a Release simulator build with xcodebuild. The pinned dependency versions are part of the reproduction; do not update them.
  2. Run tools/verify-flavors.sh on the built app (UUID check), then tools/census.sh for the ten-launch series: 10/10 crash during the first mount. tools/crash-signature.sh reads the exit signals from the simulator log (SIGTRAP/SIGSEGV are the crash; SIGABRT would indicate a broken dependency set instead).
  3. git checkout HEAD~1 is the identical app on react-native 0.86.0; built the same way, it launches clean 0/10. git diff HEAD~1 HEAD is the complete regression delta: the react-native pin, its lockstep @react-native/* pins, and the regenerated lockfiles.
  4. The framework-swap counter-experiment (replacing only Frameworks/React.framework inside an already-built app) and the expo-modules-core masking measurements are in the supplementary repository: https://github.com/it-bens/rn-0862-ios-fabric-launch-crash (tools/swap-core.sh).
  5. If crashes stop producing .ips reports, that is ReportCrash throttling repeated identical crashes; both READMEs document counting abnormal exits from the simulator log instead.

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: 3.72 GB / 48.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 26.5.0
    path: /Users/martinbens/.nvm/versions/node/v26.5.0/bin/node
  Yarn: Not Found
  npm:
    version: 11.17.0
    path: /Users/martinbens/.nvm/versions/node/v26.5.0/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.17.0
    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:
      - "35"
      - "36"
    Build Tools:
      - 35.0.0
      - 36.0.0
    System Images:
      - android-35 | Google APIs ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: Not Found
  Xcode:
    version: 26.6/17F113
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.20
    path: /usr/bin/javac
  Ruby:
    version: 4.0.6
    path: /opt/homebrew/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 19.2.8
    wanted: 19.2.8
  react-native:
    installed: 0.86.2
    wanted: 0.86.2
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: Not found
  newArchEnabled: Not found
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Reproducer dependency pins: expo 57.0.8, expo-symbols 57.0.1, and npm overrides holding expo-modules-core 57.0.7, expo-file-system 57.0.1, expo-asset 57.0.7, expo-font 57.0.1, expo-keep-awake 57.0.1, expo-constants 57.0.10. The committed package-lock.json is authoritative.

Stacktrace or Logs

Two representative faulting threads (full `.ips` files available on request,
from both the reproducer and the production app). Heap-corruption abort on the
JavaScript thread:


Exception: EXC_BREAKPOINT
Thread: com.facebook.react.runtime.JavaScript
  libsystem_malloc.dylib  _xzm_xzone_malloc_freelist_outlined
  libsystem_malloc.dylib  _xzm_xzone_malloc_tiny
  libc++abi.dylib         operator new(unsigned long)
  React                   <deduplicated_symbol>
  React                   std::__1::vector<facebook::react::RawPropsKey, ...>::push_back(...)
  React                   facebook::react::RawPropsParser::at(facebook::react::RawProps const&, facebook::react::RawPropsKey const&) const


Null-region dereference on the main thread during a mounting transaction:


Exception: EXC_BAD_ACCESS, KERN_INVALID_ADDRESS at 0x0000000000000018
Thread: com.apple.main-thread
  React  -[RCTComponentViewFactory createComponentViewWithComponentHandle:]
  React  -[RCTComponentViewRegistry _dequeueComponentViewWithComponentHandle:]
  React  -[RCTComponentViewRegistry dequeueComponentViewWithComponentHandle:tag:]
  React  RCTPerformMountInstructions(...)
  React  facebook::react::TelemetryController::pullTransaction(...)
  React  -[RCTMountingManager performTransaction:]


Loaded-image UUIDs in the crashing processes match the release tarballs: `React`
= 791C3298-6723-3FE6-B25E-4C6BD2F09175 (reactnative-core-0.86.2-release, arm64
simulator slice), `hermesvm` = 75872184-F738-3E5C-B47A-FD4BCE09605A
(hermes-ios-250829098.0.16-release). The clean runs carry `React` =
61086F4A-30F5-3EA0-A72A-2C94C71C7AAC (reactnative-core-0.86.0-release).

MANDATORY Reproducer

https://github.com/SpiGAndromeda/reproducer-rn-0862-symbolview-launch-crash

Created from the reproducer-react-native template (generated-from metadata intact; the template's pre-merge CI is green on the reproducing state), with the Expo modules integrated manually and App.tsx mounting one <SymbolView name="gearshape" /> next to a <Text>. On top of the two template commits sit exactly two more: a working baseline at react-native 0.86.0 (0/10 crashed, flavor-verified), and HEAD, which moves only the react-native pin to 0.86.2 with its lockstep @react-native/* pins and lockfiles (10/10 crashed; exit signals 7 SIGTRAP + 3 SIGSEGV, zero SIGABRT). It ships tools/verify-flavors.sh, tools/census.sh and tools/crash-signature.sh, and commits the package-lock.json that holds the expo module set at the versions the crash requires.

Supplementary repository with the dylib-swap tooling and the expo-modules-core masking table: https://github.com/it-bens/rn-0862-ios-fabric-launch-crash.

Screenshots and Videos

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: AttentionIssues where the author has responded to feedback.Needs: ReproThis issue could be improved with a clear list of steps to reproduce the issue.Platform: iOSiOS applications.Type: New ArchitectureIssues and PRs related to new architecture (Fabric/Turbo Modules)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions