fix(bls): compare version numbers of any length

Numeric segments were parsed into a saturating u64, so any two numbers
above u64::MAX compared equal. Compare the digits instead, by length
and then digit by digit, as systemd does.
This commit is contained in:
alex authored and Alex Zenla committed 2026-09-26 18:58:54 -07:00
1 parent 29e405f11f
commit 44c8ab28af
1 file changed
+22 -5
+22 -5
View File
@@ -340,6 +340,8 @@ fn is_valid_char(c: char) -> bool {
} }
/// Compares numerical prefixes by extracting numbers. /// Compares numerical prefixes by extracting numbers.
/// Numbers are compared by their digit count first and then digit by digit,
/// which supports numbers of any length.
fn compare_numeric<I: Iterator<Item = char>>( fn compare_numeric<I: Iterator<Item = char>>(
iter_a: &mut Peekable<I>, iter_a: &mut Peekable<I>,
iter_b: &mut Peekable<I>, iter_b: &mut Peekable<I>,
@@ -347,21 +349,24 @@ fn compare_numeric<I: Iterator<Item = char>>(
let num_a = extract_number(iter_a); let num_a = extract_number(iter_a);
let num_b = extract_number(iter_b); let num_b = extract_number(iter_b);
num_a.cmp(&num_b) num_a
.len()
.cmp(&num_b.len())
.then_with(|| num_a.cmp(&num_b))
} }
/// Extracts a number from the iterator, skipping leading zeros. /// Extracts the digits of a number from the iterator, skipping leading zeros.
fn extract_number<I: Iterator<Item = char>>(iter: &mut Peekable<I>) -> u64 { fn extract_number<I: Iterator<Item = char>>(iter: &mut Peekable<I>) -> String {
// Skip leading zeros // Skip leading zeros
while let Some(&'0') = iter.peek() { while let Some(&'0') = iter.peek() {
iter.next(); iter.next();
} }
let mut num = 0u64; let mut num = String::new();
while let Some(&c) = iter.peek() { while let Some(&c) = iter.peek() {
if c.is_ascii_digit() { if c.is_ascii_digit() {
iter.next(); iter.next();
num = num.saturating_mul(10).saturating_add(c as u64 - '0' as u64); num.push(c);
} else { } else {
break; break;
} }
@@ -732,6 +737,18 @@ efi /EFI/fedora/shimx64.efi
assert_eq!(compare_versions("1.a", "1.0"), Ordering::Less); assert_eq!(compare_versions("1.a", "1.0"), Ordering::Less);
} }
#[test]
fn large_numbers_do_not_saturate() {
assert_eq!(
compare_versions("99999999999999999999", "99999999999999999998"),
Ordering::Greater
);
assert_eq!(
compare_versions("1.99999999999999999998", "1.99999999999999999999"),
Ordering::Less
);
}
#[test] #[test]
fn optional_both_none_equal() { fn optional_both_none_equal() {
assert_eq!(compare_versions_optional(None, None), Ordering::Equal); assert_eq!(compare_versions_optional(None, None), Ordering::Equal);