Skip to content

🐞 Android/Fabric: measure(animatedRef) segfaults if called before node is attached #8292

@scottmas

Description

@scottmas

Description

On Android with Fabric, calling measure(animatedRef) before the underlying native view is attached/laid out crashes the app with a SIGSEGV inside React Native’s UIManager. This occurs even in debug builds. It’s not specific to keyboards; any timing/race that calls measure too early can reproduce it.

Environment

•	React Native: 0.79.5
•	react-native-reanimated: 3.19.1
•	Build type: Debug (Fabric enabled)

Hypothesized Triggering Flow

1.	JS: Declare animatedRef on an Animated.View.
2.	JS: In useLayoutEffect, register a worklet callback (via REA Core registerEventHandler or native module binding) that calls measure(animatedRef).
3.	Native: A native module fires an event immediately after handler registration (e.g., onStart/onProgress from react-native-keyboard-controller / WindowInsetsAnimation), before the view’s node is attached/la id out.
4.	Worklet runs → measure(animatedRef) → RN UIManager::getRelativeLayoutMetrics → SIGSEGV (null deref of ShadowNode/Surface).

Attempted (Failed) Repro

Here's my FAILED attempt at a repro. It's conceptually similar to what is happening in my app. But I must be missing some key ingredient because it does not in fact trigger a crash:

let element: ReactNode = null;
let callback = () => {};

export function PouchesOverview() {
  const storeData = useSyncExternalStore(
    (fn) => {
      callback = fn;
      return () => {};
    },
    () => element,
  );
  return (
    <View>
      <Button
        title="Trigger Crash"
        onPress={() => {
          Keyboard.dismiss(); //Begin emitting move events...
          element = <Crash />;
          callback();
        }}
      />
      <View style={{ flexDirection: "row" }}>
        <TryCrash />
        {storeData}
      </View>
    </View>
  );
}

function TryCrash() {
  useKeyboardHandler({
    onMove: () => {
      "worklet";
    },
  });

  return (
    <View style={{ flex: 1 }}>
      <TextInput style={{ backgroundColor: "pink", borderRadius: 9999 }} />
      <CrashInner />
    </View>
  );
}

function TryCrashInner() {
  const ref1 = useAnimatedRef<View>();
  const ref2 = useAnimatedRef<View>();

  useKeyboardHandler({
    onMove: () => {
      "worklet";
      measure(ref1);
      measure(ref2);
    },
  });

  return (
    <View style={{ flex: 1 }}>
      <Animated.View ref={ref1}>
        <Animated.View
          ref={ref2}
          style={{ flex: 1, height: 50, width: 50, backgroundColor: "red", borderRadius: 999 }}
        />
      </Animated.View>
      {Array(200)
        .fill(0)
        .map((__, i) => (
          <View key={i} style={{ height: 50, width: 50, backgroundColor: "blue" }}>
            <Text>{Math.random()}</Text>
          </View>
        ))}
    </View>
  );
}

Native Logs

I do however have some pretty decent tombstone data:

F/libc    : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0000000000000058
F/DEBUG   : *** *** *** *** *** *** *** *** *** *** *** ***
F/DEBUG   : Build fingerprint: 'google/sdk_gphone64_arm64/emu64a:14/UE1A.230829.036/11036701:user/release-keys'
F/DEBUG   : ABI: 'arm64'
F/DEBUG   : Timestamp: 2025-09-24 11:04:12.054223612-0600
F/DEBUG   : Process uptime: 10s
F/DEBUG   : Cmdline: money.roo.app
F/DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x58
F/DEBUG   : Cause: null pointer dereference
Registers (excerpt):
F/DEBUG   : x0  0000000000000058  ...  lr 0000007584ac7f70  sp 0000007ff6bc9f10  pc 000000758468c6d0

And some top native frames showing the null deref:

#00 pc 0x...  /base.apk!libreactnative.so (...offset...)     <-- crash
#01 pc 0x...  /base.apk!libreactnative.so  facebook::react::ShadowNode::getSurfaceId() const+24
#02 pc 0x...  /base.apk!libreactnative.so  facebook::react::UIManager::getRelativeLayoutMetrics(...)+124
#03 pc 0x...  /base.apk!libreanimated.so   reanimated::ReanimatedModuleProxy::measure(...)+176
#04-#18       /base.apk!libreanimated.so   (worklets::jsi_utils / host function plumbing)
#19-#27       /base.apk!libworklets.so     (RuntimeDecorator / WithRuntimeDecorator / call chain)
#34-#59       /base.apk!libhermes.so       (Hermes frames around host call)

Event/dispatch path indicating the native-side keyboard/insets source and Fabric events:

#70 pc ...  libworklets.so  worklets::EventHandlerRegistry::processEvent(...)
#71 pc ...  libreanimated.so ReanimatedModuleProxy::handleEvent(...)
#72 pc ...  libreanimated.so NativeProxy::handleEvent(...)
#80 pc ...  libreanimated.so reanimated::EventHandler::receiveEvent(...)
#83-#84 ... libreanimated.so JNI MethodWrapper/FunctionWrapper dispatch

#90 pc ...  <anonymous> com.swmansion.reanimated.nativeProxy.EventHandler.receiveEvent+0
#95 pc ...  <anonymous> com.facebook.react.uimanager.events.Event.dispatch+0
#110 pc ... <anonymous> com.facebook.react.uimanager.events.FabricEventDispatcher.dispatchEvent+0

#115 pc ... <anonymous> com.reactnativekeyboardcontroller.extensions.ThemedReactContextKt.dispatchEvent+0
#120 pc ... <anonymous> com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback.onProgress+0
#125 pc ... <anonymous> androidx.core.view.WindowInsetsAnimationCompat$Impl30$ProxyCallback.onProgress+0

These frames show:
• A native event (KeyboardAnimationCallback.onProgress → REA native proxy/event handler) runs a worklet.
• The worklet invokes ReanimatedModuleProxy::measure.
• RN UIManager::getRelativeLayoutMetrics calls into ShadowNode::getSurfaceId() on a node that isn’t attached → SIGSEGV.

Expected behavior

If the referenced node isn’t attached/la id out, measure(animatedRef) should not crash:
• Return null / throw a JS exception / or log a warning—but never segfault.

Proposed fix (high-level)

In Reanimated’s measure implementation for Fabric:
1. Resolve the ref → ShadowNode.
2. Check attachment/SurfaceId validity before calling UIManager::getRelativeLayoutMetrics.
3. If not attached (no valid SurfaceId), short-circuit back to JS with null/undefined (or a controlled error).

Workarounds until fixed

•	Gate measure with an attachment/layout flag set in onLayout (shared value), and ensure native event handlers check that flag before calling measure.
•	Optionally defer first measure by a frame (requestAnimationFrame) and skip while transient animations (e.g., keyboard/insets) are in progress.

Steps to reproduce

See above

Snack or a link to a repository

https://could-not-repro.com

Reanimated version

3.19.1

Worklets version

3.19.1

React Native version

79.1

Platforms

Android

JavaScript runtime

Hermes

Workflow

Expo Dev Client

Architecture

New Architecture (Fabric renderer)

Build type

Debug app & dev bundle

Device

Android emulator

Host machine

macOS

Device model

No response

Acknowledgements

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    Missing reproThis issue need minimum repro scenarioPlatform: AndroidThis issue is specific to Android

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions