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 browse on GitHub

Category: API Design / Policy | Verdict: BREAKING (bad practice)

What this case is about

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.

This is a design-level bad practice: unprefixed names in C shared libraries are a collision time-bomb in any non-trivial application that links multiple libraries.

Why namespace pollution is bad practice

C has no namespaces. All exported symbols from all shared libraries loaded in a process share a single flat namespace. Generic names cause:

  • Silent symbol interposition: If two libraries export init(), the dynamic linker picks one (typically the first loaded). The other library silently calls the wrong function with no warning.
  • Hard-to-debug crashes: Symbol collisions produce mysterious behavior โ€” wrong return values, corrupted state, segfaults in unrelated code.
  • Prevents library composition: Applications cannot safely link two libraries with overlapping symbol names without RTLD_LOCAL hacks.
  • LD_PRELOAD conflicts: Tools like sanitizers, profilers, and debug libraries commonly use names like init and cleanup.

What abicheck detects

  • FUNC_REMOVED: init, process, cleanup, status removed
  • FUNC_ADDED: mylib_init, mylib_process, mylib_cleanup, mylib_status added
  • NAMESPACE_POLLUTION: v1 exports symbols without a consistent prefix

Verdict: BREAKING (bad practice) โ€” symbols renamed; v1 was the bad practice.

How to reproduce

# Build both versions
gcc -shared -fPIC -g bad.c  -o libbad.so
gcc -shared -fPIC -g good.c -o libgood.so

# Check exports
nm -D libbad.so  | grep ' T '
# โ†’ T cleanup, T init, T process, T status  โ† generic names!
nm -D libgood.so | grep ' T '
# โ†’ T mylib_cleanup, T mylib_init, T mylib_process, T mylib_status  โ† prefixed

# Demonstrate the collision
cat > other_lib.c <<'EOF'
int init(void) { return 99; }  /* another library's init */
EOF
gcc -shared -fPIC other_lib.c -o libother.so

# Link app against both โ€” silent collision
gcc -g app.c -L. -lbad -lother -Wl,-rpath,. -o app
LD_DEBUG=symbols ./app 2>&1 | grep 'init'
# โ†’ init() resolves to libother.so's version โ€” wrong library!

# Run abicheck
python3 -m abicheck.cli dump libbad.so  -o /tmp/v1.json
python3 -m abicheck.cli dump libgood.so -o /tmp/v2.json
python3 -m abicheck.cli compare /tmp/v1.json /tmp/v2.json
# โ†’ BREAKING (renamed symbols) + NAMESPACE_POLLUTION warning

How to fix

Use a consistent prefix for all exported symbols:

/* 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 unprefixed names:

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

Real-world examples

  • zlib uses z_ prefix: z_inflate, z_deflate
  • OpenSSL uses SSL_, EVP_, BN_ prefixes
  • SQLite prefixes everything with sqlite3_
  • libpng uses png_ prefix

Libraries that historically did NOT prefix (like early POSIX open, read, write) cause endless compatibility headaches.

References


Source files

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