aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 01:20:45 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 01:20:45 +0000
commited00dd159343e31898a50b46cb849da8b2933469 (patch)
tree8d9f6d7719ba1ff0dc5cdbd5c35fea9dfaa2ef8a
parent9070be305b28eb0d6429c2ad22a53a6741456c26 (diff)
parentdab5a943946be1805fedc9c609c1a6647f156f9e (diff)
downloadsemver-android14-mainline-networking-release.tar.gz
Change-Id: I115f575a2f6b81dcdc391a441a08a928363c35d2
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.github/workflows/ci.yml61
-rw-r--r--Android.bp6
-rw-r--r--Cargo.toml12
-rw-r--r--Cargo.toml.orig14
-rw-r--r--LICENSE-APACHE25
-rw-r--r--METADATA14
-rw-r--r--README.md4
-rw-r--r--TEST_MAPPING10
-rw-r--r--build.rs10
-rw-r--r--src/backport.rs42
-rw-r--r--src/error.rs36
-rw-r--r--src/identifier.rs154
-rw-r--r--src/lib.rs4
-rw-r--r--src/parse.rs14
-rw-r--r--src/serde.rs37
-rw-r--r--tests/test_autotrait.rs14
-rw-r--r--tests/test_version.rs5
-rw-r--r--tests/test_version_req.rs2
20 files changed, 299 insertions, 168 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index a7e226d..f1fdee4 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "8d8bdb2adaf4572b3c4e8502e7498838b2cf7ccb"
+ "sha1": "1b162e8dd2283a62380c30b04cf8444c9c194d5c"
},
"path_in_vcs": ""
} \ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..7507077
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: dtolnay
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fd7b48a..8acd5fc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,24 +3,37 @@ name: CI
on:
push:
pull_request:
+ workflow_dispatch:
schedule: [cron: "40 1 * * *"]
+permissions:
+ contents: read
+
env:
RUSTFLAGS: -Dwarnings
jobs:
+ pre_ci:
+ uses: dtolnay/.github/.github/workflows/pre_ci.yml@master
+
test:
name: Rust ${{matrix.rust}}
+ needs: pre_ci
+ if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- rust: [nightly, beta, stable, 1.52.0, 1.46.0, 1.45.0, 1.40.0, 1.39.0, 1.36.0, 1.33.0, 1.32.0, 1.31.0]
+ rust: [nightly, beta, stable, 1.52.0, 1.46.0, 1.40.0, 1.39.0, 1.36.0, 1.33.0, 1.32.0, 1.31.0]
+ timeout-minutes: 45
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
+ - name: Enable type layout randomization
+ run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
+ if: matrix.rust == 'nightly'
- run: cargo test
- run: cargo check --no-default-features
- run: cargo check --features serde
@@ -28,9 +41,12 @@ jobs:
node:
name: Node
+ needs: pre_ci
+ if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
+ timeout-minutes: 45
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- run: npm install semver
- run: cargo test
@@ -41,26 +57,51 @@ jobs:
name: Clippy
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
+ timeout-minutes: 45
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@clippy
- run: cargo clippy --tests --benches -- -Dclippy::all -Dclippy::pedantic
miri:
name: Miri
+ needs: pre_ci
+ if: needs.pre_ci.outputs.continue
+ runs-on: ubuntu-latest
+ env:
+ MIRIFLAGS: -Zmiri-strict-provenance
+ timeout-minutes: 45
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@miri
+ - name: Run cargo miri test (64-bit little endian)
+ run: cargo miri test --target x86_64-unknown-linux-gnu
+ - name: Run cargo miri test (64-bit big endian)
+ run: cargo miri test --target powerpc64-unknown-linux-gnu
+ - name: Run cargo miri test (32-bit little endian)
+ run: cargo miri test --target i686-unknown-linux-gnu
+ - name: Run cargo miri test (32-bit big endian)
+ run: cargo miri test --target mips-unknown-linux-gnu
+
+ fuzz:
+ name: Fuzz
+ needs: pre_ci
+ if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
+ timeout-minutes: 45
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- with:
- components: miri
- - run: cargo miri test
+ - uses: dtolnay/install@cargo-fuzz
+ - run: cargo fuzz check
outdated:
name: Outdated
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
+ timeout-minutes: 45
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: dtolnay/install@cargo-outdated
- - run: cargo outdated --exit-code 1
+ - run: cargo outdated --workspace --exit-code 1
+ - run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1
diff --git a/Android.bp b/Android.bp
index 402c65c..f148121 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,8 +1,6 @@
// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
-
-
package {
default_applicable_licenses: ["external_rust_crates_semver_license"],
}
@@ -43,7 +41,7 @@ rust_library {
name: "libsemver",
crate_name: "semver",
cargo_env_compat: true,
- cargo_pkg_version: "1.0.6",
+ cargo_pkg_version: "1.0.17",
srcs: ["src/lib.rs"],
edition: "2018",
features: [
@@ -58,4 +56,6 @@ rust_library {
"//apex_available:platform",
"com.android.virt",
],
+ product_available: true,
+ vendor_available: true,
}
diff --git a/Cargo.toml b/Cargo.toml
index e250936..e7fcfb7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,20 +13,28 @@
edition = "2018"
rust-version = "1.31"
name = "semver"
-version = "1.0.6"
+version = "1.0.17"
authors = ["David Tolnay <dtolnay@gmail.com>"]
description = "Parser and evaluator for Cargo's flavor of Semantic Versioning"
documentation = "https://docs.rs/semver"
readme = "README.md"
+keywords = ["cargo"]
+categories = [
+ "data-structures",
+ "no-std",
+]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/semver"
[package.metadata.docs.rs]
-targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = [
"--cfg",
"doc_cfg",
]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[lib]
+doc-scrape-examples = false
[dependencies.serde]
version = "1.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index ef65cdc..30ee98e 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,14 +1,15 @@
[package]
name = "semver"
-version = "1.0.6"
+version = "1.0.17"
authors = ["David Tolnay <dtolnay@gmail.com>"]
+categories = ["data-structures", "no-std"]
+description = "Parser and evaluator for Cargo's flavor of Semantic Versioning"
+documentation = "https://docs.rs/semver"
edition = "2018"
-rust-version = "1.31"
+keywords = ["cargo"]
license = "MIT OR Apache-2.0"
-description = "Parser and evaluator for Cargo's flavor of Semantic Versioning"
repository = "https://github.com/dtolnay/semver"
-documentation = "https://docs.rs/semver"
-readme = "README.md"
+rust-version = "1.31"
[features]
default = ["std"]
@@ -17,6 +18,9 @@ std = []
[dependencies]
serde = { version = "1.0", optional = true, default-features = false }
+[lib]
+doc-scrape-examples = false
+
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = ["--cfg", "doc_cfg"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
index 16fe87b..1b5ec8b 100644
--- a/LICENSE-APACHE
+++ b/LICENSE-APACHE
@@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/METADATA b/METADATA
index 98ff1b7..642e29a 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/semver
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
name: "semver"
description: "Parser and evaluator for Cargo\'s flavor of Semantic Versioning"
third_party {
@@ -7,13 +11,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/semver/semver-1.0.6.crate"
+ value: "https://static.crates.io/crates/semver/semver-1.0.17.crate"
}
- version: "1.0.6"
+ version: "1.0.17"
license_type: NOTICE
last_upgrade_date {
- year: 2022
- month: 3
- day: 1
+ year: 2023
+ month: 4
+ day: 3
}
}
diff --git a/README.md b/README.md
index 9de11d7..a9a1cb8 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@ semver
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/semver-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/semver)
[<img alt="crates.io" src="https://img.shields.io/crates/v/semver.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/semver)
-[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-semver-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/semver)
-[<img alt="build status" src="https://img.shields.io/github/workflow/status/dtolnay/semver/CI/master?style=for-the-badge" height="20">](https://github.com/dtolnay/semver/actions?query=branch%3Amaster)
+[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-semver-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/semver)
+[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/semver/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/semver/actions?query=branch%3Amaster)
A parser and evaluator for Cargo's flavor of Semantic Versioning.
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 061ff86..5366bfb 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,13 +1,11 @@
// Generated by update_crate_tests.py for tests that depend on this crate.
{
- "presubmit": [
+ "imports": [
{
- "name": "virtualizationservice_device_test"
- }
- ],
- "presubmit-rust": [
+ "path": "packages/modules/Virtualization/virtualizationmanager"
+ },
{
- "name": "virtualizationservice_device_test"
+ "path": "packages/modules/Virtualization/vm"
}
]
}
diff --git a/build.rs b/build.rs
index b39468d..81ad970 100644
--- a/build.rs
+++ b/build.rs
@@ -3,17 +3,13 @@ use std::process::Command;
use std::str;
fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+
let compiler = match rustc_minor_version() {
Some(compiler) => compiler,
None => return,
};
- if compiler < 32 {
- // u64::from_ne_bytes.
- // https://doc.rust-lang.org/std/primitive.u64.html#method.from_ne_bytes
- println!("cargo:rustc-cfg=no_from_ne_bytes");
- }
-
if compiler < 33 {
// Exhaustive integer patterns. On older compilers, a final `_` arm is
// required even if every possible integer value is otherwise covered.
@@ -41,7 +37,7 @@ fn main() {
if compiler < 45 {
// String::strip_prefix.
- // https://doc.rust-lang.org/std/primitive.str.html#method.repeat
+ // https://doc.rust-lang.org/std/primitive.str.html#method.strip_prefix
println!("cargo:rustc-cfg=no_str_strip_prefix");
}
diff --git a/src/backport.rs b/src/backport.rs
index f3a69c9..b5e1d02 100644
--- a/src/backport.rs
+++ b/src/backport.rs
@@ -14,50 +14,10 @@ impl StripPrefixExt for str {
}
}
-#[cfg(no_from_ne_bytes)] // rustc <1.32
-pub(crate) trait FromNeBytes {
- fn from_ne_bytes(bytes: [u8; 8]) -> Self;
-}
-
-#[cfg(no_from_ne_bytes)]
-impl FromNeBytes for u64 {
- fn from_ne_bytes(bytes: [u8; 8]) -> Self {
- unsafe { std::mem::transmute(bytes) }
- }
-}
-
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;
-
- pub mod alloc {
- use std::mem;
-
- pub struct Layout {
- size: usize,
- }
-
- impl Layout {
- pub unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
- assert_eq!(align, 2);
- Layout { size }
- }
- }
-
- pub unsafe fn alloc(layout: Layout) -> *mut u8 {
- let len_u16 = (layout.size + 1) / 2;
- let mut vec = Vec::new();
- vec.reserve_exact(len_u16);
- let ptr: *mut u16 = vec.as_mut_ptr();
- mem::forget(vec);
- ptr as *mut u8
- }
-
- pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
- let len_u16 = (layout.size + 1) / 2;
- unsafe { Vec::from_raw_parts(ptr as *mut u16, 0, len_u16) };
- }
- }
}
diff --git a/src/error.rs b/src/error.rs
index 72749c2..93b05ee 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -2,6 +2,7 @@ use crate::parse::Error;
use core::fmt::{self, Debug, Display};
pub(crate) enum ErrorKind {
+ Empty,
UnexpectedEnd(Position),
UnexpectedChar(Position, char),
UnexpectedCharAfter(Position, char),
@@ -31,21 +32,33 @@ impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match &self.kind {
+ ErrorKind::Empty => formatter.write_str("empty string, expected a semver version"),
ErrorKind::UnexpectedEnd(pos) => {
write!(formatter, "unexpected end of input while parsing {}", pos)
}
ErrorKind::UnexpectedChar(pos, ch) => {
write!(
formatter,
- "unexpected character {:?} while parsing {}",
- ch, pos,
+ "unexpected character {} while parsing {}",
+ QuotedChar(*ch),
+ pos,
)
}
ErrorKind::UnexpectedCharAfter(pos, ch) => {
- write!(formatter, "unexpected character {:?} after {}", ch, pos)
+ write!(
+ formatter,
+ "unexpected character {} after {}",
+ QuotedChar(*ch),
+ pos,
+ )
}
ErrorKind::ExpectedCommaFound(pos, ch) => {
- write!(formatter, "expected comma after {}, found {:?}", pos, ch)
+ write!(
+ formatter,
+ "expected comma after {}, found {}",
+ pos,
+ QuotedChar(*ch),
+ )
}
ErrorKind::LeadingZero(pos) => {
write!(formatter, "invalid leading zero in {}", pos)
@@ -96,3 +109,18 @@ impl Debug for Error {
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/src/identifier.rs b/src/identifier.rs
index 500239e..0273ae6 100644
--- a/src/identifier.rs
+++ b/src/identifier.rs
@@ -66,51 +66,82 @@
// 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, Layout};
-#[cfg(no_from_ne_bytes)]
-use crate::backport::FromNeBytes;
+use crate::alloc::alloc::{alloc, dealloc, handle_alloc_error, Layout};
+use core::isize;
use core::mem;
use core::num::{NonZeroU64, NonZeroUsize};
-use core::ptr;
+use core::ptr::{self, NonNull};
use core::slice;
use core::str;
+use core::usize;
-#[repr(transparent)]
+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 {
- repr: NonZeroU64,
+ head: NonNull<u8>,
+ tail: [u8; TAIL_BYTES],
}
impl Identifier {
- const EMPTY: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(!0) };
-
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 { repr: Self::EMPTY }
+ 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();
- let repr = match len as u64 {
- 0 => Self::EMPTY,
+ debug_assert!(len <= isize::MAX as usize);
+ match len as u64 {
+ 0 => Self::empty(),
1..=8 => {
- let mut bytes = [0u8; 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: it's nonzero because the input string was at least 1
- // byte of ASCII and did not contain \0.
- unsafe { NonZeroU64::new_unchecked(u64::from_ne_bytes(bytes)) }
+ // 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 usize::MAX.
+ // 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 {
@@ -124,25 +155,33 @@ impl Identifier {
// 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) };
- ptr_to_repr(ptr)
+ 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!(),
- };
- Identifier { repr }
+ }
}
pub(crate) fn is_empty(&self) -> bool {
// `cmp rdi, -1` -- basically: `repr as i64 == -1`
- self.repr == Self::EMPTY
+ 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.repr.get() >> 63 == 0
+ self.head.as_ptr() as usize >> (PTR_BYTES * 8 - 1) == 0
}
fn is_empty_or_inline(&self) -> bool {
@@ -155,38 +194,46 @@ impl Identifier {
""
} else if self.is_inline() {
// SAFETY: repr is in the inline representation.
- unsafe { inline_as_str(&self.repr) }
+ unsafe { inline_as_str(self) }
} else {
// SAFETY: repr is in the heap allocated representation.
- unsafe { ptr_as_str(&self.repr) }
+ unsafe { ptr_as_str(&self.head) }
}
}
}
impl Clone for Identifier {
fn clone(&self) -> Self {
- let repr = if self.is_empty_or_inline() {
- self.repr
+ if self.is_empty_or_inline() {
+ Identifier {
+ head: self.head,
+ tail: self.tail,
+ }
} else {
- let ptr = repr_to_ptr(self.repr);
+ 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 usize::MAX. This is just
+ // 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) }
- ptr_to_repr(clone)
- };
- Identifier { repr }
+ Identifier {
+ head: ptr_to_repr(clone),
+ tail: [0; TAIL_BYTES],
+ }
+ }
}
}
@@ -195,7 +242,7 @@ impl Drop for Identifier {
if self.is_empty_or_inline() {
return;
}
- let ptr = repr_to_ptr_mut(self.repr);
+ 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();
@@ -214,46 +261,65 @@ impl PartialEq for Identifier {
fn eq(&self, rhs: &Self) -> bool {
if self.is_empty_or_inline() {
// Fast path (most common)
- self.repr == rhs.repr
+ 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.repr) == ptr_as_str(&rhs.repr) }
+ 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(ptr: *mut u8) -> NonZeroU64 {
+fn ptr_to_repr(original: *mut u8) -> NonNull<u8> {
// `mov eax, 1`
// `shld rax, rdi, 63`
- let repr = (ptr as u64 | 1).rotate_right(1);
+ 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 { NonZeroU64::new_unchecked(repr) }
+ 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(repr: NonZeroU64) -> *const u8 {
+fn repr_to_ptr(modified: NonNull<u8>) -> *const u8 {
// `lea rax, [rdi + rdi]`
- (repr.get() << 1) as *const u8
+ 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: NonZeroU64) -> *mut u8 {
+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.
-fn inline_len(repr: NonZeroU64) -> NonZeroUsize {
+//
+// 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,
@@ -275,9 +341,9 @@ fn inline_len(repr: NonZeroU64) -> NonZeroUsize {
// 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: &NonZeroU64) -> &str {
- let ptr = repr as *const NonZeroU64 as *const u8;
- let len = inline_len(*repr).get();
+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.
@@ -335,7 +401,7 @@ unsafe fn decode_len(ptr: *const u8) -> NonZeroUsize {
// SAFETY: repr must be in the heap allocated representation, with varint header
// and string contents already written.
-unsafe fn ptr_as_str(repr: &NonZeroU64) -> &str {
+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);
diff --git a/src/lib.rs b/src/lib.rs
index 5887ea2..d6a8fe3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,7 +2,7 @@
//!
//! [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&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
+//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
//! <br>
//!
@@ -60,7 +60,7 @@
//!
//! [Specifying Dependencies]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
-#![doc(html_root_url = "https://docs.rs/semver/1.0.6")]
+#![doc(html_root_url = "https://docs.rs/semver/1.0.17")]
#![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))]
diff --git a/src/parse.rs b/src/parse.rs
index bf70739..e92d87a 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -26,6 +26,10 @@ impl FromStr for Version {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
+ if text.is_empty() {
+ return Err(Error::new(ErrorKind::Empty));
+ }
+
let mut pos = Position::Major;
let (major, text) = numeric_identifier(text, pos)?;
let text = dot(text, pos)?;
@@ -262,23 +266,23 @@ fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
fn op(input: &str) -> (Op, &str) {
let bytes = input.as_bytes();
- if bytes.get(0) == Some(&b'=') {
+ if bytes.first() == Some(&b'=') {
(Op::Exact, &input[1..])
- } else if bytes.get(0) == Some(&b'>') {
+ } else if bytes.first() == Some(&b'>') {
if bytes.get(1) == Some(&b'=') {
(Op::GreaterEq, &input[2..])
} else {
(Op::Greater, &input[1..])
}
- } else if bytes.get(0) == Some(&b'<') {
+ } else if bytes.first() == Some(&b'<') {
if bytes.get(1) == Some(&b'=') {
(Op::LessEq, &input[2..])
} else {
(Op::Less, &input[1..])
}
- } else if bytes.get(0) == Some(&b'~') {
+ } else if bytes.first() == Some(&b'~') {
(Op::Tilde, &input[1..])
- } else if bytes.get(0) == Some(&b'^') {
+ } else if bytes.first() == Some(&b'^') {
(Op::Caret, &input[1..])
} else {
(Op::DEFAULT, input)
diff --git a/src/serde.rs b/src/serde.rs
index 06eceb6..1fcc7d8 100644
--- a/src/serde.rs
+++ b/src/serde.rs
@@ -1,4 +1,4 @@
-use crate::{Version, VersionReq};
+use crate::{Comparator, Version, VersionReq};
use core::fmt;
use serde::de::{Deserialize, Deserializer, Error, Visitor};
use serde::ser::{Serialize, Serializer};
@@ -21,6 +21,15 @@ impl Serialize for VersionReq {
}
}
+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
@@ -72,3 +81,29 @@ impl<'de> Deserialize<'de> for VersionReq {
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)
+ }
+}
diff --git a/tests/test_autotrait.rs b/tests/test_autotrait.rs
new file mode 100644
index 0000000..5d16689
--- /dev/null
+++ b/tests/test_autotrait.rs
@@ -0,0 +1,14 @@
+#![allow(clippy::extra_unused_type_parameters)]
+
+fn assert_send_sync<T: Send + Sync>() {}
+
+#[test]
+fn test() {
+ assert_send_sync::<semver::BuildMetadata>();
+ assert_send_sync::<semver::Comparator>();
+ assert_send_sync::<semver::Error>();
+ assert_send_sync::<semver::Prerelease>();
+ assert_send_sync::<semver::Version>();
+ assert_send_sync::<semver::VersionReq>();
+ assert_send_sync::<semver::Op>();
+}
diff --git a/tests/test_version.rs b/tests/test_version.rs
index 93a528c..de3628f 100644
--- a/tests/test_version.rs
+++ b/tests/test_version.rs
@@ -12,10 +12,7 @@ use semver::{BuildMetadata, Prerelease, Version};
#[test]
fn test_parse() {
let err = version_err("");
- assert_to_string(
- err,
- "unexpected end of input while parsing major version number",
- );
+ assert_to_string(err, "empty string, expected a semver version");
let err = version_err(" ");
assert_to_string(
diff --git a/tests/test_version_req.rs b/tests/test_version_req.rs
index 2ea33ab..98a03ac 100644
--- a/tests/test_version_req.rs
+++ b/tests/test_version_req.rs
@@ -336,7 +336,7 @@ pub fn test_parse_errors() {
let err = req_err("\0");
assert_to_string(
err,
- "unexpected character '\\u{0}' while parsing major version number",
+ "unexpected character '\\0' while parsing major version number",
);
let err = req_err(">= >= 0.0.2");