Case 15 โ noexcept Changed¶
| Field | Value |
|---|---|
| Verdict | ๐ก COMPATIBLE_WITH_RISK |
| Category | Risk |
| Platforms | Linux |
| Flags | API break |
Detected ChangeKinds |
โ |
| Source files | browse on GitHub |
Category: Risk | Verdict: ๐ก COMPATIBLE_WITH_RISK
ground_truth.json:
expected: COMPATIBLE_WITH_RISK,category: riskchecker_policy.py:FUNC_NOEXCEPT_REMOVEDโCOMPATIBLE_KINDS;SYMBOL_VERSION_REQUIRED_ADDEDโRISK_KINDS
What changes¶
| Version | Signature | Implementation |
|---|---|---|
| v1 | void reset() noexcept; |
no-throw implementation |
| v2 | void reset(); |
throws std::runtime_error |
Why this is COMPATIBLE_WITH_RISK (not BREAKING)¶
The verdict has two independent components:
-
FUNC_NOEXCEPT_REMOVEDโ COMPATIBLE: In the Itanium C++ ABI (GCC/Clang),noexceptdoes not change the mangled symbol name. The symbol is identical in both.sofiles. Existing binaries resolve the same symbol โ no linkage failure occurs. This is a source-level contract concern (C++17 function type system), not a binary ABI break. -
SYMBOL_VERSION_REQUIRED_ADDEDโ COMPATIBLE_WITH_RISK: When v2's implementation usesthrow(linking__cxa_throw/std::runtime_error), the compiled.soacquires a newer GLIBCXX symbol version requirement (e.g.GLIBCXX_3.4.21). This is a deployment risk: the new library won't load on systems with an older libstdc++. But it is not a binary ABI break for the library's own symbols.
Combined verdict: COMPATIBLE_WITH_RISK โ binary-compatible, but deployment risk present from the new GLIBCXX requirement.
What abicheck detects¶
FUNC_NOEXCEPT_REMOVED(COMPATIBLE) โ detected in header mode (-H). The dumper reads thenoexceptattribute from castxml output and stores it asis_noexcepton each function; the checker emitsFUNC_NOEXCEPT_REMOVEDwhen the flag changes between versions. This kind is classified as COMPATIBLE because it does not change the mangled symbol name (Itanium ABI).SYMBOL_VERSION_REQUIRED_ADDED: GLIBCXX_3.4.21(COMPATIBLE_WITH_RISK) โ detected via ELF VERNEED comparison. When v2's implementation usesthrow, the compiled.soacquires a newer GLIBCXX symbol version requirement.
Both kinds are detected. The combined verdict is COMPATIBLE_WITH_RISK because
FUNC_NOEXCEPT_REMOVED โ COMPATIBLE_KINDS and SYMBOL_VERSION_REQUIRED_ADDED
โ RISK_KINDS, and RISK trumps COMPATIBLE in the verdict hierarchy.
Behavioral risk (runtime)¶
While the binary linkage is fine, there is a critical behavioral risk:
Code compiled against v1 may omit exception landing pads, assuming reset() never
throws. If v2's reset() throws at runtime, the exception propagates through the
noexcept frame and std::terminate is called โ no recovery possible.
This is a contract violation, not a linkage failure. The app terminates because
the caller trusted the noexcept guarantee, not because symbols are missing.
Important distinction¶
This case demonstrates the difference between ABI verdict and runtime safety:
| Aspect | Assessment |
|---|---|
| Binary ABI (symbol linkage) | COMPATIBLE โ same mangled name |
| Deployment risk (GLIBCXX) | COMPATIBLE_WITH_RISK โ new version requirement |
| Runtime safety (behavioral) | CRITICAL โ std::terminate if v2 throws |
The tool reports COMPATIBLE_WITH_RISK because it analyzes binary compatibility. The behavioral risk (noexcept contract violation) is a separate concern that requires source-level analysis or runtime testing to detect.
Why abidiff misses it¶
abidiff compares DWARF type information and symbol tables. noexcept is not
stored in DWARF โ it is purely a source-level annotation. abidiff has no way to
detect the change.
Why ABICC catches it¶
ABICC parses C++ headers using GCC internals and sees the noexcept specifier.
When v1 and v2 headers differ in noexcept, ABICC flags it as a source-level break.
Code diff¶
Reproduce steps¶
cd examples/case15_noexcept_change
# Build v1 and v2
g++ -shared -fPIC -std=c++17 -g v1.cpp -o libv1.so
g++ -shared -fPIC -std=c++17 -g v2.cpp -o libv2.so
# abidiff: misses the change (noexcept not in DWARF)
abidw --out-file v1.xml libv1.so
abidw --out-file v2.xml libv2.so
abidiff v1.xml v2.xml || true # exits 0 โ misses it!
# abicheck with headers: detects both noexcept removal and GLIBCXX bump
python3 -m abicheck.cli dump libv1.so -H v1.h -o /tmp/v1.json
python3 -m abicheck.cli dump libv2.so -H v2.h -o /tmp/v2.json
python3 -m abicheck.cli compare /tmp/v1.json /tmp/v2.json
# โ COMPATIBLE_WITH_RISK
# - func_noexcept_removed: Buffer::reset (COMPATIBLE)
# - symbol_version_required_added: GLIBCXX_3.4.21 (RISK)
Real Failure Demo¶
Severity: CRITICAL (behavioral, not linkage)
Scenario: app compiled against v1 (reset() noexcept) calls v2 which throws โ
exception propagates through a noexcept frame โ std::terminate.
Important: This demo combines two changes: (1) removing
noexceptfrom the declaration, and (2) addingthrowto the implementation.The
noexceptremoval itself is COMPATIBLE (same mangled symbol). The deployment risk comes from the GLIBCXX version requirement added by thethrowin v2. The runtime crash is a behavioral contract violation โ the caller omitted landing pads because v1 declarednoexcept.
# Build v1 + app (app includes v1.h which declares reset() noexcept)
g++ -shared -fPIC -std=c++17 -g v1.cpp -o libbuf.so
g++ -std=c++17 -g app.cpp -I. -L. -lbuf -Wl,-rpath,. -o app
./app
# โ Calling reset()...
# โ reset() completed OK
# Swap in v2 (reset() throws)
g++ -shared -fPIC -std=c++17 -g v2.cpp -o libbuf.so
./app
# โ terminate called after throwing an instance of 'std::runtime_error'
# what(): reset failed
# โ Aborted (core dumped)
Why CRITICAL (behavioral): The caller was compiled with the assumption that
reset() is noexcept, so no try/catch or landing pad was generated. When v2
throws, std::terminate is called unconditionally โ no recovery possible.
Note: the binary linkage is fine (COMPATIBLE); the crash is a behavioral contract
violation, not a symbol resolution failure.
Real-world example¶
In Folly (Facebook's C++ library), several internal reset() and destroy()
methods had noexcept removed during a refactor. Downstream projects compiled with
old headers started hitting silent std::terminate crashes when running with the
new .so.
References¶
- C++ noexcept specifier
- P0012R1: noexcept as part of function type
checker_policy.pyโ FUNC_NOEXCEPT_REMOVED
Source files¶
See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.