Batch fix: EPS: load returns an operator value, so an executable-name alias runs what its name is bound to (#1909)

This commit is contained in:
aiongg
2026-10-24 01:43:40 +00:00
parent a407281852
commit 2cca29830e
2 changed files with 30 additions and 4 deletions
+19 -4
View File
@@ -28,6 +28,8 @@ enum V {
Name(Rc<str>),
/// A literal name (`/name`).
Lit(Rc<str>),
/// A built-in operator, as `load` returns it for a name no dictionary defines.
Op(Rc<str>),
Str(Rc<RefCell<Vec<u8>>>),
Proc(Rc<Vec<V>>),
Array(Rc<RefCell<Vec<V>>>),
@@ -47,7 +49,7 @@ impl V {
}
fn key(&self) -> String {
match self {
V::Name(n) | V::Lit(n) => n.to_string(),
V::Name(n) | V::Lit(n) | V::Op(n) => n.to_string(),
V::Str(s) => String::from_utf8_lossy(&s.borrow()).into_owned(),
V::Num(n) => format!("{n}"),
V::Bool(b) => format!("{b}"),
@@ -441,6 +443,12 @@ impl Interp {
break;
}
}
V::Op(op) => {
r = self.spend(1).and_then(|()| self.builtin(op, src));
if r.is_err() {
break;
}
}
// Other items count against the budget as names do.
other => {
r = self.spend(1);
@@ -465,6 +473,10 @@ impl Interp {
self.calls -= 1;
r
}
V::Op(op) => {
self.spend(1)?;
self.builtin(&op, src)
}
other => {
self.push(other);
Ok(())
@@ -480,7 +492,9 @@ impl Interp {
// An operator stored under another name (`/mo /moveto load def`, as Illustrator's
// EPS prolog does for every path operator) runs that operator. It pushed the
// operator's name instead, so such files drew nothing (#955).
V::Name(op) => self.builtin(&op, src),
V::Op(op) => self.builtin(&op, src),
// An executable name (`/y /x cvx def`) runs what that name is bound to.
V::Name(_) => self.exec_obj(v, src),
other => {
self.push(other);
Ok(())
@@ -977,7 +991,7 @@ impl Interp {
}
"load" => {
let k = self.pop().key();
let v = self.lookup(&k).unwrap_or(V::Name(k.into()));
let v = self.lookup(&k).unwrap_or(V::Op(k.into()));
self.push(v);
}
"undef" => {
@@ -1174,13 +1188,14 @@ impl Interp {
}
"xcheck" => {
let v = self.pop();
self.push(V::Bool(matches!(v, V::Proc(_) | V::Name(_))));
self.push(V::Bool(matches!(v, V::Proc(_) | V::Name(_) | V::Op(_))));
}
"type" => {
let t = match self.pop() {
V::Num(_) => "realtype",
V::Bool(_) => "booleantype",
V::Name(_) | V::Lit(_) => "nametype",
V::Op(_) => "operatortype",
V::Str(_) => "stringtype",
V::Proc(_) | V::Array(_) => "arraytype",
V::Dict(_) => "dicttype",
+11
View File
@@ -55,6 +55,17 @@ fn eps_filter_on_a_string_source_does_not_skip_the_program() {
assert!(coverage(&render(&doc), 50, 25, 150, 75) > 4900.0, "drawing after an image read from a string");
}
/// A loaded operator runs that operator even after its name is redefined (the prolog idiom
/// `/_m /moveto load def /moveto {… _m} def`), and an executable name bound with `cvx` runs what
/// its name is bound to.
#[test]
fn eps_loaded_operators_and_executable_name_aliases() {
let saved = "/_m /moveto load def /moveto {_m} def 1 0 0 setrgbcolor 50 25 moveto 150 25 lineto 150 75 lineto 50 75 lineto closepath fill\n";
assert!(coverage(&render(&eps(saved.as_bytes())), 50, 25, 150, 75) > 4900.0, "a redefined moveto that calls the saved one");
let alias = "/sq {50 25 moveto 150 25 lineto 150 75 lineto 50 75 lineto closepath fill} def /y /sq cvx def 1 0 0 setrgbcolor y\n";
assert!(coverage(&render(&eps(alias.as_bytes())), 50, 25, 150, 75) > 4900.0, "an executable name bound to a procedure's name");
}
/// Each `exec` of the executable name `exec` runs the next one: the chain is cut off at the call
/// depth instead of overflowing the stack.
#[test]