M7: the docs say which failed reads the allowance doesn't count

`Metrics::past_allowance`, `Reads::decode` and the Type 3 glyph-box read
said a stream that fails on its own data isn't counted as refused even when
it spends the last of the allowance. A stream past its own limit fails with
any allowance, yet with no more left than its stages' total it is counted,
as `Reads::decode` itself says just above. They now name the case that isn't:
a read that fails without the allowance refusing it, such as an empty stream
behind a filter nothing decodes. The program-list test's doc no longer says
more than it checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
Grzegorz Martens
2026-11-10 06:59:28 +01:00
co-authored by Claude Opus 5.5
parent 973ddc17e9
commit 5de168955b
2 changed files with 8 additions and 6 deletions
+7 -5
View File
@@ -72,7 +72,8 @@ pub struct Metrics {
/// was given was spent: what it would have decided (code lengths, CIDs, widths, meanings, a
/// Type 3 font's height) is a guess. It is set exactly when the allowance counted a refusal
/// during the read ([`DecodeBudget::refused`]), which callers that share it may refuse on; a
/// stream that fails on its own data doesn't set it, with or without anything left.
/// read that fails without one, such as an empty stream behind a filter nothing decodes,
/// doesn't set it, with or without anything left.
pub past_allowance: bool,
}
@@ -423,9 +424,9 @@ impl<'a> Reads<'a> {
/// made and `max`, it is charged those two and isn't refused; otherwise, with more left than
/// the stages' total (behind one filter, `max`), it is charged that total and isn't refused;
/// with no more left than that total, it spends all of it and counts as refused. Only what
/// the allowance counts as refused ([`DecodeBudget::refused`]) is: a stream that fails on its
/// own data fails the same with more left, even when it spends the last of it or nothing was
/// left, and callers that refuse on that count must agree with [`Metrics::past_allowance`].
/// the allowance counts as refused ([`DecodeBudget::refused`]) is noted: a read that fails
/// without one, such as an empty stream behind a filter nothing decodes, fails the same with
/// more left, and callers that refuse on that count must agree with [`Metrics::past_allowance`].
fn decode(&mut self, stream: &Stream, max: usize) -> Option<Vec<u8>> {
let refused = self.budget.refused();
match self.budget.decode_stages_within(self.doc, stream, max) {
@@ -502,7 +503,8 @@ fn type3_glyph_box(doc: &Document, font: &Dict, reads: &mut Reads<'_>) -> Option
left = left.saturating_sub(before.saturating_sub(reads.budget.remaining()));
let Ok(data) = read else {
// As in `Reads::decode`, only a refusal the allowance counts: a description that fails
// on its own data, spending the last of it, leaves the rest to be refused above.
// without one (an empty one nothing decodes, once the allowance is spent) leaves the
// rest to be read, or refused above.
if reads.budget.refused() > refused {
// The rest go unread too, for want of the reading's allowance.
reads.refused = true;
+1 -1
View File
@@ -437,7 +437,7 @@ fn each_stage_of_a_metrics_stream_is_held_to_its_limit() {
/// The editor's program list reads a legacy Thai font's metrics after its program. With the program
/// spending the allowance to the byte, a ToUnicode that is an empty stream behind a filter nothing
/// decodes, unread with any allowance, doesn't put the metrics past the allowance: the program is
/// listed, as with plenty. A ToUnicode with data is refused by the allowance, and the program isn't.
/// listed. A ToUnicode with data is refused by the allowance, and the program isn't.
#[test]
fn an_editors_program_is_listed_past_a_stream_unread_on_its_own() {
let bytes = include_bytes!("../../../../assets/fonts/Inter-Regular.ttf");