fix(cos): count set-aside xref rows against the chain's total cap (#5082)

Rows set aside are kept for the whole chain, and a section can set aside
nearly everything it lists, so they count toward MAX_XREF_OBJECTS too.
Review feedback on #5090.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Higgs32584
2026-10-27 21:49:52 -04:00
co-authored by Claude Opus 5.5
parent fde104475b
commit 780764cbac
+7 -4
View File
@@ -417,10 +417,13 @@ impl Document {
// Each section's rows are capped, so the chain is too: at most MAX_XREF_OBJECTS rows
// merged in all, or the file is rebuilt from object headers, as when one section's
// stream decodes past that. This counts rows merged, not the map's size, since a chain
// that relists the same objects costs as much to merge. A section's rows (its hybrid
// stream's included) never outnumber the rows the compact reader counts for it, and
// that reader stops at about a quarter of this cap: no chain within its limit is refused.
let Some(total) = merged_rows.checked_add(sect.len()).filter(|total| *total <= MAX_XREF_OBJECTS) else {
// that relists the same objects costs as much to merge. Rows set aside count too: they
// are kept for the whole chain, and a section can set aside nearly all it lists. A
// section's rows (its hybrid stream's included) never outnumber the rows the compact
// reader counts for it, and that reader stops at about a quarter of this cap: no chain
// within its limit is refused.
let rows = sect.len().saturating_add(odd.len());
let Some(total) = merged_rows.checked_add(rows).filter(|total| *total <= MAX_XREF_OBJECTS) else {
return Err(CosError::Syntax {
offset: usize::try_from(off).unwrap_or(usize::MAX),
detail: format!("cross-reference chain lists more than {MAX_XREF_OBJECTS} entries"),