@ 261027.5

250919.5 · 52115ab633cf04a60a43f2d3727cec729cda5c13

globset: add opt-in `Arbitrary` trait implementations

This feature is mandatory when using `Glob` in fuzz testing.

Closes #2720

reading declarations

.github/workflows/ci.yml

@@ -230,3 +230,28 @@ jobs:         env:           RUSTDOCFLAGS: -D warnings         run: cargo doc --no-deps --document-private-items --workspace++  fuzz_testing:+    name: Compile Fuzz Test Targets+    runs-on: ubuntu-latest+    steps:+      - name: Checkout repository+        uses: actions/checkout@v4++      - name: Install required packages (Ubuntu)+        run: |+          sudo apt-get update+          sudo apt-get install g++ --yes++      - name: Install Rust+        uses: dtolnay/rust-toolchain@master+        with:+          toolchain: stable++      - name: Install Fuzzer+        run: cargo install cargo-fuzz+        working-directory: fuzz++      - name: Verify fuzz targets build+        run: cargo check+        working-directory: fuzz

Cargo.lock

@@ -17,6 +17,15 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]]+name = "arbitrary"+version = "1.4.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"+dependencies = [+ "derive_arbitrary",+]+ [[package]] name = "bitflags" version = "2.9.1"@@ -85,6 +94,17 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]]+name = "derive_arbitrary"+version = "1.4.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"+dependencies = [+ "proc-macro2",+ "quote",+ "syn",+]+ [[package]] name = "encoding_rs" version = "0.8.35"@@ -126,6 +146,7 @@ name = "globset" version = "0.4.16" dependencies = [  "aho-corasick",+ "arbitrary",  "bstr",  "glob",  "log",

Cargo.toml

@@ -20,6 +20,7 @@ exclude = [   "/pkg/brew",   "/benchsuite/",   "/scripts/",+  "/crates/fuzz", ] build = "build.rs" autotests = false

ci/ubuntu-install-packages

@@ -11,4 +11,4 @@ if ! command -V sudo; then fi sudo apt-get update sudo apt-get install -y --no-install-recommends \-  zsh xz-utils liblz4-tool musl-tools brotli zstd+  zsh xz-utils liblz4-tool musl-tools brotli zstd g++

crates/globset/Cargo.toml

@@ -21,6 +21,7 @@ bench = false  [dependencies] aho-corasick = "1.1.1"+arbitrary = { version = "1.3.2", optional = true, features = ["derive"] } bstr = { version = "1.6.2", default-features = false, features = ["std"] } log = { version = "0.4.20", optional = true } serde = { version = "1.0.188", optional = true }@@ -41,6 +42,7 @@ serde_json = "1.0.107"  [features] default = ["log"]+arbitrary = ["dep:arbitrary"] # DEPRECATED. It is a no-op. SIMD is done automatically through runtime # dispatch. simd-accel = []

crates/globset/src/glob.rs

@@ -72,6 +72,7 @@ impl MatchStrategy { /// It cannot be used directly to match file paths, but it can be converted /// to a regular expression string or a matcher. #[derive(Clone, Debug, Eq)]+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Glob {     glob: String,     re: String,@@ -194,6 +195,7 @@ pub struct GlobBuilder<'a> { }  #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] struct GlobOptions {     /// Whether to match case insensitively.     case_insensitive: bool,@@ -220,6 +222,7 @@ impl GlobOptions { }  #[derive(Clone, Debug, Default, Eq, PartialEq)]+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] struct Tokens(Vec<Token>);  impl std::ops::Deref for Tokens {@@ -236,6 +239,7 @@ impl std::ops::DerefMut for Tokens { }  #[derive(Clone, Debug, Eq, PartialEq)]+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] enum Token {     Literal(char),     Any,

crates/globset/src/lib.rs

@@ -94,6 +94,19 @@ Standard Unix-style glob syntax is supported:  A `GlobBuilder` can be used to prevent wildcards from matching path separators, or to enable case insensitive matching.++# Crate Features++This crate includes optional features that can be enabled if necessary.+These features are not required but may be useful depending on the use case.++The following features are available:++* **arbitrary** -+  Enabling this feature introduces a public dependency on the+  [`arbitrary`](https://crates.io/crates/arbitrary)+  crate. Namely, it implements the `Arbitrary` trait from that crate for the+  [`Glob`] type. This feature is disabled by default. */  #![deny(missing_docs)]

fuzz/.gitignore

new file mode 100644@@ -0,0 +1,4 @@+target+corpus+artifacts+coverage

fuzz/Cargo.lock

new file mode 100644@@ -0,0 +1,188 @@+# This file is automatically @generated by Cargo.+# It is not intended for manual editing.+version = 4++[[package]]+name = "aho-corasick"+version = "1.1.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"+dependencies = [+ "memchr",+]++[[package]]+name = "arbitrary"+version = "1.3.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"+dependencies = [+ "derive_arbitrary",+]++[[package]]+name = "bstr"+version = "1.9.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"+dependencies = [+ "memchr",+ "serde",+]++[[package]]+name = "cc"+version = "1.0.83"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"+dependencies = [+ "jobserver",+ "libc",+]++[[package]]+name = "derive_arbitrary"+version = "1.3.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"+dependencies = [+ "proc-macro2",+ "quote",+ "syn",+]++[[package]]+name = "fuzz"+version = "0.0.1"+dependencies = [+ "globset",+ "libfuzzer-sys",+]++[[package]]+name = "globset"+version = "0.4.16"+dependencies = [+ "aho-corasick",+ "arbitrary",+ "bstr",+ "log",+ "regex-automata",+ "regex-syntax",+]++[[package]]+name = "jobserver"+version = "0.1.27"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"+dependencies = [+ "libc",+]++[[package]]+name = "libc"+version = "0.2.152"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"++[[package]]+name = "libfuzzer-sys"+version = "0.4.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"+dependencies = [+ "arbitrary",+ "cc",+ "once_cell",+]++[[package]]+name = "log"+version = "0.4.20"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"++[[package]]+name = "memchr"+version = "2.7.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"++[[package]]+name = "once_cell"+version = "1.19.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"++[[package]]+name = "proc-macro2"+version = "1.0.78"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"+dependencies = [+ "unicode-ident",+]++[[package]]+name = "quote"+version = "1.0.35"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"+dependencies = [+ "proc-macro2",+]++[[package]]+name = "regex-automata"+version = "0.4.4"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"+dependencies = [+ "aho-corasick",+ "memchr",+ "regex-syntax",+]++[[package]]+name = "regex-syntax"+version = "0.8.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"++[[package]]+name = "serde"+version = "1.0.195"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"+dependencies = [+ "serde_derive",+]++[[package]]+name = "serde_derive"+version = "1.0.195"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"+dependencies = [+ "proc-macro2",+ "quote",+ "syn",+]++[[package]]+name = "syn"+version = "2.0.48"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"+dependencies = [+ "proc-macro2",+ "quote",+ "unicode-ident",+]++[[package]]+name = "unicode-ident"+version = "1.0.12"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

fuzz/Cargo.toml

new file mode 100644@@ -0,0 +1,25 @@+[package]+name = "fuzz"+version = "0.0.1"+publish = false+edition = "2021"++[package.metadata]+cargo-fuzz = true++[dependencies]+libfuzzer-sys = "0.4"+globset = { path = "../crates/globset", features = ["arbitrary"] }++# Prevent this from interfering with workspaces+[workspace]+members = ["."]++[profile.release]+debug = 1++[[bin]]+name = "fuzz_glob"+path = "fuzz_targets/fuzz_glob.rs"+test = false+doc = false

fuzz/README.md

new file mode 100644@@ -0,0 +1,52 @@+# Fuzz Testing++## Introduction++Fuzz testing produces pseudo-random / arbitrary data that is used to find+stability issues within a code base. While Rust provides a strong type system,+this does not guarantee that an object will convert properly from one struct+to another. It is the responsibility of the developer to ensure that a struct+is converted properly. Fuzz testing will generate input within the domain of+each property. This arbitrary data can then be used to convert from ObjectA+to ObjectB and then back. This type of testing will help catch bugs that the+type system is not able to see.++## Installation++This crate relies on the `cargo-fuzz` component. To install this component,+run the following from the `fuzz` directory:++```bash+cargo install cargo-fuzz+```++## Listing Targets++Once installed, fuzz targets can be listed by running the following command:++```bash+cargo fuzz list+```++This command will print out a list of all targets that can be tested.++## Running Fuzz Tests++To run a fuzz test, the target must be specified:++```bash+cargo fuzz run <target>+```++Note that the above will run the fuzz test indefinitely. Use the+`-max_total_time=<num seconds>` flag to specify how many seconds the test+should run for:++```bash+cargo fuzz run <target> -- -max_total_time=5+```++The above command will run the fuzz test for five seconds. If the test+completes without error it will show how many tests were run successfully.+The test will abort and return a non-zero error code if it is able to produce+an error. The arbitrary input will be displayed in the event of a failure.

fuzz/fuzz_targets/fuzz_glob.rs

new file mode 100644@@ -0,0 +1,22 @@+#![no_main]++use std::str::FromStr;++use globset::Glob;++libfuzzer_sys::fuzz_target!(|glob_str: &str| {+    let Ok(glob) = Glob::new(glob_str) else {+        return;+    };++    let Ok(glob2) = Glob::from_str(glob_str) else {+        return;+    };++    // Verify that a `Glob` constructed with `new` is the same as a `Glob`` constructed+    // with `from_str`.+    assert_eq!(glob, glob2);++    // Verify that `Glob::glob` produces the same string as the original.+    assert_eq!(glob.glob(), glob_str);+});