diff --git a/crates/engine/src/cmd/spelling.rs b/crates/engine/src/cmd/spelling.rs index 1f1401495..1dd3df7a5 100644 --- a/crates/engine/src/cmd/spelling.rs +++ b/crates/engine/src/cmd/spelling.rs @@ -64,7 +64,14 @@ fn known(s: &Session, w: &str) -> bool { false } +/// Longest word we generate suggestions for. Candidate text grows with the square of the word length, so longer +/// tokens (no real word is this long) get no suggestions instead of an unbounded allocation. +const MAX_SUGGEST_LEN: usize = 64; + fn edits1(w: &str) -> Vec { + if w.chars().count() > MAX_SUGGEST_LEN { + return Vec::new(); + } let chars: Vec = w.chars().collect(); let mut out = Vec::new(); let letters = "abcdefghijklmnopqrstuvwxyz"; @@ -177,3 +184,16 @@ fn thesaurus(_: &mut Session, p: &Json) -> Result { related.truncate(20); Ok(json!(related)) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn edits1_is_capped_for_very_long_words() { + assert_eq!(edits1("helo").len(), 54 * 4 + 25); + assert_eq!(edits1(&"a".repeat(MAX_SUGGEST_LEN)).len(), 54 * MAX_SUGGEST_LEN + 25); + assert!(edits1(&"a".repeat(MAX_SUGGEST_LEN + 1)).is_empty()); + assert!(edits1(&"a".repeat(1_000_000)).is_empty()); + } +}