Reject overflowing slice rectangles (#768)

* fix(engine): reject overflowing slice rectangles (#715)

* test(engine): cover slice rectangle overflow (#715)

* refactor(engine): keep slice rect validation explicit (#715)

* test(engine): exercise overflowing slice commands (#715)

* fix(engine): reject invalid slice.set geometry before mutation
This commit is contained in:
Nigel Ihza Firdausy
2026-10-07 17:12:39 -04:00
committed by GitHub
parent bd632e7eb3
commit 1a052d6115
2 changed files with 25 additions and 4 deletions
+12 -4
View File
@@ -126,15 +126,20 @@ pub(crate) fn refresh(s: &mut Session) {
pub(crate) fn rect_param(p: &Value) -> Option<Rect> {
let f = |v: &Value| v.as_f64().filter(|x| x.is_finite()).map(|x| x.round() as i32);
let rect = |x: i32, y: i32, w: i32, h: i32| {
if w <= 0 || h <= 0 {
return None;
}
Some(Rect::new(x, y, x.checked_add(w)?, y.checked_add(h)?))
};
if let Some(a) = p.get("rect").and_then(Value::as_array).filter(|a| a.len() == 4) {
let v: Vec<i32> = a.iter().filter_map(f).collect();
if v.len() == 4 && v[2] > 0 && v[3] > 0 {
return Some(Rect::new(v[0], v[1], v[0] + v[2], v[1] + v[3]));
if v.len() == 4 {
return rect(v[0], v[1], v[2], v[3]);
}
return None;
}
let (x, y, w, h) = (f(p.get("x")?)?, f(p.get("y")?)?, f(p.get("width")?)?, f(p.get("height")?)?);
(w > 0 && h > 0).then(|| Rect::new(x, y, x + w, y + h))
rect(f(p.get("x")?)?, f(p.get("y")?)?, f(p.get("width")?)?, f(p.get("height")?)?)
}
fn hex_color(s: &str) -> Option<[u8; 4]> {
@@ -289,6 +294,9 @@ fn from_guides(s: &mut Session) -> Result<Value> {
fn set_slice(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "slice.set";
if ["rect", "x", "y", "width", "height"].iter().any(|key| p.get(key).is_some()) && rect_param(p).is_none() {
return Err(bad(cmd, "give a finite rectangle with positive dimensions and representable corners"));
}
let id = target(s, p, cmd, true)?;
let coalesce_into_promote = s.active().is_some_and(|d| d.history.undo_label() == Some("Promote to User Slice")) && p.get("number").is_some();
let label = "Slice Options";
+13
View File
@@ -50,6 +50,19 @@ fn user_slices_and_options() {
}
}
#[test]
fn rect_param_rejects_corner_overflow() {
assert_eq!(rect_param(&json!({"rect": [1, 2, 3, 4]})), Some(Rect::new(1, 2, 4, 6)));
assert_eq!(rect_param(&json!({"x": 1, "y": 2, "width": 3, "height": 4})), Some(Rect::new(1, 2, 4, 6)));
assert_eq!(rect_param(&json!({"rect": [i32::MAX, 0, 1, 1]})), None);
assert_eq!(rect_param(&json!({"x": 0, "y": i32::MAX, "width": 1, "height": 1})), None);
let mut s = session(8);
assert!(s.execute("slice.new", json!({"rect": [i32::MAX, 0, 1, 1]})).is_err());
let id = s.execute("slice.new", json!({"rect": [1, 1, 8, 8]})).unwrap()["slice"].as_u64().unwrap();
assert!(s.execute("slice.set", json!({"slice": id, "rect": [0, i32::MAX, 1, 1]})).is_err());
}
#[test]
fn promote_auto_slice_and_divide() {
let mut s = session(8);