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.txtapp.cppv1.cppv2.cpp
See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.