From f26226590ad579f0dcf9931d842050e915e6688e Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:13:36 +0100 Subject: [PATCH] Reject negative and fractional index in variable.moveMode Found while testing crates/engine/src/cmd/variables.rs:267 (move_mode) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/variables.rs | 6 +++--- crates/engine/src/variables_behaviour_tests.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/variables.rs b/crates/engine/src/cmd/variables.rs index 59ad46ced..75607ae64 100644 --- a/crates/engine/src/cmd/variables.rs +++ b/crates/engine/src/cmd/variables.rs @@ -12,7 +12,7 @@ use serde_json::{Value, json}; use super::lists::BoundList; use super::{CommandSpec, ok}; -use crate::params::{boolean, get, num, req_string, string}; +use crate::params::{boolean, get, req_index, req_string, string}; use crate::{Result, Session, bad, spec}; pub fn specs() -> Vec { @@ -258,13 +258,13 @@ fn remove_mode(s: &mut Session, p: &Value) -> Result { fn move_mode(s: &mut Session, p: &Value) -> Result { let cmd = "variable.moveMode"; - let to = num(p, "to").filter(|x| x.is_finite()).ok_or_else(|| bad(cmd, "`to` must be a number"))?; + let to = req_index(cmd, p, "to")?; let doc = s.doc_mut()?; let (ci, mode) = mode_of(doc, cmd, p)?; let coll = doc.variables.collections.get_mut(ci).ok_or_else(|| bad(cmd, "no such collection"))?; let from = coll.modes.iter().position(|m| m.id == mode).ok_or_else(|| bad(cmd, "no such mode"))?; let m = coll.modes.remove(from); - let at = (to.max(0.0) as usize).min(coll.modes.len()); + let at = to.min(coll.modes.len()); coll.modes.insert(at, m); ok() } diff --git a/crates/engine/src/variables_behaviour_tests.rs b/crates/engine/src/variables_behaviour_tests.rs index 306a8fba9..2717726f8 100644 --- a/crates/engine/src/variables_behaviour_tests.rs +++ b/crates/engine/src/variables_behaviour_tests.rs @@ -68,6 +68,20 @@ fn types_and_their_starting_values() { assert!(s.execute("variable.create", &json!({"collection": "C", "name": "y", "type": "number", "value": "wide"})).is_err()); } +#[test] +fn move_mode_rejects_negative_and_fractional_indexes() { + let mut s = Session::new(); + theme(&mut s); + run(&mut s, "variable.addMode", json!({"collection": "Theme", "name": "Dim"})); + let order = + |s: &Session| -> Vec { s.doc().unwrap().variables.collection("Theme").unwrap().modes.iter().map(|m| m.name.clone()).collect() }; + let before = order(&s); + for to in [json!(-1), json!(1.9)] { + assert!(s.execute("variable.moveMode", &json!({"collection": "Theme", "mode": "Dim", "to": to})).is_err(), "{to}"); + assert_eq!(order(&s), before, "{to}"); + } +} + #[test] fn modes_new_duplicated_reordered_and_the_default() { // Documented: a new mode starts with the default mode's values and a duplicated one with its