Skip to content

Assert the spec's cheap invariants in the serialization layer (framing, TArray, streamer elements) #102

Description

@nsmith-

🤖 AI generated content

The spec ends most chapters with an "Invariants" section. rootfilespec checks roughly one of them (the end position, at indent == 0 only — src/rootfilespec/bootstrap/streamedobject.py:261). The reviewers picked out the cheap ones that would turn silent desyncs into immediate, located errors. None of these is a known failure on a real file; this is hardening.

Framing — Buffer framing §9, at StreamHeader.read (streamedobject.py:51-99):

  • inv. 1: byte count < kMaxMapCount (0x3FFFFFFE)
  • inv. 4: masked version word in [0, kMaxVersion] (0x3FFF)
  • inv. 8: a class/object back-reference above 1 points backwards
  • inv. 2: a byte count + 4 does not extend past the enclosing byte count / payload

Values:

  • TArray §5 inv. 1: fN >= 0 — today a negative count reaches np.frombuffer(count=n) (src/rootfilespec/container.py:48)
  • References §8 inv. 1: the 10-vs-12-byte TObject base (only checked indirectly today)

Streamer info / elementsStreamer information §13, Element types §11, Collections §14, Streamer-driven reading §10, in src/rootfilespec/bootstrap/TStreamerInfo.py:

  • fArrayDim ∈ [0,5]; when non-zero the first fArrayDim entries of fMaxIndex are positive and their product is fArrayLength (needs TStreamerElement.fMaxIndex is read in native byte order #92 first; the spec notes it legitimately fails for an STL element written before ROOT 6.24/02)
  • a TStreamerBase has fTypeName == "BASE" and fType ∈ {0, 66, 67, -1}
  • a TStreamerSTL/TStreamerSTLstring has fType == 500 (ROOT ≥ 5); a TStreamerSTLstring has fSTLtype == fCtype == 365
  • fCountName names an integer element earlier in the same list or in a base — the commented-out check at TStreamerInfo.py:397-402 is this one. It must accept kInt (3) and kUInt (13) as well as kCounter (6) (Element types §2.1: TArrayD.fArray names an fType 3 fN on ROOT 4 files; TBits.fAllBits names an fType 13 fNbytes)
  • 0 <= fClassVersion <= 65000 (Schema evolution §9 inv. 1)

Design note: Buffer §9 says invariant 9 (consumed bytes == byte count) legitimately fails in the wild, so these checks should report through the same warning channel as #98 rather than all being hard errors; the framing ones (1, 4, 8) are safe to raise on.

Severity / size: low / S each.

Related: #81 (container-layer counterpart), #98, #107, #92.

Assisted-by: claude-code:claude-fable-5-1

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions