Case 81: Serialization Tag ID Reassigned¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break |
Detected ChangeKinds |
serialization_tag_changed |
| Source files | examples/case81_serialization_tag_reassigned/ |
Category: Payload ABI | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
DAAL-style SerializationIface assigns each serializable class a tag ID
persisted inside every saved file; on load, a registry maps tag_id ->
factory. v2 swaps the values of knn_model and linear_regression (e.g.
"putting them in alphabetical order"). A .dat file written by v1 embeds
the bytes for knn_model = 0x1002; loaded against v2, the registry looks
up 0x1002 and returns the linear_regression factory instead.
Deserialization proceeds with no error at all โ the model silently
becomes the wrong class. Symbols, types, and layout are all unchanged, so
every conventional (symbol/layout-only) ABI checker reports COMPATIBLE.
Old/new diff¶
| v1.h | v2.h |
|---|---|
knn_model = 0x1002, linear_regression = 0x1003 |
knn_model = 0x1003, linear_regression = 0x1002 |
abicheck command¶
g++ -std=c++17 -shared -fPIC -g v1.cpp -o libfoo_v1.so
g++ -std=c++17 -shared -fPIC -g v2.cpp -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so -H old=v1.h -H new=v2.h --ast-frontend clang
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- enum_member_value_changed: Enum member value changed:
SerializationTag::knn_model (4098 -> 4099)
- enum_member_value_changed: Enum member value changed:
SerializationTag::linear_regression (4099 -> 4098)
- serialization_tag_changed: Serialization tag 'SerializationTag::knn_model'
value changed 4098 -> 4099; this is the same value previously assigned to
'SerializationTag::linear_regression'. Saved data referencing the old
value now deserialises as the wrong class.
- serialization_tag_changed: Serialization tag
'SerializationTag::linear_regression' value changed 4099 -> 4098; this is
the same value previously assigned to 'SerializationTag::knn_model'.
Minimum evidence¶
min_evidence: L2 โ the header AST is what lets abicheck recognize
SerializationTag's members as tag-shaped constants (configurable naming
convention: *_serialization_tag, *_tag, kSerializationTag,
SERIALIZATION_TAG, DAAL's *SerializationTag pattern) and cross-reference
their numeric values between versions. castxml is the documented default
backend for this evidence layer; clang (--ast-frontend clang) is a
supported alternative AST frontend used above.
Why abicheck catches it¶
The dedicated serialization-tag detector inspects exported enum
members/constants matching the tag-naming convention and checks whether
two such constants swap values between snapshots. Here knn_model
(0x1002 โ 0x1003) and linear_regression (0x1003 โ 0x1002) trade
places exactly โ the detector reports a serialization_tag_changed
finding for each, explicitly naming which sibling now holds the old value.
This is a payload-level invariant: no type, symbol, or layout changed, so
without this dedicated detector the change would be invisible to every
symbol/layout-based ABI checker.
Runtime failure demonstration¶
Severity: BREAKING โ silent persistence-format corruption
Scenario: compile app against v1, swap in v2 .so without recompile โ
the app just reads back the tag values a real serializer would embed in a
saved file.
# Build old library + app
g++ -std=c++17 -shared -fPIC -g v1.cpp -o libfoo.so
g++ -std=c++17 -g app.cpp -L. -lfoo -Wl,-rpath,. -o app
./app
# โ knn_model_tag = 0x1002
# โ linear_regression = 0x1003
# Swap in new library (no recompile)
g++ -std=c++17 -shared -fPIC -g v2.cpp -o libfoo.so
./app
# โ knn_model_tag = 0x1003
# โ linear_regression = 0x1002
Why this is BREAKING despite no crash: there is no symbol change, no
layout change, no link error, no load error โ the process runs to
completion cleanly on both sides. But a .dat file written while linked
against v1 (tagged 0x1002 for knn_model) silently deserializes as
linear_regression once the same bytes are read back against v2. This is
data corruption with zero observable failure signal at the point of
damage.
Safe redesign¶
Treat serialization tag IDs as a permanent, append-only registry: once a tag value is assigned to a class, never reassign it to a different class, even during "cleanup" refactors. Retire a class by reserving its old tag value rather than reusing it.
Real-world example: daal::services::SerializationIface::getSerializationTag()
returns an int uniquely identifying a class for persistence, implemented
across cpp/daal/include/algorithms/*/_model.h via DAAL_SERIALIZATION_TAG
macros. The tag IDs are part of the persisted file format โ changing them
is on par with changing a wire-protocol field.
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.