🤖 AI generated content
read_streamed_item returns the placeholder Ref(Some()) for every non-null object reference (src/rootfilespec/bootstrap/streamedobject.py:158-166, with # TODO: fetch the referenced object from the buffer.instance_refs). BufferContext.instance_refs is declared at src/rootfilespec/serializable.py:66-75 and nothing ever writes to it (# TODO: register the object addr …, streamedobject.py:191).
Spec: Buffer framing §6.1 "Object references": a reference means "the object I already built at that position"; two slots referring to one object are the same object, and cyclic graphs round-trip. When the position is not yet in the map, ROOT seeks and reads out of order — position - 2 for an object, position - 2 - 4 for a class. §6.2: "a reader MUST treat a reference of 1 as the record's top-level object rather than as a buffer position, and MUST NOT reject it."
Effect: data is lost silently — e.g. a TMap's shared values (Containers §1.1), every repeated entry of a TList, and TTree::fLeaves → TBranch::fLeaves sharing.
Suggested fix: register each object under the position of its slot's byte-count word (+ kMapOffset) as it is built, resolve references from that map, special-case 1 (top-level object) and 0 (null, already handled). Note the two maps currently use different key conventions: type_refs is keyed by the raw tag offset with kMapOffset subtracted at lookup (streamedobject.py:81, :94), while instance_refs's docstring says "the position of the StreamHeader". Pick one. Ref already avoids a dataclass repr for cycles; out-of-order resolution needs either a lazy Ref or a seek-and-read path.
Severity / size: medium / M.
Related: #106, #105, #101 (TRef/TRefArray are a separate, fUniqueID-based mechanism: #110).
Assisted-by: claude-code:claude-fable-5-1
read_streamed_itemreturns the placeholderRef(Some())for every non-null object reference (src/rootfilespec/bootstrap/streamedobject.py:158-166, with# TODO: fetch the referenced object from the buffer.instance_refs).BufferContext.instance_refsis declared atsrc/rootfilespec/serializable.py:66-75and nothing ever writes to it (# TODO: register the object addr …,streamedobject.py:191).Spec: Buffer framing §6.1 "Object references": a reference means "the object I already built at that position"; two slots referring to one object are the same object, and cyclic graphs round-trip. When the position is not yet in the map, ROOT seeks and reads out of order —
position - 2for an object,position - 2 - 4for a class. §6.2: "a reader MUST treat a reference of 1 as the record's top-level object rather than as a buffer position, and MUST NOT reject it."Effect: data is lost silently — e.g. a
TMap's shared values (Containers §1.1), every repeated entry of aTList, andTTree::fLeaves→TBranch::fLeavessharing.Suggested fix: register each object under the position of its slot's byte-count word (+
kMapOffset) as it is built, resolve references from that map, special-case 1 (top-level object) and 0 (null, already handled). Note the two maps currently use different key conventions:type_refsis keyed by the raw tag offset withkMapOffsetsubtracted at lookup (streamedobject.py:81,:94), whileinstance_refs's docstring says "the position of the StreamHeader". Pick one.Refalready avoids a dataclassreprfor cycles; out-of-order resolution needs either a lazyRefor a seek-and-read path.Severity / size: medium / M.
Related: #106, #105, #101 (
TRef/TRefArrayare a separate,fUniqueID-based mechanism: #110).Assisted-by: claude-code:claude-fable-5-1