mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3810 from Silvertree2010/fix/3567-audit-eps-import-postscript-filt
Charge PostScript filter output to the EPS interpreter budget
This commit is contained in:
@@ -731,6 +731,12 @@ impl Interp<'_> {
|
||||
self.push_interval(Obj::Str(s), 0, got.len())
|
||||
}
|
||||
|
||||
/// Count `n` decoded bytes against the memory budget, and the time to decode them.
|
||||
fn charge_decoded(&mut self, n: usize) -> Res {
|
||||
self.alloc(n)?;
|
||||
self.spend((n / 4096) as u64)
|
||||
}
|
||||
|
||||
/// `filter`: a file that decodes its source. Over the program's own data it decodes when
|
||||
/// first read (the data follows the operator that reads it); over other data, at once.
|
||||
fn filter(&mut self) -> Res {
|
||||
@@ -775,6 +781,7 @@ impl Interp<'_> {
|
||||
let mut b = f.borrow_mut();
|
||||
let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") };
|
||||
let (out, used, rgb) = decode(&spec, data.get(*pos..).unwrap_or_default())?;
|
||||
self.charge_decoded(out.len())?;
|
||||
*pos = pos.saturating_add(used);
|
||||
Stream::Buf { data: out, pos: 0, rgb }
|
||||
}
|
||||
@@ -783,11 +790,13 @@ impl Interp<'_> {
|
||||
Obj::Str(s) => {
|
||||
self.alloc(s.borrow().len())?;
|
||||
let (data, _, rgb) = decode(&spec, &s.borrow())?;
|
||||
self.charge_decoded(data.len())?;
|
||||
Stream::Buf { data, pos: 0, rgb }
|
||||
}
|
||||
p @ Obj::Array { .. } => {
|
||||
let raw = self.gather(&p, MAX_DATA)?;
|
||||
let (data, _, rgb) = decode(&spec, &raw)?;
|
||||
self.charge_decoded(data.len())?;
|
||||
Stream::Buf { data, pos: 0, rgb }
|
||||
}
|
||||
_ => return ps_err("typecheck", "filter"),
|
||||
@@ -830,13 +839,13 @@ impl Interp<'_> {
|
||||
None => {
|
||||
let raw = self.lex.data();
|
||||
let (data, used, rgb) = decode(spec, raw)?;
|
||||
self.alloc(data.len())?;
|
||||
self.charge_decoded(data.len())?;
|
||||
self.lex.advance(used);
|
||||
(data, rgb)
|
||||
}
|
||||
Some((prev, was)) => {
|
||||
let (data, _, rgb) = decode(spec, &prev)?;
|
||||
self.alloc(data.len())?;
|
||||
self.charge_decoded(data.len())?;
|
||||
(data, rgb.or(was))
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1103,3 +1103,19 @@ fn restore_puts_dictionaries_back() {
|
||||
// take, is positive (chapter 8), and programs divide by it.
|
||||
super::tests::check("cachestatus 7 1 roll 6 { pop } repeat 0 gt");
|
||||
}
|
||||
|
||||
/// #3567: what a `filter` decodes counts against the memory budget, not just its input. A
|
||||
/// small string that inflates to 16 MiB, decoded over and over (each result dropped), ends
|
||||
/// the program; a single one does not.
|
||||
#[test]
|
||||
fn filter_output_counts_against_the_memory_budget() {
|
||||
fn hex(data: &[u8]) -> String {
|
||||
data.iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
let z = hex(&deflate(&vec![0u8; 16 << 20]));
|
||||
let once = import(&file("t", &format!("/z <{z}> def z /FlateDecode filter pop 0 0 1 1 rectfill"))).unwrap();
|
||||
assert!(once.warnings.iter().all(|w| !w.contains("memory")), "{:?}", once.warnings);
|
||||
let many = import(&file("t", &format!("/z <{z}> def 100 {{ z /FlateDecode filter pop }} repeat 0 0 1 1 rectfill")));
|
||||
let Err(err) = many else { panic!("the budget ends the program") };
|
||||
assert!(err.contains("too much memory"), "{err}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user