Build: Limit shared library exports to the documented public API - #332
Build: Limit shared library exports to the documented public API#332ng-galien wants to merge 1 commit into
Conversation
libpg_query.so currently exports its bundled PostgreSQL internals
(palloc, pfree, lappend, MemoryContextSwitchTo, CurrentMemoryContext,
...) and the protobuf-c machinery in addition to the documented
pg_query_*/deparseRawStmt API.
This breaks at link time when libpg_query is embedded inside a
PostgreSQL extension: the bundled CurrentMemoryContext is declared
__thread (so the parser is reentrant), while the host PostgreSQL
declares it non-TLS, and the linker refuses with
TLS reference in libpg_query.so mismatches non-TLS reference in <ext>.o
Hide every symbol that isn't part of the public API surface, via a GNU
ld version script (pg_query.map) on ELF and a Mach-O exported_symbols
list (pg_query.exp) on macOS. Both whitelist pg_query_* and the two
deparseRawStmt entry points; nm -D / nm -gU confirms the dynamic
symbol table now matches pg_query.h + postgres_deparse.h exactly on
both Linux (debian:bookworm-slim, GNU ld 2.40) and macOS (arm64,
ld-prerelease 1115.7.3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@ng-galien Thanks for opening this PR & disclosing AI use. Overall I think this is reasonable, even though the libpg_query bindings typically don't go through the shared library. But for use cases like the one you describe, its reasonable to limit to avoid exporting any of Postgres' own symbols. One question to help me understand this better: Is there a reason that you don't call the Postgres parser directly in your extension? Or do you need other functionality provided by libpg_query? (would be helpful to know what specifically you're looking to use) |
|
@lfittl |
Summary
libpg_query.so/.dylibexports its bundled PostgreSQL internals(
palloc,pfree,lappend,MemoryContextSwitchTo,CurrentMemoryContext, ...) and the protobuf-c machinery in addition tothe documented
pg_query_*/deparseRawStmtAPI — 2044 dynamicexports vs. 1692 of public surface on a clean 17-6.2.2 build.
Restrict the
.so/.dylibexports to the public API declared inpg_query.handpostgres_deparse.h, via a GNU ld version script(
pg_query.map) on ELF and a Mach-Oexported_symbols_list(
pg_query.exp) on macOS. The static library (libpg_query.a) isunchanged.
Why
Embedding libpg_query inside a PostgreSQL extension fails at link time:
the bundled
CurrentMemoryContext/TopMemoryContext/ErrorContext/
error_context_stackare__thread(so the parser is reentrant),while the host PostgreSQL declares them non-TLS, and the linker rejects:
Hiding the bundled globals lets each
.soresolve its own copy at loadtime. We hit this in a custom PG extension consuming libpg_query
(
ng-galien/esac), where staticlinking wasn't an option because the parser is shared across modules in
the same backend.
PostgreSQL core itself adopted the same approach for extension libraries
(commitfest #3396, "Default to hidden visibility for extension
libraries").
Mechanism
pg_query.map/pg_query.exp— whitelistpg_query_*,deparseRawStmt,deparseRawStmtOpts; everything else hidden.Makefile— picks the right file viaSO_EXPORTS/SO_EXPORTS_FILEbased on
uname -s, wired into the\$(SOLIB)link line.The
pg_query_*wildcard covers the protobuf-c-generated symbols(
pg_query__scan_result__unpack,pg_query__token__descriptor, ...).Test plan
full
makepasses;nm -D/nm -gUon the resulting.so/.dylibshows onlypg_query_*anddeparseRawStmt[Opts].Makefile.msvchas no shared-lib target.(pre-existing).
Note on AI tool use: Claude Code drafted the linker scripts, Makefile
wiring and commit message; I steered it back to this repo's conventions
on review.