Skip to content

A /Prev in a section a guard stops is judged otherwise than a whole read judges it #264

Open

Description

@AdCodicem

The /Prev of a section a guard stops is judged otherwise than a whole read judges it: a reference to an object MaxObjectLength cuts is blamed on the file, and a fault the guarded read can see without reading past the guard goes unreported.

Batch 14 (#175) reads a /Prev or an /XRefStm written as a reference, in a section a guard ended the chain at, as a whole read reads it, unless it leads to what the guard hides. Two gaps are left, both older than the batch.

A reference to an object the object guard cuts, with no full index. ReadWithoutLoading reads the object /Prev names without recording it. When the object runs past MaxObjectLength, it reads as nothing. Since batch 14, the chain notes that the guard cut it, and a link so cut is unknown where a guard also ended the chain at that section, cutting its rows or filling the index. With MaxObjectLength alone, the section read whole, SectionOffset still reports the section missing: xref.section-missing, then the Error xref.section-not-found ("which no section read before it places where it can be read"), a rebuild, and file.root-invalid for the /Root the older trailer holds. A whole read follows /Prev with no fault. It is the /Prev counterpart of #263, which names /Filter and /Length.

Faults a section a guard stops does not report. SectionOffset is not called:

  • when MaxXRefSectionLength cuts a classic table (the ReadTrailerPastRows path);
  • when a table fills MaxIndexEntries;
  • when MaxDecodedStreamLength cuts a stream's rows.

When a cross-reference stream fills the index, the offset it gets is not followed. So the guarded read never reports a fault of that section's /Prev that it can see without reading past the guard:

  • a reference to a free row;
  • an offset outside the file;
  • a /Prev naming the section itself.

A whole read reports xref.section-missing and the Error xref.section-not-found for the first two, and xref.chain-cycle and the Error xref.chain-loop for the third.

Shapes, reproduced by the eleventh review of batch 14 on its branch

  • E1: cross-reference stream 22, /Index [0 1 21 1 2 20], /Prev 21 0 R, no /Root. Object 21 is a comment line of 3,000 bytes, then the older table's offset. With MaxObjectLength = 1024: xref.section-missing, limit.object, xref.rebuilt, file.root-invalid, xref.section-not-found. A whole read gives only file.trailer-value-wrong.
  • F: the newest classic table's rows are 0 1 / 21 1 (free) / 2 2 / 4 40, and its trailer is << /Size 44 /Prev 21 0 R >>, with MaxXRefSectionLength = 128. A whole read gives xref.section-missing and xref.section-not-found. The guarded read gives limit.xref-section-length, xref.rebuilt and file.root-invalid, but no finding on /Prev.
  • G: the same table, guarded the same way, with /Prev 99999999 (outside the file), or with /Prev its own offset. The guarded read gives only the guard's reports; a whole read gives xref.section-not-found, or xref.chain-cycle and xref.chain-loop.

What closes it

  • A link /Prev leads through that the object guard cut is unknown, as one past a full index is: the chain ends there for the guard, which is reported, and nothing is blamed on the file.
  • A section a guard stops still judges its /Prev as far as it can without reading past the guard: a reference to a free row, an offset outside the file, or the section itself is reported as a whole read reports it.
  • Tests in ReaderLimitsTests and CrossReferenceRuleTests for E1, F and G, each beside the same file read under the default limits.

Placed with batch 19, beside #188 and #197, the chain's reading of where its sections lie.

Found by the eleventh adversarial review of batch 14 (#129, #175).

Activity

  1. added
    debtKnown debt: a defect or a missing piece recorded rather than fixed on the way
    area: readerReading: lexer, parser, filters, cross-reference and object resolution
    on Oct 18, 2026
  2. added a commit that references this issue on Oct 18, 2026
  3. AdCodicem commented on Oct 23, 2026

    @AdCodicem
    OwnerAuthor

    Batch 16 (#263, branch ccr-6d6c0ee6-dqm4lm-174) pays this issue's first gap. The second is left for batch 19.

    Paid: a reference to an object the object guard cuts (shape E1). A /Prev or /XRefStm written as a reference to an object MaxObjectLength cuts now ends the chain for the guard, as /W, /Index, /Size, /Filter, /DecodeParms and a deferred /Length do. When the guard is reached, the reader:

    • reports limit.object once the chain is read, or throws at opening under ThrowOnLimit;
    • rebuilds nothing, so neither xref.section-missing, xref.section-not-found nor file.root-invalid follows;
    • has file.checked-in-part name the reference the guard cut.

    Batch 16 also gives the chain's own reads (TryReadUnrecorded) the file's edge, so a /Prev object ending exactly at MaxObjectLength reads whole. A hybrid file's stream's own /Prev, which no reader follows, is not read at all: file.trailer-value-wrong judges it as written.

    These tests hold it, in GuardCutValueTests:

    • A_section_offset_a_guard_cut_ends_the_chain_as_the_guard_s: a table's /Prev and a table's /XRefStm;
    • A_cross_reference_stream_s_prev_a_guard_cut_ends_the_chain_as_the_guard_s: E1's shape;
    • A_section_whose_prev_a_guard_cut_keeps_the_chain_it_hides_when_it_is_found_where_it_was_moved;
    • A_chain_a_guard_ends_at_a_hidden_prev_leaves_unjudged_what_needs_the_whole_chain.

    Left for batch 19: a section a guard stops judges no fault of its /Prev (shapes F and G). SectionOffset is still not called when one of these guards stops a section:

    • MaxXRefSectionLength;
    • a full index;
    • MaxDecodedStreamLength.

    So a reference to a free row, an offset outside the file, or a /Prev naming the section itself goes unreported under those guards. This issue stays open for that, beside #188 and #197.


    Generated by Claude Code

  4. AdCodicem commented on Oct 25, 2026

    @AdCodicem
    OwnerAuthor

    One variant of the paid shape remains, filed as #292. When the guard cuts the header of the object a /Prev names (9 0, a run of blanks, then obj), rather than its value, the chain still ends for the guard. But once the chain is read, the load that reaches the guard follows the load rule for a header past the object guard, and rebuilds the index.

    On a76c9da both forms had the index rebuilt after a false xref.section-missing. Batch 16 (#297) removes that report from both and the rebuild from the value cut.


    Generated by Claude Code

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

    area: readerReading: lexer, parser, filters, cross-reference and object resolutiondebtKnown debt: a defect or a missing piece recorded rather than fixed on the way

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions