compare versions as strings rather than parsing them to ints

Parsing version numbers to u64s could cause an panic on int overflow
with very large versions.
This commit is contained in:
John Turner
2025-11-19 05:01:34 +00:00
parent 8d3cf7c83d
commit 0d40608404

View File

@@ -185,6 +185,28 @@ impl Atom {
}
}
impl VersionNumber {
#[must_use]
pub fn cmp_as_ints(&self, other: &Self) -> Ordering {
let a = self.get().trim_start_matches('0');
let b = other.get().trim_start_matches('0');
a.len().cmp(&b.len()).then_with(|| a.cmp(b))
}
#[must_use]
pub fn cmp_as_str(&self, other: &Self) -> Ordering {
if self.get().starts_with('0') || other.get().starts_with('0') {
let a = self.get().trim_end_matches('0');
let b = other.get().trim_end_matches('0');
a.cmp(b)
} else {
self.cmp_as_ints(other)
}
}
}
impl PartialEq for VersionSuffix {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
@@ -212,11 +234,7 @@ impl Ord for VersionSuffix {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => match (&self.number, &other.number) {
(Some(a), Some(b)) => {
a.0.parse::<u64>()
.unwrap()
.cmp(&b.0.parse::<u64>().unwrap())
}
(Some(a), Some(b)) => a.cmp_as_ints(b),
(Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal,
(Some(_), None) => Ordering::Greater,
@@ -276,26 +294,21 @@ impl Ord for VersionSuffixes {
impl PartialEq for VersionNumbers {
fn eq(&self, other: &Self) -> bool {
self.get().first().unwrap().get().parse::<u64>().unwrap()
== other.get().first().unwrap().get().parse().unwrap()
self.get()
.first()
.unwrap()
.cmp_as_ints(other.get().first().unwrap())
== Ordering::Equal
&& {
let mut a = self.get().iter().skip(1);
let mut b = other.get().iter().skip(1);
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a.get().starts_with('0') => {
let a = a.get().trim_end_matches('0');
let b = b.get().trim_end_matches('0');
if a != b {
break false;
}
}
(Some(a), Some(b)) => {
if a.get().parse::<u64>().unwrap() != b.get().parse::<u64>().unwrap() {
break false;
}
let Ordering::Equal = a.cmp_as_str(b) else {
return false;
};
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => (),
(None, Some(b)) if b.get().chars().all(|c| c == '0') => (),
@@ -321,10 +334,7 @@ impl Ord for VersionNumbers {
.get()
.first()
.unwrap()
.get()
.parse::<u64>()
.unwrap()
.cmp(&other.get().first().unwrap().get().parse::<u64>().unwrap())
.cmp_as_ints(other.get().first().unwrap())
{
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
@@ -334,24 +344,7 @@ impl Ord for VersionNumbers {
loop {
match (a.next(), b.next()) {
(Some(a), Some(b))
if a.get().starts_with('0') || b.get().starts_with('0') =>
{
let a = a.get().trim_end_matches('0');
let b = b.get().trim_end_matches('0');
match a.cmp(b) {
Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater,
Ordering::Equal => (),
}
}
(Some(a), Some(b)) => match a
.get()
.parse::<u64>()
.unwrap()
.cmp(&b.get().parse::<u64>().unwrap())
{
(Some(a), Some(b)) => match a.cmp_as_str(b) {
Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater,
Ordering::Equal => (),
@@ -373,18 +366,14 @@ impl PartialEq for Version {
&& self.suffixes == other.suffixes
&& self.letter == other.letter
&& match (&self.rev, &other.rev) {
(Some(a), Some(b)) => {
a.get().parse::<u64>().unwrap() == b.get().parse::<u64>().unwrap()
}
(Some(a), Some(b)) => matches!(a.cmp_as_ints(b), Ordering::Equal),
(Some(a), None) if a.get().chars().all(|c| c == '0') => true,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => true,
(Some(_), None) | (None, Some(_)) => false,
(None, None) => true,
}
&& match (&self.build_id, &other.build_id) {
(Some(a), Some(b)) => {
a.get().parse::<u64>().unwrap() == b.get().parse::<u64>().unwrap()
}
(Some(a), Some(b)) => matches!(a.cmp_as_ints(b), Ordering::Equal),
(Some(_), None) | (None, Some(_)) => false,
(None, None) => true,
}
@@ -424,12 +413,7 @@ impl Ord for Version {
}
match (&self.rev, &other.rev) {
(Some(a), Some(b)) => match a
.get()
.parse::<u64>()
.unwrap()
.cmp(&b.get().parse().unwrap())
{
(Some(a), Some(b)) => match a.cmp_as_ints(b) {
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => (),
@@ -442,11 +426,7 @@ impl Ord for Version {
}
match (&self.build_id, &other.build_id) {
(Some(a), Some(b)) => a
.get()
.parse::<u64>()
.unwrap()
.cmp(&b.get().parse::<u64>().unwrap()),
(Some(a), Some(b)) => a.cmp_as_ints(b),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,