Skip to content

Case 53: Namespace Pollution (Generic Symbol Names)

Field Value
Verdict ๐Ÿ”ด BREAKING
Category Breaking
Platforms Linux
Flags ABI break, API break, Bad practice
Detected ChangeKinds func_removed, func_added
Source files examples/case53_namespace_pollution/

Category: API Design | Verdict: ๐Ÿ”ด BREAKING (bad practice)

Verdict and consumer impact

v1 exports functions with extremely generic names: init, process, cleanup, status. v2 renames them to mylib_init, mylib_process, mylib_cleanup, mylib_status with a proper library prefix. The rename itself is a hard ABI break โ€” every consumer that resolved the old, unprefixed names fails to load against v2 โ€” but the underlying problem is that v1's names were a collision time-bomb in the first place: C has no namespaces, and any other library sharing the process that also exports init/process/cleanup/status would silently interpose or be interposed on.

Old/new diff

bad.c (v1) good.c (v2)
int init(void) int mylib_init(void)
int process(int data) int mylib_process(int data)
void cleanup(void) void mylib_cleanup(void)
int status(void) int mylib_status(void)

abicheck command

gcc -shared -fPIC -g bad.c  -o libfoo_v1.so
gcc -shared -fPIC -g good.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: BREAKING (exit 4)

- func_removed: Public function removed: status
- func_removed: Public function removed: cleanup
- func_removed: Public function removed: process
- func_removed: Public function removed: init
  > Old binaries call a symbol that no longer exists; dynamic linker
    will refuse to load or crash at call site.
- symbol_renamed_batch: Batch symbol rename detected (namespace
  refactoring): prefix 'mylib_' added to 4 symbols (cleanup ->
  mylib_cleanup, init -> mylib_init, process -> mylib_process, status ->
  mylib_status)
  > Multiple symbols renamed (e.g. namespace prefix added/removed); old
    binaries reference the old names and will get undefined symbol errors
    at load time.

Additions:
- func_added: New public function: mylib_status, mylib_cleanup,
  mylib_process, mylib_init

Minimum evidence

min_evidence: L0 โ€” the exported-symbol table alone shows four names disappearing and four new ones appearing; abicheck's symbol_renamed_batch heuristic further correlates the two sets by common prefix, all from the .dynsym table with no debug info or headers needed.

Why abicheck catches it

The dynamic symbol table is authoritative L0 evidence โ€” abicheck diffs the exported-symbol sets directly. Beyond the plain func_removed/func_added pairs, abicheck's rename-detection heuristic notices that all four removed names reappear with a shared mylib_ prefix and groups them into a single symbol_renamed_batch finding, flagging it as a namespace-prefixing refactor rather than four unrelated symbol changes.

Runtime failure demonstration

Severity: BREAKING

Scenario: compile app against v1's unprefixed API, swap in v2 .so without recompile.

# Build old library + app
gcc -shared -fPIC -g bad.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ status() = 1
# โ†’ process(21) = 43
# โ†’ status() = 0

# Swap in new library (no recompile)
gcc -shared -fPIC -g good.c -o libfoo.so
./app
# โ†’ ./app: symbol lookup error: ./app: undefined symbol: process

Why BREAKING: all four of v1's exported names are gone from v2's dynamic symbol table; the runtime linker cannot resolve them and the process fails before main() runs.

Safe redesign

Use a consistent prefix for all exported symbols from the start:

/* Good: all symbols prefixed with library name */
int mylib_init(void);
int mylib_process(int data);
void mylib_cleanup(void);

Or use a version script to hide any unprefixed internal names:

MYLIB_1.0 {
    global: mylib_*;
    local: *;
};

Real-world example: zlib uses a z_/inflate/deflate naming convention, OpenSSL uses SSL_/EVP_/BN_ prefixes, SQLite prefixes everything with sqlite3_, and libpng uses png_ โ€” all specifically to avoid the flat-namespace collision this case demonstrates. Libraries that historically did not prefix (early POSIX open, read, write) cause endless compatibility headaches.

Cross-tool comparison

nm -D libfoo_v1.so | grep ' T '
nm -D libfoo_v2.so | grep ' T '

References


Source files

  • CMakeLists.txt
  • app.c
  • bad.c
  • good.c

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.