fix(native): stop exporting the symbols of statically linked dependencies - #39
fix(native): stop exporting the symbols of statically linked dependencies#39prakunin wants to merge 1 commit into
Conversation
libepub4j_native statically links pugixml, gumbo, uchardet, zlib,
libarchive, libjpeg-turbo, libpng and libwebp, and exports all of their
symbols globally: 2023 dynamic symbols, of which only 43 are the
epub_native_* public API and 270 are libarchive's archive_* entry points.
That surface is reachable by unrelated code in the same process. A JVM
that resolves libarchive through SymbolLookup.loaderLookup() searches
every native library loaded by the classloader rather than a named one,
so archive_* calls meant for the system libarchive can bind into the copy
vendored here -- which is configured with ENABLE_LZMA/BZip2/ZSTD/LZ4 = OFF
and so refuses archives the system library reads fine. Which of the two
wins is decided by hash iteration order over library paths, and the native
library is extracted to a randomly named temp directory, so it is settled
once per JVM start.
Restrict the exported surface to the public C API:
* C_/CXX_VISIBILITY_PRESET hidden for this target's own translation
units (the public entry points are already tagged EPUB_NATIVE_API,
which is visibility("default") on non-Windows);
* a linker export filter, which is what covers the symbols coming out
of the static archives -- a version script on ELF platforms, an
exported-symbols list on Apple.
The version tag is anonymous, so the dynamic symbol table is filtered
without attaching a symbol version and the ABI seen by dlsym()/FFM is
unchanged. Windows is untouched; it already relies on __declspec(dllexport)
to suppress auto-export.
Afterwards the library exports 43 symbols, all epub_native_*, matching the
43 declared EPUB_NATIVE_API in epub_native.h and the 43 looked up by
EpubNativeHeaders.java; archive_* exports go from 270 to 0.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
📜 Recent review details🔇 Additional comments (4)
📝 WalkthroughWalkthroughThe native library build now hides default C and C++ symbols. macOS and Linux use linker filters to export only the intended native API symbols. ChangesNative symbol visibility
Estimated code review effort: 3 (Moderate) | ~15–30 minutes Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
PR Summary by QodoHide vendored symbols in libepub4j_native; export only epub_native_*
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTo customize comments, go to the Qodo configuration screen, or learn more in the docs. |
Fork-only workflow. Builds all seven native classifiers from a tagged ref, asserts that each produced binary exports exactly the 43 epub_native_* entry points and zero archive_* symbols, then publishes epub4j-core and epub4j-native into the gh-pages branch as a plain static Maven repository at https://prakunin.github.io/epub4j/maven/ . Not intended for upstream: the PR at grimmory-tools#39 carries only the 62-line cpp/ source change.



What
libepub4j_nativestatically links pugixml, gumbo, uchardet, zlib, libarchive, libjpeg-turbo, libpng and libwebp, and currently exports all of their symbols as global dynamic symbols. Onlinux-x86_64that is 2023 exported symbols, of which 43 are theepub_native_*public API and 270 are libarchive'sarchive_*entry points.This PR restricts the exported surface to the public C API. It changes no source, no header, no Java and no Gradle — only the link step.
Why it matters to consumers
Anything else in the same process can bind to epub4j's private copy of a library instead of its own. The concrete case: a JVM that resolves libarchive through
SymbolLookup.loaderLookup()searches every native library loaded by the classloader rather than a named one, soarchive_*calls meant for the system libarchive can resolve into the copy vendored here — whichcpp/CMakeLists.txtconfigures withENABLE_LZMA/BZip2/ZSTD/LZ4 = OFFand which therefore refuses archives the system library reads without trouble (LZMA codec is unsupported,BZ2 …,ZSTD …).Which of the two libraries wins is decided by hash iteration order over library paths in
jdk.internal.loader.NativeLibraries(first hit wins over aConcurrentHashMap), andPanamaConstantsextracts the.soto a randomly named temp directory on every start. So it is settled once per JVM launch and then holds for the life of the process — a process either reads every archive correctly or none of them. Load order makes no difference; I tested both.It is also quietly hard to diagnose: the library exports
archive_version_stringandarchive_version_numberbut notarchive_version_details, so the usual "which libarchive am I talking to?" check answers correctly even in a process where every real call is going to the wrong one.archive_*is the one that bit me, but it is not the only exposure — roughly 391png*, 121jpeg*, plusgumbo*,tj*,inflate,adler*,crc*anduchardet*are exported too.Reproduction
Two
System.loadcalls, no framework:Twelve runs, each with a freshly
mktemp -d-named copy of the.so, printing which mapping in/proc/self/mapsownsarchive_read_next_header:LZMA codec is unsupported)Control with epub4j not loaded at all: OK every time.
The change
C_VISIBILITY_PRESET/CXX_VISIBILITY_PRESET hidden+VISIBILITY_INLINES_HIDDENon theepub4j_nativetarget. This is safe becauseepub_native.halready tags every public entry pointEPUB_NATIVE_API==__attribute__((visibility("default")))on non-Windows. Visibility alone is not sufficient — it only covers this target's own translation units; the symbols coming out of the static archives need the linker.cpp/epub4j_native.map(-Wl,--version-script, plus-Wl,--exclude-libs,ALL) on ELF, andcpp/epub4j_native.symbols(-Wl,-exported_symbols_list) on Apple.The version tag in the map file is anonymous, so the dynamic symbol table is filtered without attaching a symbol version — the ABI seen by
dlsym()/ Panama FFM is byte-for-byte the contract it was before. A named tag would have versioned the exports and changed that.Windows is left alone: it already relies on
__declspec(dllexport), which suppresses MinGW auto-export.Evidence
The remaining 43 are all
epub_native_*, and they are the same 43 that 1.4.0 exported: I diffed the list against theEPUB_NATIVE_APIdeclarations incpp/include/epub_native.hand against the 43 string constantsEpubNativeHeaders.javalooks up. They match one for one, so nothing the Java side binds to was lost../gradlew :epub4j-native:test— 39 tests, 0 failures, 0 skipped:skipped=0is the meaningful part — the natives loaded, so that is not a silent no-op run. Between them those cover gumbo, pugixml, and libjpeg-turbo/libpng/libwebp through the hidden-visibility build.There is no
NativeArchiveTestin the repo, so I exercisedepub_native_archive_*by hand againstepub4j-core/src/test/resources/testbook1.epub: open, list (12 entries), read a stored entry (mimetype) and read a deflated entry (OEBPS/content.opf, 2120 bytes, through the now-hidden static zlib) all work. Intra-.socalls do not go through the global lookup, as expected — but that seemed worth demonstrating rather than asserting.On the provenance of these numbers: the probe runs and the test suite above come from the session in which I developed and verified this change, and I did not re-run them while preparing this PR. What I did re-verify against the built artifact here is the symbol counts —
0archive_*exports, 43 total, allepub_native_*. Please treat the rest as a report of an earlier run rather than something reproduced today, and CI is of course the authority.Scope and caveats
linux-x86_64only. The Apple-exported_symbols_listbranch is written but has never been compiled, and the Windows and musl branches are untouched but likewise unexercised by me.native-classifiers.ymlbuilds all seven — running it on this branch would settle it, and the same workflow could assertnm -D --defined-only … | grep -c ' archive_'equals 0 as a regression gate (macOS and musl need a different symbol-tool invocation).-Wl,--exclude-libs,ALLis GNU-ld/gold/lld-specific. It is belt-and-braces; the version script alone is sufficient, so it is one line to drop if you would rather not carry it.archive_*/png_*/gumbo_*exports loses them. That may argue for a minor bump rather than a patch, and for a CHANGELOG line — but your release flow drives versions from axion tags, so I have left the version choice to you and tagged nothing here.1.5.0on Maven Central has the identical symbol counts (2023 / 270), so this is not something that has already been fixed.Happy to adjust any of it, or to split the macOS branch out if you would rather land Linux first.
Summary by CodeRabbit