diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/semver/src | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/semver/src/backport.rs | 23 | ||||
-rw-r--r-- | third_party/rust/semver/src/display.rs | 165 | ||||
-rw-r--r-- | third_party/rust/semver/src/error.rs | 124 | ||||
-rw-r--r-- | third_party/rust/semver/src/eval.rs | 181 | ||||
-rw-r--r-- | third_party/rust/semver/src/identifier.rs | 422 | ||||
-rw-r--r-- | third_party/rust/semver/src/impls.rs | 156 | ||||
-rw-r--r-- | third_party/rust/semver/src/lib.rs | 533 | ||||
-rw-r--r-- | third_party/rust/semver/src/parse.rs | 405 | ||||
-rw-r--r-- | third_party/rust/semver/src/serde.rs | 109 |
9 files changed, 2118 insertions, 0 deletions
diff --git a/third_party/rust/semver/src/backport.rs b/third_party/rust/semver/src/backport.rs new file mode 100644 index 0000000000..b5e1d02be5 --- /dev/null +++ b/third_party/rust/semver/src/backport.rs @@ -0,0 +1,23 @@ +#[cfg(no_str_strip_prefix)] // rustc <1.45 +pub(crate) trait StripPrefixExt { + fn strip_prefix(&self, ch: char) -> Option<&str>; +} + +#[cfg(no_str_strip_prefix)] +impl StripPrefixExt for str { + fn strip_prefix(&self, ch: char) -> Option<&str> { + if self.starts_with(ch) { + Some(&self[ch.len_utf8()..]) + } else { + None + } + } +} + +pub(crate) use crate::alloc::vec::Vec; + +#[cfg(no_alloc_crate)] // rustc <1.36 +pub(crate) mod alloc { + pub use std::alloc; + pub use std::vec; +} diff --git a/third_party/rust/semver/src/display.rs b/third_party/rust/semver/src/display.rs new file mode 100644 index 0000000000..3c2871bb97 --- /dev/null +++ b/third_party/rust/semver/src/display.rs @@ -0,0 +1,165 @@ +use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq}; +use core::fmt::{self, Alignment, Debug, Display, Write}; + +impl Display for Version { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let do_display = |formatter: &mut fmt::Formatter| -> fmt::Result { + write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)?; + if !self.pre.is_empty() { + write!(formatter, "-{}", self.pre)?; + } + if !self.build.is_empty() { + write!(formatter, "+{}", self.build)?; + } + Ok(()) + }; + + let do_len = || -> usize { + digits(self.major) + + 1 + + digits(self.minor) + + 1 + + digits(self.patch) + + !self.pre.is_empty() as usize + + self.pre.len() + + !self.build.is_empty() as usize + + self.build.len() + }; + + pad(formatter, do_display, do_len) + } +} + +impl Display for VersionReq { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + if self.comparators.is_empty() { + return formatter.write_str("*"); + } + for (i, comparator) in self.comparators.iter().enumerate() { + if i > 0 { + formatter.write_str(", ")?; + } + write!(formatter, "{}", comparator)?; + } + Ok(()) + } +} + +impl Display for Comparator { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let op = match self.op { + Op::Exact => "=", + Op::Greater => ">", + Op::GreaterEq => ">=", + Op::Less => "<", + Op::LessEq => "<=", + Op::Tilde => "~", + Op::Caret => "^", + Op::Wildcard => "", + #[cfg(no_non_exhaustive)] + Op::__NonExhaustive => unreachable!(), + }; + formatter.write_str(op)?; + write!(formatter, "{}", self.major)?; + if let Some(minor) = &self.minor { + write!(formatter, ".{}", minor)?; + if let Some(patch) = &self.patch { + write!(formatter, ".{}", patch)?; + if !self.pre.is_empty() { + write!(formatter, "-{}", self.pre)?; + } + } else if self.op == Op::Wildcard { + formatter.write_str(".*")?; + } + } else if self.op == Op::Wildcard { + formatter.write_str(".*")?; + } + Ok(()) + } +} + +impl Display for Prerelease { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +impl Display for BuildMetadata { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +impl Debug for Version { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let mut debug = formatter.debug_struct("Version"); + debug + .field("major", &self.major) + .field("minor", &self.minor) + .field("patch", &self.patch); + if !self.pre.is_empty() { + debug.field("pre", &self.pre); + } + if !self.build.is_empty() { + debug.field("build", &self.build); + } + debug.finish() + } +} + +impl Debug for Prerelease { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "Prerelease(\"{}\")", self) + } +} + +impl Debug for BuildMetadata { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "BuildMetadata(\"{}\")", self) + } +} + +fn pad( + formatter: &mut fmt::Formatter, + do_display: impl FnOnce(&mut fmt::Formatter) -> fmt::Result, + do_len: impl FnOnce() -> usize, +) -> fmt::Result { + let min_width = match formatter.width() { + Some(min_width) => min_width, + None => return do_display(formatter), + }; + + let len = do_len(); + if len >= min_width { + return do_display(formatter); + } + + let default_align = Alignment::Left; + let align = formatter.align().unwrap_or(default_align); + let padding = min_width - len; + let (pre_pad, post_pad) = match align { + Alignment::Left => (0, padding), + Alignment::Right => (padding, 0), + Alignment::Center => (padding / 2, (padding + 1) / 2), + }; + + let fill = formatter.fill(); + for _ in 0..pre_pad { + formatter.write_char(fill)?; + } + + do_display(formatter)?; + + for _ in 0..post_pad { + formatter.write_char(fill)?; + } + Ok(()) +} + +fn digits(val: u64) -> usize { + if val < 10 { + 1 + } else { + 1 + digits(val / 10) + } +} diff --git a/third_party/rust/semver/src/error.rs b/third_party/rust/semver/src/error.rs new file mode 100644 index 0000000000..a514e3f11e --- /dev/null +++ b/third_party/rust/semver/src/error.rs @@ -0,0 +1,124 @@ +use crate::parse::Error; +use core::fmt::{self, Debug, Display}; + +pub(crate) enum ErrorKind { + UnexpectedEnd(Position), + UnexpectedChar(Position, char), + UnexpectedCharAfter(Position, char), + ExpectedCommaFound(Position, char), + LeadingZero(Position), + Overflow(Position), + EmptySegment(Position), + IllegalCharacter(Position), + WildcardNotTheOnlyComparator(char), + UnexpectedAfterWildcard, + ExcessiveComparators, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub(crate) enum Position { + Major, + Minor, + Patch, + Pre, + Build, +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match &self.kind { + ErrorKind::UnexpectedEnd(pos) => { + write!(formatter, "unexpected end of input while parsing {}", pos) + } + ErrorKind::UnexpectedChar(pos, ch) => { + write!( + formatter, + "unexpected character {} while parsing {}", + QuotedChar(*ch), + pos, + ) + } + ErrorKind::UnexpectedCharAfter(pos, ch) => { + write!( + formatter, + "unexpected character {} after {}", + QuotedChar(*ch), + pos, + ) + } + ErrorKind::ExpectedCommaFound(pos, ch) => { + write!( + formatter, + "expected comma after {}, found {}", + pos, + QuotedChar(*ch), + ) + } + ErrorKind::LeadingZero(pos) => { + write!(formatter, "invalid leading zero in {}", pos) + } + ErrorKind::Overflow(pos) => { + write!(formatter, "value of {} exceeds u64::MAX", pos) + } + ErrorKind::EmptySegment(pos) => { + write!(formatter, "empty identifier segment in {}", pos) + } + ErrorKind::IllegalCharacter(pos) => { + write!(formatter, "unexpected character in {}", pos) + } + ErrorKind::WildcardNotTheOnlyComparator(ch) => { + write!( + formatter, + "wildcard req ({}) must be the only comparator in the version req", + ch, + ) + } + ErrorKind::UnexpectedAfterWildcard => { + formatter.write_str("unexpected character after wildcard in version req") + } + ErrorKind::ExcessiveComparators => { + formatter.write_str("excessive number of version comparators") + } + } + } +} + +impl Display for Position { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(match self { + Position::Major => "major version number", + Position::Minor => "minor version number", + Position::Patch => "patch version number", + Position::Pre => "pre-release identifier", + Position::Build => "build metadata", + }) + } +} + +impl Debug for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Error(\"")?; + Display::fmt(self, formatter)?; + formatter.write_str("\")")?; + Ok(()) + } +} + +struct QuotedChar(char); + +impl Display for QuotedChar { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + // Standard library versions prior to https://github.com/rust-lang/rust/pull/95345 + // print character 0 as '\u{0}'. We prefer '\0' to keep error messages + // the same across all supported Rust versions. + if self.0 == '\0' { + formatter.write_str("'\\0'") + } else { + write!(formatter, "{:?}", self.0) + } + } +} diff --git a/third_party/rust/semver/src/eval.rs b/third_party/rust/semver/src/eval.rs new file mode 100644 index 0000000000..e6e38949a9 --- /dev/null +++ b/third_party/rust/semver/src/eval.rs @@ -0,0 +1,181 @@ +use crate::{Comparator, Op, Version, VersionReq}; + +pub(crate) fn matches_req(req: &VersionReq, ver: &Version) -> bool { + for cmp in &req.comparators { + if !matches_impl(cmp, ver) { + return false; + } + } + + if ver.pre.is_empty() { + return true; + } + + // If a version has a prerelease tag (for example, 1.2.3-alpha.3) then it + // will only be allowed to satisfy req if at least one comparator with the + // same major.minor.patch also has a prerelease tag. + for cmp in &req.comparators { + if pre_is_compatible(cmp, ver) { + return true; + } + } + + false +} + +pub(crate) fn matches_comparator(cmp: &Comparator, ver: &Version) -> bool { + matches_impl(cmp, ver) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver)) +} + +fn matches_impl(cmp: &Comparator, ver: &Version) -> bool { + match cmp.op { + Op::Exact | Op::Wildcard => matches_exact(cmp, ver), + Op::Greater => matches_greater(cmp, ver), + Op::GreaterEq => matches_exact(cmp, ver) || matches_greater(cmp, ver), + Op::Less => matches_less(cmp, ver), + Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver), + Op::Tilde => matches_tilde(cmp, ver), + Op::Caret => matches_caret(cmp, ver), + #[cfg(no_non_exhaustive)] + Op::__NonExhaustive => unreachable!(), + } +} + +fn matches_exact(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return false; + } + + if let Some(minor) = cmp.minor { + if ver.minor != minor { + return false; + } + } + + if let Some(patch) = cmp.patch { + if ver.patch != patch { + return false; + } + } + + ver.pre == cmp.pre +} + +fn matches_greater(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return ver.major > cmp.major; + } + + match cmp.minor { + None => return false, + Some(minor) => { + if ver.minor != minor { + return ver.minor > minor; + } + } + } + + match cmp.patch { + None => return false, + Some(patch) => { + if ver.patch != patch { + return ver.patch > patch; + } + } + } + + ver.pre > cmp.pre +} + +fn matches_less(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return ver.major < cmp.major; + } + + match cmp.minor { + None => return false, + Some(minor) => { + if ver.minor != minor { + return ver.minor < minor; + } + } + } + + match cmp.patch { + None => return false, + Some(patch) => { + if ver.patch != patch { + return ver.patch < patch; + } + } + } + + ver.pre < cmp.pre +} + +fn matches_tilde(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return false; + } + + if let Some(minor) = cmp.minor { + if ver.minor != minor { + return false; + } + } + + if let Some(patch) = cmp.patch { + if ver.patch != patch { + return ver.patch > patch; + } + } + + ver.pre >= cmp.pre +} + +fn matches_caret(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return false; + } + + let minor = match cmp.minor { + None => return true, + Some(minor) => minor, + }; + + let patch = match cmp.patch { + None => { + if cmp.major > 0 { + return ver.minor >= minor; + } else { + return ver.minor == minor; + } + } + Some(patch) => patch, + }; + + if cmp.major > 0 { + if ver.minor != minor { + return ver.minor > minor; + } else if ver.patch != patch { + return ver.patch > patch; + } + } else if minor > 0 { + if ver.minor != minor { + return false; + } else if ver.patch != patch { + return ver.patch > patch; + } + } else if ver.minor != minor || ver.patch != patch { + return false; + } + + ver.pre >= cmp.pre +} + +fn pre_is_compatible(cmp: &Comparator, ver: &Version) -> bool { + cmp.major == ver.major + && cmp.minor == Some(ver.minor) + && cmp.patch == Some(ver.patch) + && !cmp.pre.is_empty() +} diff --git a/third_party/rust/semver/src/identifier.rs b/third_party/rust/semver/src/identifier.rs new file mode 100644 index 0000000000..0273ae62a8 --- /dev/null +++ b/third_party/rust/semver/src/identifier.rs @@ -0,0 +1,422 @@ +// This module implements Identifier, a short-optimized string allowed to +// contain only the ASCII characters hyphen, dot, 0-9, A-Z, a-z. +// +// As of mid-2021, the distribution of pre-release lengths on crates.io is: +// +// length count length count length count +// 0 355929 11 81 24 2 +// 1 208 12 48 25 6 +// 2 236 13 55 26 10 +// 3 1909 14 25 27 4 +// 4 1284 15 15 28 1 +// 5 1742 16 35 30 1 +// 6 3440 17 9 31 5 +// 7 5624 18 6 32 1 +// 8 1321 19 12 36 2 +// 9 179 20 2 37 379 +// 10 65 23 11 +// +// and the distribution of build metadata lengths is: +// +// length count length count length count +// 0 364445 8 7725 18 1 +// 1 72 9 16 19 1 +// 2 7 10 85 20 1 +// 3 28 11 17 22 4 +// 4 9 12 10 26 1 +// 5 68 13 9 27 1 +// 6 73 14 10 40 5 +// 7 53 15 6 +// +// Therefore it really behooves us to be able to use the entire 8 bytes of a +// pointer for inline storage. For both pre-release and build metadata there are +// vastly more strings with length exactly 8 bytes than the sum over all lengths +// longer than 8 bytes. +// +// To differentiate the inline representation from the heap allocated long +// representation, we'll allocate heap pointers with 2-byte alignment so that +// they are guaranteed to have an unset least significant bit. Then in the repr +// we store for pointers, we rotate a 1 into the most significant bit of the +// most significant byte, which is never set for an ASCII byte. +// +// Inline repr: +// +// 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx +// +// Heap allocated repr: +// +// 1ppppppp pppppppp pppppppp pppppppp pppppppp pppppppp pppppppp pppppppp 0 +// ^ most significant bit least significant bit of orig ptr, rotated out ^ +// +// Since the most significant bit doubles as a sign bit for the similarly sized +// signed integer type, the CPU has an efficient instruction for inspecting it, +// meaning we can differentiate between an inline repr and a heap allocated repr +// in one instruction. Effectively an inline repr always looks like a positive +// i64 while a heap allocated repr always looks like a negative i64. +// +// For the inline repr, we store \0 padding on the end of the stored characters, +// and thus the string length is readily determined efficiently by a cttz (count +// trailing zeros) or bsf (bit scan forward) instruction. +// +// For the heap allocated repr, the length is encoded as a base-128 varint at +// the head of the allocation. +// +// Empty strings are stored as an all-1 bit pattern, corresponding to -1i64. +// Consequently the all-0 bit pattern is never a legal representation in any +// repr, leaving it available as a niche for downstream code. For example this +// allows size_of::<Version>() == size_of::<Option<Version>>(). + +use crate::alloc::alloc::{alloc, dealloc, handle_alloc_error, Layout}; +use core::isize; +use core::mem; +use core::num::{NonZeroU64, NonZeroUsize}; +use core::ptr::{self, NonNull}; +use core::slice; +use core::str; +use core::usize; + +const PTR_BYTES: usize = mem::size_of::<NonNull<u8>>(); + +// If pointers are already 8 bytes or bigger, then 0. If pointers are smaller +// than 8 bytes, then Identifier will contain a byte array to raise its size up +// to 8 bytes total. +const TAIL_BYTES: usize = 8 * (PTR_BYTES < 8) as usize - PTR_BYTES * (PTR_BYTES < 8) as usize; + +#[repr(C, align(8))] +pub(crate) struct Identifier { + head: NonNull<u8>, + tail: [u8; TAIL_BYTES], +} + +impl Identifier { + pub(crate) const fn empty() -> Self { + // This is a separate constant because unsafe function calls are not + // allowed in a const fn body, only in a const, until later rustc than + // what we support. + const HEAD: NonNull<u8> = unsafe { NonNull::new_unchecked(!0 as *mut u8) }; + + // `mov rax, -1` + Identifier { + head: HEAD, + tail: [!0; TAIL_BYTES], + } + } + + // SAFETY: string must be ASCII and not contain \0 bytes. + pub(crate) unsafe fn new_unchecked(string: &str) -> Self { + let len = string.len(); + debug_assert!(len <= isize::MAX as usize); + match len as u64 { + 0 => Self::empty(), + 1..=8 => { + let mut bytes = [0u8; mem::size_of::<Identifier>()]; + // SAFETY: string is big enough to read len bytes, bytes is big + // enough to write len bytes, and they do not overlap. + unsafe { ptr::copy_nonoverlapping(string.as_ptr(), bytes.as_mut_ptr(), len) }; + // SAFETY: the head field is nonzero because the input string + // was at least 1 byte of ASCII and did not contain \0. + unsafe { mem::transmute::<[u8; mem::size_of::<Identifier>()], Identifier>(bytes) } + } + 9..=0xff_ffff_ffff_ffff => { + // SAFETY: len is in a range that does not contain 0. + let size = bytes_for_varint(unsafe { NonZeroUsize::new_unchecked(len) }) + len; + let align = 2; + // On 32-bit and 16-bit architecture, check for size overflowing + // isize::MAX. Making an allocation request bigger than this to + // the allocator is considered UB. All allocations (including + // static ones) are limited to isize::MAX so we're guaranteed + // len <= isize::MAX, and we know bytes_for_varint(len) <= 5 + // because 128**5 > isize::MAX, which means the only problem + // that can arise is when isize::MAX - 5 <= len <= isize::MAX. + // This is pretty much guaranteed to be malicious input so we + // don't need to care about returning a good error message. + if mem::size_of::<usize>() < 8 { + let max_alloc = usize::MAX / 2 - align; + assert!(size <= max_alloc); + } + // SAFETY: align is not zero, align is a power of two, and + // rounding size up to align does not overflow isize::MAX. + let layout = unsafe { Layout::from_size_align_unchecked(size, align) }; + // SAFETY: layout's size is nonzero. + let ptr = unsafe { alloc(layout) }; + if ptr.is_null() { + handle_alloc_error(layout); + } + let mut write = ptr; + let mut varint_remaining = len; + while varint_remaining > 0 { + // SAFETY: size is bytes_for_varint(len) bytes + len bytes. + // This is writing the first bytes_for_varint(len) bytes. + unsafe { ptr::write(write, varint_remaining as u8 | 0x80) }; + varint_remaining >>= 7; + // SAFETY: still in bounds of the same allocation. + write = unsafe { write.add(1) }; + } + // SAFETY: size is bytes_for_varint(len) bytes + len bytes. This + // is writing to the last len bytes. + unsafe { ptr::copy_nonoverlapping(string.as_ptr(), write, len) }; + Identifier { + head: ptr_to_repr(ptr), + tail: [0; TAIL_BYTES], + } + } + 0x100_0000_0000_0000..=0xffff_ffff_ffff_ffff => { + unreachable!("please refrain from storing >64 petabytes of text in semver version"); + } + #[cfg(no_exhaustive_int_match)] // rustc <1.33 + _ => unreachable!(), + } + } + + pub(crate) fn is_empty(&self) -> bool { + // `cmp rdi, -1` -- basically: `repr as i64 == -1` + let empty = Self::empty(); + let is_empty = self.head == empty.head && self.tail == empty.tail; + // The empty representation does nothing on Drop. We can't let this one + // drop normally because `impl Drop for Identifier` calls is_empty; that + // would be an infinite recursion. + mem::forget(empty); + is_empty + } + + fn is_inline(&self) -> bool { + // `test rdi, rdi` -- basically: `repr as i64 >= 0` + self.head.as_ptr() as usize >> (PTR_BYTES * 8 - 1) == 0 + } + + fn is_empty_or_inline(&self) -> bool { + // `cmp rdi, -2` -- basically: `repr as i64 > -2` + self.is_empty() || self.is_inline() + } + + pub(crate) fn as_str(&self) -> &str { + if self.is_empty() { + "" + } else if self.is_inline() { + // SAFETY: repr is in the inline representation. + unsafe { inline_as_str(self) } + } else { + // SAFETY: repr is in the heap allocated representation. + unsafe { ptr_as_str(&self.head) } + } + } +} + +impl Clone for Identifier { + fn clone(&self) -> Self { + if self.is_empty_or_inline() { + Identifier { + head: self.head, + tail: self.tail, + } + } else { + let ptr = repr_to_ptr(self.head); + // SAFETY: ptr is one of our own heap allocations. + let len = unsafe { decode_len(ptr) }; + let size = bytes_for_varint(len) + len.get(); + let align = 2; + // SAFETY: align is not zero, align is a power of two, and rounding + // size up to align does not overflow isize::MAX. This is just + // duplicating a previous allocation where all of these guarantees + // were already made. + let layout = unsafe { Layout::from_size_align_unchecked(size, align) }; + // SAFETY: layout's size is nonzero. + let clone = unsafe { alloc(layout) }; + if clone.is_null() { + handle_alloc_error(layout); + } + // SAFETY: new allocation cannot overlap the previous one (this was + // not a realloc). The argument ptrs are readable/writeable + // respectively for size bytes. + unsafe { ptr::copy_nonoverlapping(ptr, clone, size) } + Identifier { + head: ptr_to_repr(clone), + tail: [0; TAIL_BYTES], + } + } + } +} + +impl Drop for Identifier { + fn drop(&mut self) { + if self.is_empty_or_inline() { + return; + } + let ptr = repr_to_ptr_mut(self.head); + // SAFETY: ptr is one of our own heap allocations. + let len = unsafe { decode_len(ptr) }; + let size = bytes_for_varint(len) + len.get(); + let align = 2; + // SAFETY: align is not zero, align is a power of two, and rounding + // size up to align does not overflow usize::MAX. These guarantees were + // made when originally allocating this memory. + let layout = unsafe { Layout::from_size_align_unchecked(size, align) }; + // SAFETY: ptr was previously allocated by the same allocator with the + // same layout. + unsafe { dealloc(ptr, layout) } + } +} + +impl PartialEq for Identifier { + fn eq(&self, rhs: &Self) -> bool { + if self.is_empty_or_inline() { + // Fast path (most common) + self.head == rhs.head && self.tail == rhs.tail + } else if rhs.is_empty_or_inline() { + false + } else { + // SAFETY: both reprs are in the heap allocated representation. + unsafe { ptr_as_str(&self.head) == ptr_as_str(&rhs.head) } + } + } +} + +unsafe impl Send for Identifier {} +unsafe impl Sync for Identifier {} + +// We use heap pointers that are 2-byte aligned, meaning they have an +// insignificant 0 in the least significant bit. We take advantage of that +// unneeded bit to rotate a 1 into the most significant bit to make the repr +// distinguishable from ASCII bytes. +fn ptr_to_repr(original: *mut u8) -> NonNull<u8> { + // `mov eax, 1` + // `shld rax, rdi, 63` + let modified = (original as usize | 1).rotate_right(1); + + // `original + (modified - original)`, but being mindful of provenance. + let diff = modified.wrapping_sub(original as usize); + let modified = original.wrapping_add(diff); + + // SAFETY: the most significant bit of repr is known to be set, so the value + // is not zero. + unsafe { NonNull::new_unchecked(modified) } +} + +// Shift out the 1 previously placed into the most significant bit of the least +// significant byte. Shift in a low 0 bit to reconstruct the original 2-byte +// aligned pointer. +fn repr_to_ptr(modified: NonNull<u8>) -> *const u8 { + // `lea rax, [rdi + rdi]` + let modified = modified.as_ptr(); + let original = (modified as usize) << 1; + + // `modified + (original - modified)`, but being mindful of provenance. + let diff = original.wrapping_sub(modified as usize); + modified.wrapping_add(diff) +} + +fn repr_to_ptr_mut(repr: NonNull<u8>) -> *mut u8 { + repr_to_ptr(repr) as *mut u8 +} + +// Compute the length of the inline string, assuming the argument is in short +// string representation. Short strings are stored as 1 to 8 nonzero ASCII +// bytes, followed by \0 padding for the remaining bytes. +// +// SAFETY: the identifier must indeed be in the inline representation. +unsafe fn inline_len(repr: &Identifier) -> NonZeroUsize { + // SAFETY: Identifier's layout is align(8) and at least size 8. We're doing + // an aligned read of the first 8 bytes from it. The bytes are not all zero + // because inline strings are at least 1 byte long and cannot contain \0. + let repr = unsafe { ptr::read(repr as *const Identifier as *const NonZeroU64) }; + + // Rustc >=1.53 has intrinsics for counting zeros on a non-zeroable integer. + // On many architectures these are more efficient than counting on ordinary + // zeroable integers (bsf vs cttz). On rustc <1.53 without those intrinsics, + // we count zeros in the u64 rather than the NonZeroU64. + #[cfg(no_nonzero_bitscan)] + let repr = repr.get(); + + #[cfg(target_endian = "little")] + let zero_bits_on_string_end = repr.leading_zeros(); + #[cfg(target_endian = "big")] + let zero_bits_on_string_end = repr.trailing_zeros(); + + let nonzero_bytes = 8 - zero_bits_on_string_end as usize / 8; + + // SAFETY: repr is nonzero, so it has at most 63 zero bits on either end, + // thus at least one nonzero byte. + unsafe { NonZeroUsize::new_unchecked(nonzero_bytes) } +} + +// SAFETY: repr must be in the inline representation, i.e. at least 1 and at +// most 8 nonzero ASCII bytes padded on the end with \0 bytes. +unsafe fn inline_as_str(repr: &Identifier) -> &str { + let ptr = repr as *const Identifier as *const u8; + let len = unsafe { inline_len(repr) }.get(); + // SAFETY: we are viewing the nonzero ASCII prefix of the inline repr's + // contents as a slice of bytes. Input/output lifetimes are correctly + // associated. + let slice = unsafe { slice::from_raw_parts(ptr, len) }; + // SAFETY: the string contents are known to be only ASCII bytes, which are + // always valid UTF-8. + unsafe { str::from_utf8_unchecked(slice) } +} + +// Decode varint. Varints consist of between one and eight base-128 digits, each +// of which is stored in a byte with most significant bit set. Adjacent to the +// varint in memory there is guaranteed to be at least 9 ASCII bytes, each of +// which has an unset most significant bit. +// +// SAFETY: ptr must be one of our own heap allocations, with the varint header +// already written. +unsafe fn decode_len(ptr: *const u8) -> NonZeroUsize { + // SAFETY: There is at least one byte of varint followed by at least 9 bytes + // of string content, which is at least 10 bytes total for the allocation, + // so reading the first two is no problem. + let [first, second] = unsafe { ptr::read(ptr as *const [u8; 2]) }; + if second < 0x80 { + // SAFETY: the length of this heap allocated string has been encoded as + // one base-128 digit, so the length is at least 9 and at most 127. It + // cannot be zero. + unsafe { NonZeroUsize::new_unchecked((first & 0x7f) as usize) } + } else { + return unsafe { decode_len_cold(ptr) }; + + // Identifiers 128 bytes or longer. This is not exercised by any crate + // version currently published to crates.io. + #[cold] + #[inline(never)] + unsafe fn decode_len_cold(mut ptr: *const u8) -> NonZeroUsize { + let mut len = 0; + let mut shift = 0; + loop { + // SAFETY: varint continues while there are bytes having the + // most significant bit set, i.e. until we start hitting the + // ASCII string content with msb unset. + let byte = unsafe { *ptr }; + if byte < 0x80 { + // SAFETY: the string length is known to be 128 bytes or + // longer. + return unsafe { NonZeroUsize::new_unchecked(len) }; + } + // SAFETY: still in bounds of the same allocation. + ptr = unsafe { ptr.add(1) }; + len += ((byte & 0x7f) as usize) << shift; + shift += 7; + } + } + } +} + +// SAFETY: repr must be in the heap allocated representation, with varint header +// and string contents already written. +unsafe fn ptr_as_str(repr: &NonNull<u8>) -> &str { + let ptr = repr_to_ptr(*repr); + let len = unsafe { decode_len(ptr) }; + let header = bytes_for_varint(len); + let slice = unsafe { slice::from_raw_parts(ptr.add(header), len.get()) }; + // SAFETY: all identifier contents are ASCII bytes, which are always valid + // UTF-8. + unsafe { str::from_utf8_unchecked(slice) } +} + +// Number of base-128 digits required for the varint representation of a length. +fn bytes_for_varint(len: NonZeroUsize) -> usize { + #[cfg(no_nonzero_bitscan)] // rustc <1.53 + let len = len.get(); + + let usize_bits = mem::size_of::<usize>() * 8; + let len_bits = usize_bits - len.leading_zeros() as usize; + (len_bits + 6) / 7 +} diff --git a/third_party/rust/semver/src/impls.rs b/third_party/rust/semver/src/impls.rs new file mode 100644 index 0000000000..c3b6c60133 --- /dev/null +++ b/third_party/rust/semver/src/impls.rs @@ -0,0 +1,156 @@ +use crate::backport::*; +use crate::identifier::Identifier; +use crate::{BuildMetadata, Comparator, Prerelease, VersionReq}; +use core::cmp::Ordering; +use core::hash::{Hash, Hasher}; +use core::iter::FromIterator; +use core::ops::Deref; + +impl Default for Identifier { + fn default() -> Self { + Identifier::empty() + } +} + +impl Eq for Identifier {} + +impl Hash for Identifier { + fn hash<H: Hasher>(&self, hasher: &mut H) { + self.as_str().hash(hasher); + } +} + +impl Deref for Prerelease { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.identifier.as_str() + } +} + +impl Deref for BuildMetadata { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.identifier.as_str() + } +} + +impl PartialOrd for Prerelease { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(Ord::cmp(self, rhs)) + } +} + +impl PartialOrd for BuildMetadata { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(Ord::cmp(self, rhs)) + } +} + +impl Ord for Prerelease { + fn cmp(&self, rhs: &Self) -> Ordering { + match self.is_empty() { + true if rhs.is_empty() => return Ordering::Equal, + // A real release compares greater than prerelease. + true => return Ordering::Greater, + // Prerelease compares less than the real release. + false if rhs.is_empty() => return Ordering::Less, + false => {} + } + + let lhs = self.as_str().split('.'); + let mut rhs = rhs.as_str().split('.'); + + for lhs in lhs { + let rhs = match rhs.next() { + // Spec: "A larger set of pre-release fields has a higher + // precedence than a smaller set, if all of the preceding + // identifiers are equal." + None => return Ordering::Greater, + Some(rhs) => rhs, + }; + + let string_cmp = || Ord::cmp(lhs, rhs); + let is_ascii_digit = |b: u8| b.is_ascii_digit(); + let ordering = match ( + lhs.bytes().all(is_ascii_digit), + rhs.bytes().all(is_ascii_digit), + ) { + // Respect numeric ordering, for example 99 < 100. Spec says: + // "Identifiers consisting of only digits are compared + // numerically." + (true, true) => Ord::cmp(&lhs.len(), &rhs.len()).then_with(string_cmp), + // Spec: "Numeric identifiers always have lower precedence than + // non-numeric identifiers." + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + // Spec: "Identifiers with letters or hyphens are compared + // lexically in ASCII sort order." + (false, false) => string_cmp(), + }; + + if ordering != Ordering::Equal { + return ordering; + } + } + + if rhs.next().is_none() { + Ordering::Equal + } else { + Ordering::Less + } + } +} + +impl Ord for BuildMetadata { + fn cmp(&self, rhs: &Self) -> Ordering { + let lhs = self.as_str().split('.'); + let mut rhs = rhs.as_str().split('.'); + + for lhs in lhs { + let rhs = match rhs.next() { + None => return Ordering::Greater, + Some(rhs) => rhs, + }; + + let is_ascii_digit = |b: u8| b.is_ascii_digit(); + let ordering = match ( + lhs.bytes().all(is_ascii_digit), + rhs.bytes().all(is_ascii_digit), + ) { + (true, true) => { + // 0 < 00 < 1 < 01 < 001 < 2 < 02 < 002 < 10 + let lhval = lhs.trim_start_matches('0'); + let rhval = rhs.trim_start_matches('0'); + Ord::cmp(&lhval.len(), &rhval.len()) + .then_with(|| Ord::cmp(lhval, rhval)) + .then_with(|| Ord::cmp(&lhs.len(), &rhs.len())) + } + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + (false, false) => Ord::cmp(lhs, rhs), + }; + + if ordering != Ordering::Equal { + return ordering; + } + } + + if rhs.next().is_none() { + Ordering::Equal + } else { + Ordering::Less + } + } +} + +impl FromIterator<Comparator> for VersionReq { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = Comparator>, + { + let comparators = Vec::from_iter(iter); + VersionReq { comparators } + } +} diff --git a/third_party/rust/semver/src/lib.rs b/third_party/rust/semver/src/lib.rs new file mode 100644 index 0000000000..32ed96d1ca --- /dev/null +++ b/third_party/rust/semver/src/lib.rs @@ -0,0 +1,533 @@ +//! [![github]](https://github.com/dtolnay/semver) [![crates-io]](https://crates.io/crates/semver) [![docs-rs]](https://docs.rs/semver) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! +//! <br> +//! +//! A parser and evaluator for Cargo's flavor of Semantic Versioning. +//! +//! Semantic Versioning (see <https://semver.org>) is a guideline for how +//! version numbers are assigned and incremented. It is widely followed within +//! the Cargo/crates.io ecosystem for Rust. +//! +//! <br> +//! +//! # Example +//! +//! ``` +//! use semver::{BuildMetadata, Prerelease, Version, VersionReq}; +//! +//! fn main() { +//! let req = VersionReq::parse(">=1.2.3, <1.8.0").unwrap(); +//! +//! // Check whether this requirement matches version 1.2.3-alpha.1 (no) +//! let version = Version { +//! major: 1, +//! minor: 2, +//! patch: 3, +//! pre: Prerelease::new("alpha.1").unwrap(), +//! build: BuildMetadata::EMPTY, +//! }; +//! assert!(!req.matches(&version)); +//! +//! // Check whether it matches 1.3.0 (yes it does) +//! let version = Version::parse("1.3.0").unwrap(); +//! assert!(req.matches(&version)); +//! } +//! ``` +//! +//! <br><br> +//! +//! # Scope of this crate +//! +//! Besides Cargo, several other package ecosystems and package managers for +//! other languages also use SemVer: RubyGems/Bundler for Ruby, npm for +//! JavaScript, Composer for PHP, CocoaPods for Objective-C... +//! +//! The `semver` crate is specifically intended to implement Cargo's +//! interpretation of Semantic Versioning. +//! +//! Where the various tools differ in their interpretation or implementation of +//! the spec, this crate follows the implementation choices made by Cargo. If +//! you are operating on version numbers from some other package ecosystem, you +//! will want to use a different semver library which is appropriate to that +//! ecosystem. +//! +//! The extent of Cargo's SemVer support is documented in the *[Specifying +//! Dependencies]* chapter of the Cargo reference. +//! +//! [Specifying Dependencies]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html + +#![doc(html_root_url = "https://docs.rs/semver/1.0.16")] +#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![cfg_attr(all(not(feature = "std"), not(no_alloc_crate)), no_std)] +#![cfg_attr(not(no_unsafe_op_in_unsafe_fn_lint), deny(unsafe_op_in_unsafe_fn))] +#![cfg_attr(no_unsafe_op_in_unsafe_fn_lint, allow(unused_unsafe))] +#![cfg_attr(no_str_strip_prefix, allow(unstable_name_collisions))] +#![allow( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::doc_markdown, + clippy::items_after_statements, + clippy::manual_map, + clippy::match_bool, + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::needless_doctest_main, + clippy::option_if_let_else, + clippy::ptr_as_ptr, + clippy::redundant_else, + clippy::semicolon_if_nothing_returned, // https://github.com/rust-lang/rust-clippy/issues/7324 + clippy::similar_names, + clippy::unnested_or_patterns, + clippy::unseparated_literal_suffix, + clippy::wildcard_imports +)] + +#[cfg(not(no_alloc_crate))] +extern crate alloc; + +mod backport; +mod display; +mod error; +mod eval; +mod identifier; +mod impls; +mod parse; + +#[cfg(feature = "serde")] +mod serde; + +use crate::alloc::vec::Vec; +use crate::identifier::Identifier; +use core::str::FromStr; + +#[allow(unused_imports)] +use crate::backport::*; + +pub use crate::parse::Error; + +/// **SemVer version** as defined by <https://semver.org>. +/// +/// # Syntax +/// +/// - The major, minor, and patch numbers may be any integer 0 through u64::MAX. +/// When representing a SemVer version as a string, each number is written as +/// a base 10 integer. For example, `1.0.119`. +/// +/// - Leading zeros are forbidden in those positions. For example `1.01.00` is +/// invalid as a SemVer version. +/// +/// - The pre-release identifier, if present, must conform to the syntax +/// documented for [`Prerelease`]. +/// +/// - The build metadata, if present, must conform to the syntax documented for +/// [`BuildMetadata`]. +/// +/// - Whitespace is not allowed anywhere in the version. +/// +/// # Total ordering +/// +/// Given any two SemVer versions, one is less than, greater than, or equal to +/// the other. Versions may be compared against one another using Rust's usual +/// comparison operators. +/// +/// - The major, minor, and patch number are compared numerically from left to +/// right, lexicographically ordered as a 3-tuple of integers. So for example +/// version `1.5.0` is less than version `1.19.0`, despite the fact that +/// "1.19.0" < "1.5.0" as ASCIIbetically compared strings and 1.19 < 1.5 +/// as real numbers. +/// +/// - When major, minor, and patch are equal, a pre-release version is +/// considered less than the ordinary release: version `1.0.0-alpha.1` is +/// less than version `1.0.0`. +/// +/// - Two pre-releases of the same major, minor, patch are compared by +/// lexicographic ordering of dot-separated components of the pre-release +/// string. +/// +/// - Identifiers consisting of only digits are compared +/// numerically: `1.0.0-pre.8` is less than `1.0.0-pre.12`. +/// +/// - Identifiers that contain a letter or hyphen are compared in ASCII sort +/// order: `1.0.0-pre12` is less than `1.0.0-pre8`. +/// +/// - Any numeric identifier is always less than any non-numeric +/// identifier: `1.0.0-pre.1` is less than `1.0.0-pre.x`. +/// +/// Example: `1.0.0-alpha` < `1.0.0-alpha.1` < `1.0.0-alpha.beta` < `1.0.0-beta` < `1.0.0-beta.2` < `1.0.0-beta.11` < `1.0.0-rc.1` < `1.0.0` +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Version { + pub major: u64, + pub minor: u64, + pub patch: u64, + pub pre: Prerelease, + pub build: BuildMetadata, +} + +/// **SemVer version requirement** describing the intersection of some version +/// comparators, such as `>=1.2.3, <1.8`. +/// +/// # Syntax +/// +/// - Either `*` (meaning "any"), or one or more comma-separated comparators. +/// +/// - A [`Comparator`] is an operator ([`Op`]) and a partial version, separated +/// by optional whitespace. For example `>=1.0.0` or `>=1.0`. +/// +/// - Build metadata is syntactically permitted on the partial versions, but is +/// completely ignored, as it's never relevant to whether any comparator +/// matches a particular version. +/// +/// - Whitespace is permitted around commas and around operators. Whitespace is +/// not permitted within a partial version, i.e. anywhere between the major +/// version number and its minor, patch, pre-release, or build metadata. +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(no_const_vec_new, derive(Default))] +pub struct VersionReq { + pub comparators: Vec<Comparator>, +} + +/// A pair of comparison operator and partial version, such as `>=1.2`. Forms +/// one piece of a VersionReq. +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct Comparator { + pub op: Op, + pub major: u64, + pub minor: Option<u64>, + /// Patch is only allowed if minor is Some. + pub patch: Option<u64>, + /// Non-empty pre-release is only allowed if patch is Some. + pub pre: Prerelease, +} + +/// SemVer comparison operator: `=`, `>`, `>=`, `<`, `<=`, `~`, `^`, `*`. +/// +/// # Op::Exact +/// -  **`=I.J.K`** — exactly the version I.J.K +/// -  **`=I.J`** — equivalent to `>=I.J.0, <I.(J+1).0` +/// -  **`=I`** — equivalent to `>=I.0.0, <(I+1).0.0` +/// +/// # Op::Greater +/// -  **`>I.J.K`** +/// -  **`>I.J`** — equivalent to `>=I.(J+1).0` +/// -  **`>I`** — equivalent to `>=(I+1).0.0` +/// +/// # Op::GreaterEq +/// -  **`>=I.J.K`** +/// -  **`>=I.J`** — equivalent to `>=I.J.0` +/// -  **`>=I`** — equivalent to `>=I.0.0` +/// +/// # Op::Less +/// -  **`<I.J.K`** +/// -  **`<I.J`** — equivalent to `<I.J.0` +/// -  **`<I`** — equivalent to `<I.0.0` +/// +/// # Op::LessEq +/// -  **`<=I.J.K`** +/// -  **`<=I.J`** — equivalent to `<I.(J+1).0` +/// -  **`<=I`** — equivalent to `<(I+1).0.0` +/// +/// # Op::Tilde ("patch" updates) +/// *Tilde requirements allow the **patch** part of the semver version (the third number) to increase.* +/// -  **`~I.J.K`** — equivalent to `>=I.J.K, <I.(J+1).0` +/// -  **`~I.J`** — equivalent to `=I.J` +/// -  **`~I`** — equivalent to `=I` +/// +/// # Op::Caret ("compatible" updates) +/// *Caret requirements allow parts that are **right of the first nonzero** part of the semver version to increase.* +/// -  **`^I.J.K`** (for I\>0) — equivalent to `>=I.J.K, <(I+1).0.0` +/// -  **`^0.J.K`** (for J\>0) — equivalent to `>=0.J.K, <0.(J+1).0` +/// -  **`^0.0.K`** — equivalent to `=0.0.K` +/// -  **`^I.J`** (for I\>0 or J\>0) — equivalent to `^I.J.0` +/// -  **`^0.0`** — equivalent to `=0.0` +/// -  **`^I`** — equivalent to `=I` +/// +/// # Op::Wildcard +/// -  **`I.J.*`** — equivalent to `=I.J` +/// -  **`I.*`** or **`I.*.*`** — equivalent to `=I` +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(not(no_non_exhaustive), non_exhaustive)] +pub enum Op { + Exact, + Greater, + GreaterEq, + Less, + LessEq, + Tilde, + Caret, + Wildcard, + + #[cfg(no_non_exhaustive)] // rustc <1.40 + #[doc(hidden)] + __NonExhaustive, +} + +/// Optional pre-release identifier on a version string. This comes after `-` in +/// a SemVer version, like `1.0.0-alpha.1` +/// +/// # Examples +/// +/// Some real world pre-release idioms drawn from crates.io: +/// +/// - **[mio]** <code>0.7.0-<b>alpha.1</b></code> — the most common style +/// for numbering pre-releases. +/// +/// - **[pest]** <code>1.0.0-<b>beta.8</b></code>, <code>1.0.0-<b>rc.0</b></code> +/// — this crate makes a distinction between betas and release +/// candidates. +/// +/// - **[sassers]** <code>0.11.0-<b>shitshow</b></code> — ???. +/// +/// - **[atomic-utils]** <code>0.0.0-<b>reserved</b></code> — a squatted +/// crate name. +/// +/// [mio]: https://crates.io/crates/mio +/// [pest]: https://crates.io/crates/pest +/// [atomic-utils]: https://crates.io/crates/atomic-utils +/// [sassers]: https://crates.io/crates/sassers +/// +/// *Tip:* Be aware that if you are planning to number your own pre-releases, +/// you should prefer to separate the numeric part from any non-numeric +/// identifiers by using a dot in between. That is, prefer pre-releases +/// `alpha.1`, `alpha.2`, etc rather than `alpha1`, `alpha2` etc. The SemVer +/// spec's rule for pre-release precedence has special treatment of numeric +/// components in the pre-release string, but only if there are no non-digit +/// characters in the same dot-separated component. So you'd have `alpha.2` < +/// `alpha.11` as intended, but `alpha11` < `alpha2`. +/// +/// # Syntax +/// +/// Pre-release strings are a series of dot separated identifiers immediately +/// following the patch version. Identifiers must comprise only ASCII +/// alphanumerics and hyphens: `0-9`, `A-Z`, `a-z`, `-`. Identifiers must not be +/// empty. Numeric identifiers must not include leading zeros. +/// +/// # Total ordering +/// +/// Pre-releases have a total order defined by the SemVer spec. It uses +/// lexicographic ordering of dot-separated components. Identifiers consisting +/// of only digits are compared numerically. Otherwise, identifiers are compared +/// in ASCII sort order. Any numeric identifier is always less than any +/// non-numeric identifier. +/// +/// Example: `alpha` < `alpha.85` < `alpha.90` < `alpha.200` < `alpha.0a` < `alpha.1a0` < `alpha.a` < `beta` +#[derive(Default, Clone, Eq, PartialEq, Hash)] +pub struct Prerelease { + identifier: Identifier, +} + +/// Optional build metadata identifier. This comes after `+` in a SemVer +/// version, as in `0.8.1+zstd.1.5.0`. +/// +/// # Examples +/// +/// Some real world build metadata idioms drawn from crates.io: +/// +/// - **[libgit2-sys]** <code>0.12.20+<b>1.1.0</b></code> — for this +/// crate, the build metadata indicates the version of the C libgit2 library +/// that the Rust crate is built against. +/// +/// - **[mashup]** <code>0.1.13+<b>deprecated</b></code> — just the word +/// "deprecated" for a crate that has been superseded by another. Eventually +/// people will take notice of this in Cargo's build output where it lists the +/// crates being compiled. +/// +/// - **[google-bigquery2]** <code>2.0.4+<b>20210327</b></code> — this +/// library is automatically generated from an official API schema, and the +/// build metadata indicates the date on which that schema was last captured. +/// +/// - **[fbthrift-git]** <code>0.0.6+<b>c7fcc0e</b></code> — this crate is +/// published from snapshots of a big company monorepo. In monorepo +/// development, there is no concept of versions, and all downstream code is +/// just updated atomically in the same commit that breaking changes to a +/// library are landed. Therefore for crates.io purposes, every published +/// version must be assumed to be incompatible with the previous. The build +/// metadata provides the source control hash of the snapshotted code. +/// +/// [libgit2-sys]: https://crates.io/crates/libgit2-sys +/// [mashup]: https://crates.io/crates/mashup +/// [google-bigquery2]: https://crates.io/crates/google-bigquery2 +/// [fbthrift-git]: https://crates.io/crates/fbthrift-git +/// +/// # Syntax +/// +/// Build metadata is a series of dot separated identifiers immediately +/// following the patch or pre-release version. Identifiers must comprise only +/// ASCII alphanumerics and hyphens: `0-9`, `A-Z`, `a-z`, `-`. Identifiers must +/// not be empty. Leading zeros *are* allowed, unlike any other place in the +/// SemVer grammar. +/// +/// # Total ordering +/// +/// Build metadata is ignored in evaluating `VersionReq`; it plays no role in +/// whether a `Version` matches any one of the comparison operators. +/// +/// However for comparing build metadatas among one another, they do have a +/// total order which is determined by lexicographic ordering of dot-separated +/// components. Identifiers consisting of only digits are compared numerically. +/// Otherwise, identifiers are compared in ASCII sort order. Any numeric +/// identifier is always less than any non-numeric identifier. +/// +/// Example: `demo` < `demo.85` < `demo.90` < `demo.090` < `demo.200` < `demo.1a0` < `demo.a` < `memo` +#[derive(Default, Clone, Eq, PartialEq, Hash)] +pub struct BuildMetadata { + identifier: Identifier, +} + +impl Version { + /// Create `Version` with an empty pre-release and build metadata. + /// + /// Equivalent to: + /// + /// ``` + /// # use semver::{BuildMetadata, Prerelease, Version}; + /// # + /// # const fn new(major: u64, minor: u64, patch: u64) -> Version { + /// Version { + /// major, + /// minor, + /// patch, + /// pre: Prerelease::EMPTY, + /// build: BuildMetadata::EMPTY, + /// } + /// # } + /// ``` + pub const fn new(major: u64, minor: u64, patch: u64) -> Self { + Version { + major, + minor, + patch, + pre: Prerelease::EMPTY, + build: BuildMetadata::EMPTY, + } + } + + /// Create `Version` by parsing from string representation. + /// + /// # Errors + /// + /// Possible reasons for the parse to fail include: + /// + /// - `1.0` — too few numeric components. A SemVer version must have + /// exactly three. If you are looking at something that has fewer than + /// three numbers in it, it's possible it is a `VersionReq` instead (with + /// an implicit default `^` comparison operator). + /// + /// - `1.0.01` — a numeric component has a leading zero. + /// + /// - `1.0.unknown` — unexpected character in one of the components. + /// + /// - `1.0.0-` or `1.0.0+` — the pre-release or build metadata are + /// indicated present but empty. + /// + /// - `1.0.0-alpha_123` — pre-release or build metadata have something + /// outside the allowed characters, which are `0-9`, `A-Z`, `a-z`, `-`, + /// and `.` (dot). + /// + /// - `23456789999999999999.0.0` — overflow of a u64. + pub fn parse(text: &str) -> Result<Self, Error> { + Version::from_str(text) + } +} + +impl VersionReq { + /// A `VersionReq` with no constraint on the version numbers it matches. + /// Equivalent to `VersionReq::parse("*").unwrap()`. + /// + /// In terms of comparators this is equivalent to `>=0.0.0`. + /// + /// Counterintuitively a `*` VersionReq does not match every possible + /// version number. In particular, in order for *any* `VersionReq` to match + /// a pre-release version, the `VersionReq` must contain at least one + /// `Comparator` that has an explicit major, minor, and patch version + /// identical to the pre-release being matched, and that has a nonempty + /// pre-release component. Since `*` is not written with an explicit major, + /// minor, and patch version, and does not contain a nonempty pre-release + /// component, it does not match any pre-release versions. + #[cfg(not(no_const_vec_new))] // rustc <1.39 + pub const STAR: Self = VersionReq { + comparators: Vec::new(), + }; + + /// Create `VersionReq` by parsing from string representation. + /// + /// # Errors + /// + /// Possible reasons for the parse to fail include: + /// + /// - `>a.b` — unexpected characters in the partial version. + /// + /// - `@1.0.0` — unrecognized comparison operator. + /// + /// - `^1.0.0, ` — unexpected end of input. + /// + /// - `>=1.0 <2.0` — missing comma between comparators. + /// + /// - `*.*` — unsupported wildcard syntax. + pub fn parse(text: &str) -> Result<Self, Error> { + VersionReq::from_str(text) + } + + /// Evaluate whether the given `Version` satisfies the version requirement + /// described by `self`. + pub fn matches(&self, version: &Version) -> bool { + eval::matches_req(self, version) + } +} + +/// The default VersionReq is the same as [`VersionReq::STAR`]. +#[cfg(not(no_const_vec_new))] +impl Default for VersionReq { + fn default() -> Self { + VersionReq::STAR + } +} + +impl Comparator { + pub fn parse(text: &str) -> Result<Self, Error> { + Comparator::from_str(text) + } + + pub fn matches(&self, version: &Version) -> bool { + eval::matches_comparator(self, version) + } +} + +impl Prerelease { + pub const EMPTY: Self = Prerelease { + identifier: Identifier::empty(), + }; + + pub fn new(text: &str) -> Result<Self, Error> { + Prerelease::from_str(text) + } + + pub fn as_str(&self) -> &str { + self.identifier.as_str() + } + + pub fn is_empty(&self) -> bool { + self.identifier.is_empty() + } +} + +impl BuildMetadata { + pub const EMPTY: Self = BuildMetadata { + identifier: Identifier::empty(), + }; + + pub fn new(text: &str) -> Result<Self, Error> { + BuildMetadata::from_str(text) + } + + pub fn as_str(&self) -> &str { + self.identifier.as_str() + } + + pub fn is_empty(&self) -> bool { + self.identifier.is_empty() + } +} diff --git a/third_party/rust/semver/src/parse.rs b/third_party/rust/semver/src/parse.rs new file mode 100644 index 0000000000..6a8f6ffba4 --- /dev/null +++ b/third_party/rust/semver/src/parse.rs @@ -0,0 +1,405 @@ +use crate::backport::*; +use crate::error::{ErrorKind, Position}; +use crate::identifier::Identifier; +use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq}; +use core::str::FromStr; + +/// Error parsing a SemVer version or version requirement. +/// +/// # Example +/// +/// ``` +/// use semver::Version; +/// +/// fn main() { +/// let err = Version::parse("1.q.r").unwrap_err(); +/// +/// // "unexpected character 'q' while parsing minor version number" +/// eprintln!("{}", err); +/// } +/// ``` +pub struct Error { + pub(crate) kind: ErrorKind, +} + +impl FromStr for Version { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let mut pos = Position::Major; + let (major, text) = numeric_identifier(text, pos)?; + let text = dot(text, pos)?; + + pos = Position::Minor; + let (minor, text) = numeric_identifier(text, pos)?; + let text = dot(text, pos)?; + + pos = Position::Patch; + let (patch, text) = numeric_identifier(text, pos)?; + + if text.is_empty() { + return Ok(Version::new(major, minor, patch)); + } + + let (pre, text) = if let Some(text) = text.strip_prefix('-') { + pos = Position::Pre; + let (pre, text) = prerelease_identifier(text)?; + if pre.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (pre, text) + } else { + (Prerelease::EMPTY, text) + }; + + let (build, text) = if let Some(text) = text.strip_prefix('+') { + pos = Position::Build; + let (build, text) = build_identifier(text)?; + if build.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (build, text) + } else { + (BuildMetadata::EMPTY, text) + }; + + if let Some(unexpected) = text.chars().next() { + return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))); + } + + Ok(Version { + major, + minor, + patch, + pre, + build, + }) + } +} + +impl FromStr for VersionReq { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let text = text.trim_start_matches(' '); + if let Some((ch, text)) = wildcard(text) { + let rest = text.trim_start_matches(' '); + if rest.is_empty() { + #[cfg(not(no_const_vec_new))] + return Ok(VersionReq::STAR); + #[cfg(no_const_vec_new)] // rustc <1.39 + return Ok(VersionReq { + comparators: Vec::new(), + }); + } else if rest.starts_with(',') { + return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch))); + } else { + return Err(Error::new(ErrorKind::UnexpectedAfterWildcard)); + } + } + + let depth = 0; + let mut comparators = Vec::new(); + let len = version_req(text, &mut comparators, depth)?; + unsafe { comparators.set_len(len) } + Ok(VersionReq { comparators }) + } +} + +impl FromStr for Comparator { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let text = text.trim_start_matches(' '); + let (comparator, pos, rest) = comparator(text)?; + if !rest.is_empty() { + let unexpected = rest.chars().next().unwrap(); + return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))); + } + Ok(comparator) + } +} + +impl FromStr for Prerelease { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let (pre, rest) = prerelease_identifier(text)?; + if !rest.is_empty() { + return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre))); + } + Ok(pre) + } +} + +impl FromStr for BuildMetadata { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let (build, rest) = build_identifier(text)?; + if !rest.is_empty() { + return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build))); + } + Ok(build) + } +} + +impl Error { + fn new(kind: ErrorKind) -> Self { + Error { kind } + } +} + +impl Op { + const DEFAULT: Self = Op::Caret; +} + +fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> { + let mut len = 0; + let mut value = 0u64; + + while let Some(&digit) = input.as_bytes().get(len) { + if digit < b'0' || digit > b'9' { + break; + } + if value == 0 && len > 0 { + return Err(Error::new(ErrorKind::LeadingZero(pos))); + } + match value + .checked_mul(10) + .and_then(|value| value.checked_add((digit - b'0') as u64)) + { + Some(sum) => value = sum, + None => return Err(Error::new(ErrorKind::Overflow(pos))), + } + len += 1; + } + + if len > 0 { + Ok((value, &input[len..])) + } else if let Some(unexpected) = input[len..].chars().next() { + Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected))) + } else { + Err(Error::new(ErrorKind::UnexpectedEnd(pos))) + } +} + +fn wildcard(input: &str) -> Option<(char, &str)> { + if let Some(rest) = input.strip_prefix('*') { + Some(('*', rest)) + } else if let Some(rest) = input.strip_prefix('x') { + Some(('x', rest)) + } else if let Some(rest) = input.strip_prefix('X') { + Some(('X', rest)) + } else { + None + } +} + +fn dot(input: &str, pos: Position) -> Result<&str, Error> { + if let Some(rest) = input.strip_prefix('.') { + Ok(rest) + } else if let Some(unexpected) = input.chars().next() { + Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))) + } else { + Err(Error::new(ErrorKind::UnexpectedEnd(pos))) + } +} + +fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> { + let (string, rest) = identifier(input, Position::Pre)?; + let identifier = unsafe { Identifier::new_unchecked(string) }; + Ok((Prerelease { identifier }, rest)) +} + +fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> { + let (string, rest) = identifier(input, Position::Build)?; + let identifier = unsafe { Identifier::new_unchecked(string) }; + Ok((BuildMetadata { identifier }, rest)) +} + +fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> { + let mut accumulated_len = 0; + let mut segment_len = 0; + let mut segment_has_nondigit = false; + + loop { + match input.as_bytes().get(accumulated_len + segment_len) { + Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => { + segment_len += 1; + segment_has_nondigit = true; + } + Some(b'0'..=b'9') => { + segment_len += 1; + } + boundary => { + if segment_len == 0 { + if accumulated_len == 0 && boundary != Some(&b'.') { + return Ok(("", input)); + } else { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + } + if pos == Position::Pre + && segment_len > 1 + && !segment_has_nondigit + && input[accumulated_len..].starts_with('0') + { + return Err(Error::new(ErrorKind::LeadingZero(pos))); + } + accumulated_len += segment_len; + if boundary == Some(&b'.') { + accumulated_len += 1; + segment_len = 0; + segment_has_nondigit = false; + } else { + return Ok(input.split_at(accumulated_len)); + } + } + } + } +} + +fn op(input: &str) -> (Op, &str) { + let bytes = input.as_bytes(); + if bytes.first() == Some(&b'=') { + (Op::Exact, &input[1..]) + } else if bytes.first() == Some(&b'>') { + if bytes.get(1) == Some(&b'=') { + (Op::GreaterEq, &input[2..]) + } else { + (Op::Greater, &input[1..]) + } + } else if bytes.first() == Some(&b'<') { + if bytes.get(1) == Some(&b'=') { + (Op::LessEq, &input[2..]) + } else { + (Op::Less, &input[1..]) + } + } else if bytes.first() == Some(&b'~') { + (Op::Tilde, &input[1..]) + } else if bytes.first() == Some(&b'^') { + (Op::Caret, &input[1..]) + } else { + (Op::DEFAULT, input) + } +} + +fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> { + let (mut op, text) = op(input); + let default_op = input.len() == text.len(); + let text = text.trim_start_matches(' '); + + let mut pos = Position::Major; + let (major, text) = numeric_identifier(text, pos)?; + let mut has_wildcard = false; + + let (minor, text) = if let Some(text) = text.strip_prefix('.') { + pos = Position::Minor; + if let Some((_, text)) = wildcard(text) { + has_wildcard = true; + if default_op { + op = Op::Wildcard; + } + (None, text) + } else { + let (minor, text) = numeric_identifier(text, pos)?; + (Some(minor), text) + } + } else { + (None, text) + }; + + let (patch, text) = if let Some(text) = text.strip_prefix('.') { + pos = Position::Patch; + if let Some((_, text)) = wildcard(text) { + if default_op { + op = Op::Wildcard; + } + (None, text) + } else if has_wildcard { + return Err(Error::new(ErrorKind::UnexpectedAfterWildcard)); + } else { + let (patch, text) = numeric_identifier(text, pos)?; + (Some(patch), text) + } + } else { + (None, text) + }; + + let (pre, text) = if patch.is_some() && text.starts_with('-') { + pos = Position::Pre; + let text = &text[1..]; + let (pre, text) = prerelease_identifier(text)?; + if pre.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (pre, text) + } else { + (Prerelease::EMPTY, text) + }; + + let text = if patch.is_some() && text.starts_with('+') { + pos = Position::Build; + let text = &text[1..]; + let (build, text) = build_identifier(text)?; + if build.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + text + } else { + text + }; + + let text = text.trim_start_matches(' '); + + let comparator = Comparator { + op, + major, + minor, + patch, + pre, + }; + + Ok((comparator, pos, text)) +} + +fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> { + let (comparator, pos, text) = match comparator(input) { + Ok(success) => success, + Err(mut error) => { + if let Some((ch, mut rest)) = wildcard(input) { + rest = rest.trim_start_matches(' '); + if rest.is_empty() || rest.starts_with(',') { + error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch); + } + } + return Err(error); + } + }; + + if text.is_empty() { + out.reserve_exact(depth + 1); + unsafe { out.as_mut_ptr().add(depth).write(comparator) } + return Ok(depth + 1); + } + + let text = if let Some(text) = text.strip_prefix(',') { + text.trim_start_matches(' ') + } else { + let unexpected = text.chars().next().unwrap(); + return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected))); + }; + + const MAX_COMPARATORS: usize = 32; + if depth + 1 == MAX_COMPARATORS { + return Err(Error::new(ErrorKind::ExcessiveComparators)); + } + + // Recurse to collect parsed Comparator objects on the stack. We perform a + // single allocation to allocate exactly the right sized Vec only once the + // total number of comparators is known. + let len = version_req(text, out, depth + 1)?; + unsafe { out.as_mut_ptr().add(depth).write(comparator) } + Ok(len) +} diff --git a/third_party/rust/semver/src/serde.rs b/third_party/rust/semver/src/serde.rs new file mode 100644 index 0000000000..1fcc7d87f6 --- /dev/null +++ b/third_party/rust/semver/src/serde.rs @@ -0,0 +1,109 @@ +use crate::{Comparator, Version, VersionReq}; +use core::fmt; +use serde::de::{Deserialize, Deserializer, Error, Visitor}; +use serde::ser::{Serialize, Serializer}; + +impl Serialize for Version { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl Serialize for VersionReq { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl Serialize for Comparator { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Version { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct VersionVisitor; + + impl<'de> Visitor<'de> for VersionVisitor { + type Value = Version; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("semver version") + } + + fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> + where + E: Error, + { + string.parse().map_err(Error::custom) + } + } + + deserializer.deserialize_str(VersionVisitor) + } +} + +impl<'de> Deserialize<'de> for VersionReq { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct VersionReqVisitor; + + impl<'de> Visitor<'de> for VersionReqVisitor { + type Value = VersionReq; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("semver version") + } + + fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> + where + E: Error, + { + string.parse().map_err(Error::custom) + } + } + + deserializer.deserialize_str(VersionReqVisitor) + } +} + +impl<'de> Deserialize<'de> for Comparator { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct ComparatorVisitor; + + impl<'de> Visitor<'de> for ComparatorVisitor { + type Value = Comparator; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("semver comparator") + } + + fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> + where + E: Error, + { + string.parse().map_err(Error::custom) + } + } + + deserializer.deserialize_str(ComparatorVisitor) + } +} |