software-mansion / software-mansion/react-native-executorch

libexecutorch.so re-exports its own libc++, duplicating 91% of libc++_shared's ABI

Open
#1,467 0 comments 0 reactions 1 assignee View on GitHub

@msluszniak is already working on this.

Since Sep 11, 2026.

  • #1468 by @msluszniak — open
Dominant language
TypeScript
Stars
1.7k
Forks
96
Avg merge
20h 51m
Merged PRs (30d)
59

Description

Summary

libexecutorch.so statically links the NDK libc++ and re-exports it. It exports 12 115 symbols; 2 130 of them are also exported by libc++_shared.so — 91% of that library's entire surface.

duplicated count
__cxa_* 29
typeinfo / typestring (_ZTI, _ZTS) 448
_ZNSt (std::) 1 007
total 2 130

Both use the __ndk1 inline namespace, so they are the same NDK libc++ flavour, and every duplicate is GLOBAL with DEFAULT visibility.

libRnExecutorch.so imports __cxa_throw and __cxa_begin_catch, and its DT_NEEDED lists libexecutorch_jni.so (position 3) before libc++_shared.so (position 11) — both at depth 1 — so RNE's C++ runtime is in practice the copy inside libexecutorch.so. (libexecutorch.so's SONAME is libexecutorch_jni.so, which is how that entry resolves to a file of a different name.)

This is not currently broken

Because every duplicate is GLOBAL/DEFAULT, ELF symbol interposition applies: the first definition in the lookup scope wins and every library binds to that same copy. One effective __cxa_throw, one effective _ZTISt9exception per process, so typeinfo pointer comparison stays consistent and catch blocks match. That is why RNE ships and works.

It is also not a size problem: the 2 130 duplicated symbols occupy 277 058 B (0.26 MB) of libexecutorch.so's 12.82 MB. Most of libc++'s weight is header-inlined templates, not these exported entry points.

What would break it

  • NDK version skew. If the libc++ statically linked into libexecutorch.so and the libc++_shared.so an app ships ever come from different NDKs, interposition silently mixes two layouts of the same types. They match today; nothing enforces that.
  • Anything that defeats interposition — a second linker namespace, or a library given a private lookup scope — puts two __cxa_eh_globals and two typeinfo sets in one process. An exception thrown against one runtime and caught against the other does not match, and terminates.

Fix

Build ExecuTorch's Android target against c++_shared rather than static libc++, so libexecutorch.so stops carrying and exporting its own copy. Needs a rebuild of the fork and a republish of core-android-*, so it cannot ride on the v0.10.1-libs repackage (which deliberately recompiled nothing).

Reproduction

NDK=$ANDROID_HOME/ndk/<ver>/toolchains/llvm/prebuilt/darwin-x86_64/bin
$NDK/llvm-nm -D --defined-only libexecutorch.so  | awk '{print $3}' | sort -u > et.syms
$NDK/llvm-nm -D --defined-only libc++_shared.so  | awk '{print $3}' | sort -u > cxx.syms
comm -12 et.syms cxx.syms | wc -l          # 2130
$NDK/llvm-readelf --dyn-syms libexecutorch.so | grep -E ' __cxa_throw$| _ZTISt9exception$'
$NDK/llvm-readelf -d libRnExecutorch.so | grep NEEDED

Found while assessing §11 of #1464.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.