engine: long digit strings no longer overflow object.opacityKeys

opacity_from_digits multiplied and added on every character of its input
string before the one-or-two-digit check, so "9999999999" overflowed u32 on
the way (panic in debug builds, wrap in release). The scan now steps n with
checked_mul/checked_add and stops (None) at the first character the number
cannot take, so a long digit string is refused like any other bad input.

Test: opacity_digits (engine keymap) covers the ten-digit string and the
command's BadParams refusal.
This commit is contained in:
midasmatterhorn
2026-10-14 23:22:41 +03:00
parent 833f0d2c27
commit 65e52aa7b9
2 changed files with 8 additions and 2 deletions
+2 -1
View File
@@ -803,7 +803,8 @@ pub fn opacity_from_digits(digits: &str) -> Option<f32> {
let mut n = 0u32;
let mut count = 0;
for c in digits.chars() {
n = n * 10 + c.to_digit(10)?;
// Input-derived: a number that outgrows `u32` stops the scan (None); it never wraps.
n = n.checked_mul(10)?.checked_add(c.to_digit(10)?)?;
count += 1;
}
match count {
+6 -1
View File
@@ -4,11 +4,11 @@
use serde_json::{Value, json};
use crate::Session;
use crate::cmd::edit::opacity_from_digits;
use crate::keymap::{Context, GESTURES, Group, KEYS, Mods, Platform, display, lookup, matches, normalize, parse, primary};
use crate::params::id_of;
use crate::tools::ToolId;
use crate::{EngineError, Session};
use figcraft_doc::NodeId;
const PLATFORMS: [Platform; 2] = [Platform::Mac, Platform::Other];
@@ -186,6 +186,7 @@ fn opacity_digits() {
assert_eq!(opacity_from_digits("00"), Some(0.0));
assert_eq!(opacity_from_digits(""), None);
assert_eq!(opacity_from_digits("123"), None);
assert_eq!(opacity_from_digits("9999999999"), None, "ten digits must not overflow u32");
assert_eq!(opacity_from_digits("4x"), None);
assert_eq!(opacity_from_digits("٣"), None, "ASCII digits only");
@@ -201,6 +202,10 @@ fn opacity_digits() {
ex(&mut s, "edit.undo", json!({}));
assert_eq!(op(&s, a), 0.3, "one undo step");
assert!(s.execute("object.opacityKeys", &json!({"digits": "123"})).is_err());
assert!(
matches!(s.execute("object.opacityKeys", &json!({"digits": "9999999999"})), Err(EngineError::BadParams { .. })),
"an overflowing digit string is a parameter error, not a panic"
);
assert!(s.execute("object.opacityKeys", &json!({})).is_err());
ex(&mut s, "select.none", json!({}));
assert!(s.execute("object.opacityKeys", &json!({"digits": "1"})).is_err(), "nothing selected");