Merge pull request #5423 from Silvertree2010/fix/5363-eps-import-of-a-large-valid-file

Scale the EPS operation budget with the program's length
This commit is contained in:
BFlat
2026-11-09 03:15:35 +08:00
committed by GitHub
2 changed files with 26 additions and 3 deletions
+9 -3
View File
@@ -15,8 +15,11 @@ use super::graphics::{GState, Out};
use super::lex::{IMMEDIATE, Lexer, number};
use super::obj::{Dict, DictRef, Key, Obj, Op, PsError, Res, Shared, ps_err};
/// Most operations one file may run.
/// Most operations one file may run, whatever its length.
const MAX_OPS: u64 = 50_000_000;
/// More operations a file may run for each byte of its program: a large file that draws a lot
/// runs proportionally more, and stays bounded by its length.
const OPS_PER_BYTE: u64 = 64;
/// Deepest procedure nesting.
const MAX_DEPTH: u32 = 400;
/// Most operands on the stack.
@@ -55,6 +58,8 @@ pub(crate) struct Interp<'a> {
resources: DictRef,
depth: u32,
ops: u64,
/// Most operations this program may run: [`MAX_OPS`] and [`OPS_PER_BYTE`] for each byte.
max_ops: u64,
/// Bytes asked for so far (see [`MAX_MEMORY`]).
allocated: usize,
seed: u32,
@@ -177,6 +182,7 @@ impl<'a> Interp<'a> {
resources,
depth: 0,
ops: 0,
max_ops: MAX_OPS.saturating_add((src.len() as u64).saturating_mul(OPS_PER_BYTE)),
allocated: 0,
seed: 1,
saves: vec![],
@@ -360,11 +366,11 @@ impl<'a> Interp<'a> {
self.spend((n / 256) as u64)
}
/// Count `ops` operations' worth of work against [`MAX_OPS`]: what an operator that costs
/// Count `ops` operations' worth of work against the budget (`max_ops`): what an operator that costs
/// far more than one operation (laying out type) does in a loop has to end too.
pub fn spend(&mut self, ops: u64) -> Res {
self.ops = self.ops.saturating_add(ops);
if self.ops > MAX_OPS {
if self.ops > self.max_ops {
return Err(PsError::Limit("the program runs too long"));
}
Ok(())
+17
View File
@@ -1131,6 +1131,23 @@ fn runs_out_of_budget(body: &str) {
assert!(r.warnings.iter().any(|w| w.contains("the program runs too long")), "{body}: {:?}", r.warnings);
}
/// A large file that runs far more operations than a small one may, but only a few hundred for
/// each object it draws, reads to its end: the operation budget grows with the program's length.
#[test]
fn a_large_file_with_many_objects_reads_to_its_end() {
let n = 200_000;
let mut body = String::from(
"/sq { 4 dict begin /y exch def /x exch def 0 1 200 { pop } for \
x y moveto x 1 add y lineto x y 1 add lineto closepath fill end } def\n",
);
for i in 0..n {
body.push_str(&format!("{} {} sq\n", i % 100, i / 2000));
}
let r = read_within(&body, 300);
assert!(r.warnings.is_empty(), "{:?}", r.warnings);
assert_eq!(objects(&r.document).len(), n);
}
/// A loop of operators whose work grows with the path they make runs out of the operation budget
/// (PLRM 3rd ed., chapter 8): `arc` and `arct` with a huge radius, which make thousands of curve
/// segments, `strokepath` with a dash far finer than the line, dashes of length zero included, and