Merge pull request #7719 from dotCooCoo/fix/unit-parse-long-expression

Units: a very long expression reads as nothing instead of overflowing the stack
This commit is contained in:
BFlat
2026-11-15 09:46:13 +07:00
committed by GitHub
3 changed files with 25 additions and 1 deletions
+15
View File
@@ -79,6 +79,21 @@ fn odd_text_reads_as_nothing_without_panicking() {
}
}
/// Text from a command or a file can be any length: a sum of thousands of terms reads as nothing
/// instead of exhausting the stack. Up to 128 `+ - * /` signs read; 129 read as nothing.
#[test]
fn a_very_long_expression_reads_as_nothing() {
let sum = |terms: usize| format!("1{}", "+1".repeat(terms - 1));
assert_eq!(Unit::Points.parse(&sum(129)), Some(129.0));
assert_eq!(Unit::Points.parse(&format!("10{} mm", "*1".repeat(128))), Some(Unit::Millimeters.to_pt(10.0)));
// Long text with no signs still reads.
assert_eq!(Unit::Points.parse(&format!("{}5", " ".repeat(2_000))), Some(5.0));
for text in [sum(200_000), format!("1{}", "*1".repeat(200_000)), sum(130), format!("10{} mm", "*1".repeat(129))] {
assert_eq!(Unit::Points.parse(&text), None, "{} bytes", text.len());
assert_eq!(crate::parse_number(&text), None, "{} bytes", text.len());
}
}
proptest! {
/// Field text made of digits, operators, `%`, spaces and unit letters reads as a finite value
/// or as nothing and does not panic.
+8
View File
@@ -156,7 +156,12 @@ impl Unit {
/// before it (`3cm*50%` is 1.5 cm, `3cm/50%` is 6 cm). After `+` or `-`, it adds or takes away
/// that share of everything before it (`50pt+25%` is 62.5 pt, `100pt+20pt-50%` is 60 pt). A
/// percentage on its own (`50%`) reads as nothing.
///
/// Text with more than 128 `+ - * /` signs reads as nothing.
pub fn parse(self, s: &str) -> Option<f64> {
if s.chars().filter(|c| matches!(c, '+' | '-' | '*' | '/')).count() > MAX_MEASURE_OPERATORS {
return None;
}
parse_measure(s, self).filter(|v| v.is_finite())
}
/// The value naming this unit in the Units preferences (`points`, `millimeters`,
@@ -207,6 +212,9 @@ impl Unit {
}
}
/// The most `+ - * /` signs [`Unit::parse`] reads in one text.
const MAX_MEASURE_OPERATORS: usize = 128;
fn parse_measure(s: &str, default: Unit) -> Option<f64> {
let s = s.trim();
// Simple arithmetic on the right (Illustrator fields accept "10+5", "100/2", "3in*2").
+2 -1
View File
@@ -6589,7 +6589,8 @@ report warn about it).
Commands take and return lengths in points (the distances of `object.path.offsetPath`, `object.path.simplify`,
`object.path.splitIntoGrid` and `text.setStyle` `size`/`leading` also take a string with a unit, `"5 mm"`). A length
string takes the same arithmetic as the desktop app's fields, where `%` is a share of the value before it:
`"200mm*50%"` is 100 mm and `"50pt+25%"` is 62.5 pt. The units
`"200mm*50%"` is 100 mm and `"50pt+25%"` is 62.5 pt. A length string with more than 128 `+ - * /` signs is not read
as a length. The units
only change what the UI shows and how it reads typed numbers. Preferences ▸ Units has
four: General (rulers, positions and sizes, the Info panel, dialog distances, canvas measurement labels), Stroke
(weights, dashes), Type (font size, leading, baseline shift) and East Asian Type (no field uses it yet). General is the active document's units