Skip to content

Case 146: RTTI Exported for an Internal Type (Single-Release Audit)

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Quality (Compatible)
Platforms Linux
Flags Bad practice
Detected ChangeKinds rtti_for_internal_type
Source files examples/case146_audit_rtti_for_internal/

Category: Quality (Audit) | Verdict: ๐ŸŸข COMPATIBLE (bad practice)

Verdict and consumer impact

Single-release audit: one build's evidence checked against itself, no baseline. abicheck's verdict is COMPATIBLE, but the audit flags an advisory finding: _ZTI12InternalNode/_ZTV12InternalNode (the C++ typeinfo and vtable for InternalNode) are both exported from the dynamic symbol table, but InternalNode is declared only in a private header โ€” render() is the sole function the public headers actually declare. Consumers can't name InternalNode in their own code, so they can't be handed one directly, but the exported RTTI still lets any code that obtains an InternalNode* polymorphically (e.g. via a base-class pointer returned from elsewhere in the library) run dynamic_cast/typeid against it. That couples consumer binaries to an internal class's identity without the maintainer ever intending InternalNode to be part of the API.

What this snapshot contains

snapshot.abi.json is a single, hand-built AbiSnapshot for one build of libdemo.so, with the export table and the header provenance of the type its RTTI belongs to:

Source in the snapshot What it records
Binary export table (L0, elf.symbols) _Z6renderv (render), _ZTI12InternalNode, _ZTV12InternalNode โ€” three exported symbols
Public-header AST (L2, functions/types) only render() is declared in a public header; the InternalNode struct entry carries origin: private_header

abicheck command

abicheck scan snapshot.abi.json

Expected abicheck finding

Verdict: COMPATIBLE (exit 0)

crosscheck:rtti_for_internal_type present   typeinfo exports โ†” private-header
  provenance: 2 RTTI symbol(s) for one of 1 private type(s)

ABI-hygiene catalog (intra-version, advisory)
  [warning] rtti_for_internal_type: 2

Minimum evidence

min_evidence: L2 โ€” the export table alone (L0) sees _ZTI/_ZTV symbols but has no way to know which class they belong to is public vs. private; the public-header AST (L2) is what supplies the class's provenance, which the check demangles the RTTI symbol name against.

Why abicheck catches it

rtti_for_internal_type is a cross-source check: abicheck demangles every exported _ZTI*/_ZTV* symbol back to its class name, then looks up that class's provenance in the public-header AST (binary_exports and public_header_ast, per provider_assertions). RTTI mapped to a class whose provenance is private_header is exactly this finding โ€” a plain export-table read has no notion of which class is public, and a plain header parse never sees the compiled RTTI symbols at all.

Why this matters for a real release

InternalNode's layout, base classes, and existence are all considered free to change since nothing declares it publicly โ€” but as long as its RTTI is exported, any consumer binary that gets a polymorphic pointer into the library's object graph can dynamic_cast against InternalNode and observe identity/layout changes as a crash or silent misbehavior, not a compile error. Because RTTI symbols are emitted per translation unit that uses the class polymorphically, this is easy to introduce by accident (any TU that dynamic_casts or throws the type emits it) and easy to miss in review.

Safe redesign

Give InternalNode a key function defined in exactly one, internal translation unit so its vtable/RTTI aren't emitted (and, ideally, __attribute__((visibility("hidden"))) on the class) โ€” or, if InternalNode is genuinely meant to be part of the public API, move its declaration into an installed header so the finding reflects an intentional decision instead of an accident.

Cross-tool comparison

rtti_for_internal_type is a cross-source check unique to abicheck's audit mode โ€” it reconciles exported RTTI symbols against header provenance within the same build, which isn't something abidiff/abi-compliance-checker do (they diff two ABI dumps against each other, not a binary's exports against its own header provenance).


Source files

  • snapshot.abi.json

See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).