Skip to content

Android: dlopen of libNitroImage.so fails on RN 0.84 debugOptimized variant (missing DebugStringConvertible vtable) #154

Description

@alessioemireni

What happened?

react-native-nitro-image@0.15.1 crashes at app startup on Android when the app is launched on the React Native debugOptimized build variant (debug app code linked against the release React Native prefab AAR — RN 0.84's "fast dev" variant).

libNitroImage.so fails to dlopen because the vtable for facebook::react::DebugStringConvertible is not exported from the release-flavor libreactnative.so. NitroImage's generated Fabric props class (HybridNitroImageViewProps) inherits from react::ViewPropsreact::Propsreact::DebugStringConvertible, so the vtable for that base class must be available at link time AND at runtime.

NitroImageOnLoad        E  Failed to load NitroImage C++ library!
java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol
"_ZTVN8facebook5react22DebugStringConvertibleE"
referenced by ".../lib/arm64/libNitroImage.so"...
    at com.margelo.nitro.image.NitroImageOnLoad$Companion.initializeNative(NitroImageOnLoad.kt:25)
    at com.margelo.nitro.image.NitroImagePackage.<clinit>(NitroImagePackage.java:44)
    at com.facebook.react.PackageList.getPackages(PackageList.java:56)
    ...

Diagnosis

Comparing the two flavors of libreactnative.so shipped by RN 0.84.1 (via nm -D):

Symbol Debug prefab (used at link time by NitroImage) Release prefab (used at runtime in *Optimized)
_ZTIN8facebook5react22DebugStringConvertibleE (typeinfo) V (weak) V (weak)
_ZTSN8facebook5react22DebugStringConvertibleE (typeinfo name) V (weak) V (weak)
_ZTVN8facebook5react22DebugStringConvertibleE (vtable) D (data, present) stripped

NitroImage builds against the debug prefab (under node_modules/react-native-nitro-image/android/build/intermediates/cmake/debug/obj/arm64-v8a/libreactnative.so, vtable present), so linking succeeds. The APK however bundles the release prefab (vtable removed by --gc-sections / hidden visibility), so dlopen fails at startup.

react-native-nitro-modules alone does NOT trigger this — the issue only appears once react-native-nitro-image is added, because only NitroImage's nitrogen-generated Fabric code subclasses RN-internal classes that need the DebugStringConvertible parent vtable. Plain HybridObject doesn't.

Versions

  • react-native@0.84.1
  • react-native-nitro-modules@0.35.9
  • react-native-nitro-image@0.15.1
  • react-native-vision-camera@5.0.11 (transitive; pulls in nitro-image)
  • New Architecture: enabled (default in RN 0.84)
  • AGP 8.12.0, NDK from RN's ndkVersion
  • Reproduces on the standard debugOptimized variant generated by the RN Gradle plugin

Workarounds

  1. Run the plain debug variant (not debugOptimized) — the debug prefab still exports the vtable. Slower dev startup but works. Confirmed.
  2. Release builds are unaffected (everything is the release prefab consistently).
  3. Forcing *Optimized variants to fall back to the debug RN prefab via matchingFallbacks also unblocks, but it defeats the purpose of debugOptimized.

Possible upstream fixes

  • Make NitroImage's nitrogen-generated Fabric subclasses self-sufficient w.r.t. the DebugStringConvertible vtable (e.g. provide an out-of-line virtual destructor / explicitly override the virtual chain so the parent vtable is no longer required at dlopen).
  • Or guard the HybridNitroImageViewComponent/HybridNitroImageViewProps generation behind a flag, so consumers that don't actually use <NitroImage /> (e.g. only use Photo.toImage() / non-component APIs from react-native-vision-camera) can opt out.
  • Or document the *Optimized incompatibility explicitly.

Happy to test patches.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions