Skip to content

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.txt
  • app.cpp
  • v1.cpp
  • v1.h
  • v2.cpp
  • v2.h

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.