Skip to content

Case 132: Thread-Safe Statics Mode Flip

Field Value
Verdict ๐ŸŸก COMPATIBLE_WITH_RISK
Category Risk
Platforms Linux
Flags โ€”
Detected ChangeKinds threadsafe_statics_mode_changed
Source files examples/case132_threadsafe_statics_flip/

Category: Build mode | Verdict: ๐ŸŸก COMPATIBLE_WITH_RISK

Verdict and consumer impact

The public symbol and its source are byte-for-byte identical between v1 and v2 โ€” only the compile mode changed. v1 is built with -fthreadsafe-statics (the default), which wraps a function-local static's first-use initialization in a __cxa_guard acquire/release pair so concurrent callers race safely. v2 is built with -fno-threadsafe-statics, which omits that guard. A public inline holding a function-local static, compiled into different translation units under different modes, ends up with mismatched guard expectations โ€” a data race or double-init on first concurrent use. Nothing crashes on load; the risk only surfaces under contention, which is why this is a risk finding rather than a hard break.

Old/new diff

v1.cpp v2.cpp
int compute(int x) { return x + 1; } (compiled -fthreadsafe-statics) int compute(int x) { return x + 1; } (compiled -fno-threadsafe-statics)

abicheck command

g++ -shared -fPIC -g -std=gnu++17 -fthreadsafe-statics v1.cpp -o libv1.so
g++ -shared -fPIC -g -std=gnu++17 -fno-threadsafe-statics v2.cpp -o libv2.so

# abicheck needs the compile flags, not just the binaries, to see this
# change โ€” a compile_commands.json per side supplies the L3 build context.
cat > v1_compile_commands.json <<EOF
[{"directory": "$PWD", "command": "g++ -std=gnu++17 -fPIC -g -fthreadsafe-statics -c v1.cpp -o v1.o", "file": "$PWD/v1.cpp"}]
EOF
cat > v2_compile_commands.json <<EOF
[{"directory": "$PWD", "command": "g++ -std=gnu++17 -fPIC -g -fno-threadsafe-statics -c v2.cpp -o v2.o", "file": "$PWD/v2.cpp"}]
EOF

abicheck dump libv1.so --build-info v1_compile_commands.json -o v1.abi.json
abicheck dump libv2.so --build-info v2_compile_commands.json -o v2.abi.json
abicheck compare v1.abi.json v2.abi.json

Expected abicheck finding

Verdict: COMPATIBLE_WITH_RISK (exit 0)

- threadsafe_statics_mode_changed: Runtime-model option 'threadsafe_statics:CXX'
  changed: 'on' -> 'off'.
  > May not be link- or runtime-compatible across consumers; the artifact
    diff confirms any concrete break.

Minimum evidence

min_evidence: L3 โ€” the symbols, DWARF, and binary layout are all identical between v1 and v2; there is nothing to see at L0/L1/L2. Only the build system's compile flags (captured in a compile_commands.json / build context) reveal that the two sides used different thread-safe-statics modes.

Why abicheck catches it

abicheck's L3 build-context diff reads each side's compiler invocation from the supplied build info, normalizes -fthreadsafe-statics / -fno-threadsafe-statics to the canonical threadsafe_statics runtime-model option, and reports the flip when the two sides disagree.

Runtime failure demonstration

Severity: RISK (not a proven break)

This is a build-mode signal, not an artifact-proven binary break (ADR-028 D3) โ€” compute() runs identically either way in this minimal case, since it has no function-local static to race on. The risk only materializes for a public inline that does hold one and gets compiled under mixed modes across translation units, which this trivial case doesn't reproduce as an observable crash. No swap-in-place demo is included for that reason.

Safe redesign

Keep thread-safe statics enabled for any public inline holding a function-local static, or document that consumers must never rely on cross-TU first-use ordering. Don't let -fno-threadsafe-statics vary between translation units that share such an inline.

Cross-tool comparison

abidiff/ABICC compare pre-built binaries or headers; neither reads compiler flags from a build system, so this build-mode-only change is invisible to both โ€” there's no symbol, type, or layout delta for them to diff. Detecting it is specific to abicheck's L3 build-context evidence layer.


Source files

  • CMakeLists.txt
  • app.cpp
  • v1.cpp
  • v2.cpp

See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.