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(); +}