From c2695f6ab0ecbb8e566bbb154b3d8bac4871c754 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:21:10 +0100 Subject: [PATCH] Refuse invalid counts in JSON arraypolar and arrayrect (#2399) Found while testing crates/engine/src/cmd/array.rs:332-334 (count_or, used by run_arrayrect at 338-340 and run_arraypolar at 363-365) Co-authored-by: Claude Haiku 5.5 --- crates/engine/src/cmd/array.rs | 21 ++++++++++++--------- crates/engine/src/tests.rs | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/engine/src/cmd/array.rs b/crates/engine/src/cmd/array.rs index 479b7873a..9d4a2fd9c 100644 --- a/crates/engine/src/cmd/array.rs +++ b/crates/engine/src/cmd/array.rs @@ -328,16 +328,19 @@ fn run_array(s: &mut Session, p: &Value) -> Result { (spec.run)(s, p) } -/// A count parameter (at least 1). -fn count_or(p: &Value, key: &str, default: u64) -> u64 { - p.get(key).and_then(Value::as_u64).unwrap_or(default).max(1) +/// A count parameter (at least 1); a missing key gives the default, an invalid value is refused. +fn count_or(cmd: &str, p: &Value, key: &str, default: u64) -> Result { + match p.get(key) { + None | Some(Value::Null) => Ok(default), + Some(v) => v.as_u64().filter(|n| *n >= 1).ok_or_else(|| bad(cmd, format!("`{key}` must be a whole number of at least 1"))), + } } fn run_arrayrect(s: &mut Session, p: &Value) -> Result { let hs = targets(s, p)?; - let rows = count_or(p, "rows", 3); - let cols = count_or(p, "cols", 4); - let levels = count_or(p, "levels", 1); + let rows = count_or("arrayrect", p, "rows", 3)?; + let cols = count_or("arrayrect", p, "cols", 4)?; + let levels = count_or("arrayrect", p, "levels", 1)?; check_size(s, rows.saturating_mul(cols).saturating_mul(levels), hs.len())?; let kind = ArrayKind::Rect(RectArray { cols: count32(cols), @@ -360,9 +363,9 @@ fn run_arrayrect(s: &mut Session, p: &Value) -> Result { fn run_arraypolar(s: &mut Session, p: &Value) -> Result { let hs = targets(s, p)?; let c = point_req("arraypolar", p, "center")?; - let n = count_or(p, "count", 6); - let rows = count_or(p, "rows", 1); - let levels = count_or(p, "levels", 1); + let n = count_or("arraypolar", p, "count", 6)?; + let rows = count_or("arraypolar", p, "rows", 1)?; + let levels = count_or("arraypolar", p, "levels", 1)?; check_size(s, n.saturating_mul(rows).saturating_mul(levels), hs.len())?; let base = point_param(p, "base").or_else(|| s.doc().ok().and_then(|d| first_center(d, &hs))).unwrap_or(c); let row_spacing = f64_or(p, "rowSpacing", 1.0); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 066df250b..b06641fbb 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1444,3 +1444,22 @@ fn hostile_variable_like_input_never_panics() { s.cmdline("pickadd").unwrap(); assert!(setvar_prompt_open(&s)); } + +#[test] +fn array_counts_reject_invalid_values() { + let mut s = Session::new(); + s.execute("line", &json!({"points": [[0, 0], [1, 0]]})).unwrap(); + for bad in [json!(-3), json!(0), json!(2.5)] { + for key in ["count", "rows", "levels"] { + let mut p = json!({"center": [0, 0], "associative": false}); + p[key] = bad.clone(); + assert!(s.execute("arraypolar", &p).is_err(), "arraypolar {key}={bad}"); + } + for key in ["rows", "cols", "levels"] { + let mut p = json!({"associative": false}); + p[key] = bad.clone(); + assert!(s.execute("arrayrect", &p).is_err(), "arrayrect {key}={bad}"); + } + } + s.execute("arraypolar", &json!({"center": [0, 0], "count": 1, "associative": false})).unwrap(); +}