diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index 06d39eee..82bb3ac6 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -432,7 +432,7 @@ fn spelling_change(s: &mut Session, p: &Value) -> Result { // One exact occurrence (a squiggled word): paragraph `para`, from character `start`. if let (Some(id), Some(para), Some(start)) = (shape, usize_param(p, "para"), usize_param(p, "start")) { let id = deckcraft_model::ShapeId(u32::try_from(id).map_err(|_| bad("review.spellingChange", "bad `shape`"))?); - let end = start + word.chars().count(); + let end = start.checked_add(word.chars().count()).ok_or_else(|| bad("review.spellingChange", "bad `start`"))?; let cell = usize_param(p, "row").zip(usize_param(p, "col")); let slide = { let st = s.doc()?; @@ -845,3 +845,18 @@ mod tests { assert!(s.active().unwrap().doc.slides[0].comments.is_empty()); } } + +#[cfg(test)] +mod spelling_change_range_tests { + use crate::{EngineError, Session}; + use serde_json::json; + + #[test] + fn spelling_change_huge_start_is_bad_params() { + let mut s = Session::with_new(); + let r = s.execute("insert.textBox", &json!({"text": "Hello"})).unwrap(); + let id = r["id"].clone(); + let err = s.execute("review.spellingChange", &json!({"shape": id, "para": 0, "start": u64::MAX, "word": "Hello", "to": "x"})).unwrap_err(); + assert!(matches!(err, EngineError::BadParams { .. }), "{err:?}"); + } +}