Skip to content

Suspicious TH1D object with fByteCount == 0 (e.g. uproot-issue-250.root) #16

Open

Description

@nsmith-
No description provided.

Activity

  1. nsmith- commented on Apr 25, 2025

    @nsmith-
    OwnerAuthor

    Noting that

    @serializable
    class TAtt3D(StreamedObject):
    """Some TH3D attributes
    This class is usually in the StreamerInfo, but missing in some files:
    uproot-from-geant4.root
    uproot-issue-250.root
    So we get "ValueError: Class TH3 depends on TAtt3D, which is not declared"
    Note that the second file also has a suspicious TH1D object with fByteCount == 0
    """

    the file reported in scikit-hep/uproot5#250 has two weird things going on

  2. nsmith- commented on Sep 18, 2026

    @nsmith-
    OwnerAuthor

    🤖 AI generated content

    Findings below come from the root-io-spec reference now vendored in this repo at reference/root-io-spec (pinned at 9c3f776). It answers this issue directly, and the answer is that nothing is suspicious about these records — they are ROOT 4 histograms and our framing reader is wrong.

    The format fact

    Buffer framing §2.3 — "A record's object data does not always begin with one":

    And on a file older than ROOT 5, an ordinary class's payload may have no byte count either. […] Measured on the two ROOT 4.00/00 files of the foreign corpus: 19 records across them — TH1D, TH2D — begin 00 01 00 03 40 00 ..., a version word, a version word, and only then a byte count.

    Those "two ROOT 4.00/00 files" are exactly our two xfails. I confirmed the bytes: uproot-issue-250.root header fVersion is 40000, and the TH1D record Eabs at offset 35698 decompresses to

    00 01 00 03 40 00 00 22 00 01 00 00 00 00 02 00 00 00 04 45 61 62 73 ...
     ^TH1D v1 ^TH1 v3 ^bc=34   ^TNamed v1  ^TObject          ^ "Eabs"
    

    uproot-from-geant4.root is also fVersion 40000 (10 TH1D + 5 TH2D records; 15 + 4 = the spec's 19).

    The decision rule is structural, not a file-version test — Buffer framing §3: read a u32; if kByteCountMask is clear, rewind 4 bytes and read an i16 version with "no byte count".

    What that means here

    StreamHeader.read (src/rootfilespec/bootstrap/streamedobject.py:51) implements only the object-slot rule (Buffer §6), where a word without kByteCountMask is a back-reference. A version word needs the different rule above. So:

    • _read_all_members (src/rootfilespec/bootstrap/streamedobject.py:223) needs a version-word reader with an optional byte count, at every nesting level (TH1's 00 03 is bare too);
    • the TH1D/TH2D guard at streamedobject.py:233-238 then goes away;
    • the end_position assertion at streamedobject.py:261 must be skipped when there is no byte count.

    Both classes are otherwise decodable from the file's own info: TH1D v1 = TH1 + TArrayD, TH1 v3 = TNamed(67), TAttLine/TAttFill/TAttMarker(0), fNcells, three TAxis… — all of which are framed normally in the bytes. (Small note for upstream: §2.3 says "only the TNamed base inside is framed"; the TAtt* bases are framed too. The precise 00 01 00 03 40 00 statement in the same paragraph is exact.)

    The other weird thing in this file

    TAtt3D (src/rootfilespec/bootstrap/assumed.py:38-47): both files' TH3 v4 info lists TStreamerBase TAtt3D (fType 0) but carries no TAtt3D streamer info, violating Streamer-driven reading §10 invariant 5. Neither file contains a TH3 object, so this only bites because we materialise every class in the list eagerly. TAtt3D is ClassDef(TAtt3D,1) with no data members, so its kBase contribution is a byte count and a version word and nothing else — the current empty stub is the right shape.

    Verdict: reader gap, not a bad file. ROOT reads both files without complaint. Size: small-to-medium, confined to streamedobject.py, no dependencies.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions