From 15df4387dd075cc6adb98a7c5109a46b5cb42b9b Mon Sep 17 00:00:00 2001 From: Markus Mayer Date: Sat, 8 Apr 2023 00:10:24 +0200 Subject: [PATCH] Add fuzzing support --- CHANGELOG.md | 10 +++ Cargo.toml | 6 +- README.md | 10 +++ fuzz/.gitignore | 4 + fuzz/Cargo.lock | 131 +++++++++++++++++++++++++++++ fuzz/Cargo.toml | 40 +++++++++ fuzz/fuzz_targets/decode_random.rs | 10 +++ fuzz/fuzz_targets/encode_random.rs | 11 +++ fuzz/fuzz_targets/roundtrip.rs | 9 ++ src/lib.rs | 13 ++- 10 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.lock create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/decode_random.rs create mode 100644 fuzz/fuzz_targets/encode_random.rs create mode 100644 fuzz/fuzz_targets/roundtrip.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f94f9b2..edcbb99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- `from_bytes_ref`, `as_uuid` and `is_empty` are now `const`. + +### Internal + +- Added fuzzing targets. + ## 0.1.0 - 2023-04-07 ### Added diff --git a/Cargo.toml b/Cargo.toml index 87079c7..54df5f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shortguid" -version = "0.1.0" +version = "0.2.0-unstable" edition = "2021" license = "EUPL-1.2" description = "Short URL-safe Base64 encoded UUIDs" @@ -10,5 +10,9 @@ readme = "README.md" rust-version = "1.67.1" [dependencies] +arbitrary = { version = "1.3.0", optional = true } base64 = "0.21.0" uuid = "1.3.0" + +[features] +arbitrary = ["uuid/arbitrary", "arbitrary/derive"] diff --git a/README.md b/README.md index ade75e2..2a26f29 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,13 @@ fn it_works() { assert_eq!(short_guid_3, uuid); } ``` + +## Fuzzing + +This uses `cargo-fuzz`. See `fuzz/fuzzers` for the available fuzzing scripts. To run, use an invocation like these: + +``` +cargo +nightly fuzz run roundtrip +cargo +nightly fuzz run decode_random +cargo +nightly fuzz run encode_random +``` diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000..22cd5eb --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,131 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "shortguid" +version = "0.2.0-unstable" +dependencies = [ + "arbitrary", + "base64", + "uuid", +] + +[[package]] +name = "shortguid-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "shortguid", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "uuid" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +dependencies = [ + "arbitrary", +] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..25e30dd --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "shortguid-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4.6" + +[dependencies.shortguid] +path = ".." +features = ["arbitrary"] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "decode_random" +path = "fuzz_targets/decode_random.rs" +test = false +doc = false + +[[bin]] +name = "encode_random" +path = "fuzz_targets/encode_random.rs" +test = false +doc = false + +[[bin]] +name = "roundtrip" +path = "fuzz_targets/roundtrip.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/decode_random.rs b/fuzz/fuzz_targets/decode_random.rs new file mode 100644 index 0000000..ff4c09e --- /dev/null +++ b/fuzz/fuzz_targets/decode_random.rs @@ -0,0 +1,10 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use shortguid::ShortGuid; + +fuzz_target!(|id: ShortGuid| { + let str = id.to_string(); + let id2 = ShortGuid::try_parse(str).expect("decoding should work"); + assert_eq!(id, id2); +}); diff --git a/fuzz/fuzz_targets/encode_random.rs b/fuzz/fuzz_targets/encode_random.rs new file mode 100644 index 0000000..d2265b8 --- /dev/null +++ b/fuzz/fuzz_targets/encode_random.rs @@ -0,0 +1,11 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use shortguid::ShortGuid; + +fuzz_target!(|str: String| { + // Since the input data is random, this likely won't + // parse correctly. As long as it returns an Err() + // instead of panicking, it's considered correct behavior. + let _ = ShortGuid::try_parse(&str); +}); diff --git a/fuzz/fuzz_targets/roundtrip.rs b/fuzz/fuzz_targets/roundtrip.rs new file mode 100644 index 0000000..69bab6f --- /dev/null +++ b/fuzz/fuzz_targets/roundtrip.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use shortguid::ShortGuid; + +fuzz_target!(|bytes: [u8; 16]| { + let id = ShortGuid::from_bytes_ref(&bytes); + assert_eq!(&bytes, id.as_bytes()); +}); diff --git a/src/lib.rs b/src/lib.rs index ce97d17..96f2f77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ use uuid::Uuid; /// assert_eq!(short_guid_a, short_guid_b); /// ``` #[derive(Default, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[repr(transparent)] pub struct ShortGuid(Uuid); @@ -47,6 +48,7 @@ impl ShortGuid { Ok(Self(uuid)) } + #[inline] pub fn from_bytes(bytes: &[u8; 16]) -> Self { Self(Uuid::from_bytes_ref(bytes).clone()) } @@ -77,18 +79,21 @@ impl ShortGuid { /// &bytes1 as *const [u8; 16] as *const u8, /// )); /// ``` - pub fn from_bytes_ref(bytes: &[u8; 16]) -> &Self { + #[inline] + pub const fn from_bytes_ref(bytes: &[u8; 16]) -> &Self { // SAFETY: `Bytes`, `Uuid` and `ShortGuid have the same ABI unsafe { &*(bytes as *const [u8; 16] as *const Uuid as *const ShortGuid) } } /// Tests if this [`ShortGuid`] is all zeros. - pub fn is_empty(&self) -> bool { + #[inline] + pub const fn is_empty(&self) -> bool { self.0.is_nil() } /// Returns the underlying [`Uuid`] instance. - pub fn as_uuid(&self) -> &Uuid { + #[inline] + pub const fn as_uuid(&self) -> &Uuid { &self.0 } @@ -118,6 +123,7 @@ impl ShortGuid { /// &bytes1 as *const [u8; 16] as *const u8, /// )); /// ``` + #[inline] pub fn as_bytes(&self) -> &[u8; 16] { self.0.as_bytes() } @@ -146,6 +152,7 @@ impl ShortGuid { /// # Ok(()) /// # } /// ``` + #[inline] pub fn to_bytes_le(&self) -> [u8; 16] { self.0.to_bytes_le() }