Skip to content

[iOS] EXC_BAD_ACCESS in EnrichedMarkdownShadowNode clone constructor (dirtyLayoutIfNeeded → YGNodeMarkDirty) under state updates on RN 0.85 #405

Description

@qq382724935

Description

On React Native 0.85 (New Architecture / Fabric, iOS), EnrichedMarkdownText (via react-native-streamdown's StreamdownText) crashes with EXC_BAD_ACCESS whenever the component's state updates (height recalculation write-back). The crash is inside the EnrichedMarkdownShadowNode clone constructor, which calls dirtyLayoutIfNeeded()YGNodeMarkDirty(&yogaNode_) on a freshly-cloned node whose Yoga node has no measure function attached yet.

Environment

package version
react-native-enriched-markdown 0.6.0
react-native 0.85.3
New Architecture (Fabric) enabled
react-native-streamdown 0.2.0
platform iOS device, arm64, Hermes

Crash & backtrace

EXC_BAD_ACCESS (code=1, address=0x104100) on thread com.facebook.react.runtime.JavaScript:

#0  0x0000000000104100
#1  YGNode::markDirtyAndPropagate() + 68
#2  EnrichedMarkdownShadowNode::dirtyLayoutIfNeeded()                 EnrichedMarkdownShadowNode.mm:41
#3  EnrichedMarkdownShadowNode::EnrichedMarkdownShadowNode(
        const ShadowNode &source, const ShadowNodeFragment &)         EnrichedMarkdownShadowNode.mm:31
#4  EnrichedMarkdownShadowNode::EnrichedMarkdownShadowNode(...)       EnrichedMarkdownShadowNode.mm:23
#5  std::construct_at<EnrichedMarkdownShadowNode, ...>
#12 ConcreteComponentDescriptor<EnrichedMarkdownShadowNode>::cloneShadowNode()
#13 ShadowNode::clone(ShadowNodeFragment const&)
#21 ShadowNode::cloneTree(...)
#29 ShadowTree::tryCommit(...)
#30 ShadowTree::commit(...)
#39 UIManager::updateState(StateUpdate const&)
#47 EventQueueProcessor::flushStateUpdates(...)
#48 EventQueue::flushStateUpdates()

Offending code:

// EnrichedMarkdownShadowNode.mm
void EnrichedMarkdownShadowNode::dirtyLayoutIfNeeded() {
  const auto state = this->getStateData();
  const int receivedCounter = state.getHeightRecalculationCounter();
  if (receivedCounter > localHeightRecalculationCounter_) {
    localHeightRecalculationCounter_ = receivedCounter;
    YGNodeMarkDirty(&yogaNode_);   // line 41 — EXC_BAD_ACCESS
  }
}

Root cause

  1. The measured height is written back into the ShadowNode state (heightRecalculationCounter).
  2. RN flushes that state update → UIManager::updateStateShadowTree::commitcloneTreecloneShadowNode, invoking the clone constructor (mm:23 / mm:31).
  3. The clone constructor calls dirtyLayoutIfNeeded()YGNodeMarkDirty(&yogaNode_).
  4. YGNodeMarkDirty requires the Yoga node to have a measure function attached. On the freshly-cloned node the measure function is not set up yet at construction time → markDirtyAndPropagate dereferences invalid memory → EXC_BAD_ACCESS.

This reproduces reliably on RN 0.85; it appears 0.85 changed the clone / Yoga-node initialization ordering such that the measure function is no longer guaranteed to be present when the clone constructor runs.

Steps to reproduce

  1. RN 0.85, New Architecture, iOS device
  2. Render StreamdownText / EnrichedMarkdownText and let its content / height update (streaming markdown, or any state-driven height recalculation)
  3. Crash at YGNodeMarkDirty during the state-update commit

Notes

  • Worklets Bundle Mode is correctly set up and working (Downloaded the bundle for Worklet Runtimes logs fine) — this is independent of streamdown's worklet pipeline, purely the native layout path.
  • Not a duplicate of App Crashes - Multiple markdown cards rendered #310 (closed 2026-06-03, a different multi-card crash).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions