Case 86: Tag struct renamed (empty class re-mangling)¶
| Field | Value |
|---|---|
| Verdict | π΄ BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
tag_type_renamed |
| Source files | examples/case86_tag_struct_renamed/ |
Category: Mangling ABI | Verdict: BREAKING
What breaks¶
Empty tag structs are the workhorses of C++ template specialization:
The library ships explicit instantiations that bake the tag's mangled name into symbol names:
v2 renames brute_force β search_brute. Mechanically:
- The struct has zero fields and zero methods. Layout-based detectors
(
type_size_changed,type_field_*) see nothing. - The old type appears as
TYPE_REMOVED, the new one asTYPE_ADDEDβ an existing detector can produce both findings but does not link them. - Every explicit instantiation referencing the old tag gets a brand-new mangled name. Consumer binaries linked against v1 request the old symbol at load time and get an unresolved-symbol error.
Real Failure Demo¶
Severity: BREAKING / LOAD-TIME FAILURE
cmake -S examples -B /tmp/abicheck-examples-build -DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/abicheck-examples-build --target case86_tag_struct_renamed_app case86_tag_struct_renamed_v2
tmp=$(mktemp -d)
cp /tmp/abicheck-examples-build/case86_tag_struct_renamed/app_v1 "$tmp/"
cp /tmp/abicheck-examples-build/case86_tag_struct_renamed/libv2.so "$tmp/libv1.so"
(cd "$tmp" && LD_LIBRARY_PATH=. ./app_v1)
# ./app_v1: symbol lookup error: undefined symbol: descriptor<brute_force, classification>::descriptor()
Why this is its own ChangeKind¶
Renaming a non-empty class is rare and would already trigger many
layout-related findings. Renaming a tag struct β the only purpose of
which is to participate in mangling β is a distinct risk of the
empty-tag-struct idiom used by many C++ libraries (such as oneDAL, Boost,
and the standard library), and no existing ChangeKind correlates the disappearance with the
appearance. A new TAG_TYPE_RENAMED makes the correlation explicit.
How abicheck detects it¶
The new detector (abicheck/diff_cpp_patterns.py::detect_tag_type_renamed) runs after the
type-diff pass:
- Find every
TYPE_REMOVEDfor a record type with zero fields and no virtual methods (an "empty" type). - Find every
TYPE_ADDEDfor an empty record type in the same parent namespace. - Look for a symbol-rename pair in the symbol diff where the old mangled substring contains the removed tag's name segment and the new mangled substring contains the added tag's name segment.
- When both halves match, emit a single
TAG_TYPE_RENAMEDfinding pairing the two and suppress the redundantTYPE_REMOVED/TYPE_ADDED.
Real-world reference¶
oneDAL's cpp/oneapi/dal/algo/*/common.hpp defines:
namespace method { struct by_default {}; struct brute_force {}; struct kd_tree {}; }
namespace task { struct by_default {}; struct classification {}; struct regression {}; }
β¦and every descriptor<Float, Method, Task> instantiation embeds these
tags in its mangled name. Renaming any of them silently breaks every
shipped instantiation symbol.
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview Β· All BREAKING cases Β· Category: Breaking.