globset: add opt-in Arbitrary trait implementations

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

Closes #2720
This commit is contained in:
William Johnson
2024-01-24 11:15:22 -05:00
committed by Andrew Gallant
parent 5548e538b1
commit 95979048c9
12 changed files with 358 additions and 1 deletions

View File

@@ -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

21
Cargo.lock generated
View File

@@ -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",

View File

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

View File

@@ -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++

View File

@@ -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 = []

View File

@@ -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,

View File

@@ -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)]

4
fuzz/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage

188
fuzz/Cargo.lock generated Normal file
View File

@@ -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"

25
fuzz/Cargo.toml Normal file
View File

@@ -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

52
fuzz/README.md Normal file
View File

@@ -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.

View File

@@ -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);
});