Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stream|tokio: Port proptest fuzz harnesses to use cargo-fuzz #5392

Merged
merged 1 commit into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \

You can run miri tests with
```
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" PROPTEST_CASES=10 \
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
cargo +nightly miri test --features full --lib
```

Expand All @@ -209,6 +209,31 @@ utilities available to use in tests, no matter the crate being tested.
The best strategy for writing a new integration test is to look at existing
integration tests in the crate and follow the style.

#### Fuzz tests

Some of our crates include a set of fuzz tests, this will be marked by a
directory `fuzz`. It is a good idea to run fuzz tests after each change.
To get started with fuzz testing you'll need to install
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).

`cargo install cargo-fuzz`

To list the available fuzzing harnesses you can run;

```bash
$ cd tokio
$ cargo fuzz list
fuzz_linked_list
````

Running a fuzz test is as simple as;

`cargo fuzz run fuzz_linked_list`

**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
harness will run forever and will only exit if you `ctrl-c` or it finds
a bug.

#### Documentation tests

Ideally, every API has at least one [documentation test] that demonstrates how to
Expand Down
3 changes: 0 additions & 3 deletions tokio-stream/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ parking_lot = "0.12.0"
tokio-test = { path = "../tokio-test" }
futures = { version = "0.3", default-features = false }

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
proptest = "1"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
Expand Down
4 changes: 4 additions & 0 deletions tokio-stream/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
29 changes: 29 additions & 0 deletions tokio-stream/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "tokio-stream-fuzz"
version = "0.0.0"
publish = false
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"
tokio-test = { path = "../../tokio-test" }

[dependencies.tokio-stream]
path = ".."


# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[profile.release]
debug = 1

[[bin]]
name = "fuzz_stream_map"
path = "fuzz_targets/fuzz_stream_map.rs"
test = false
doc = false
80 changes: 80 additions & 0 deletions tokio-stream/fuzz/fuzz_targets/fuzz_stream_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#![no_main]

use libfuzzer_sys::fuzz_target;
use std::pin::Pin;

use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
use tokio_test::{assert_ok, assert_pending, assert_ready, task};

macro_rules! assert_ready_some {
($($t:tt)*) => {
match assert_ready!($($t)*) {
Some(v) => v,
None => panic!("expected `Some`, got `None`"),
}
};
}

macro_rules! assert_ready_none {
($($t:tt)*) => {
match assert_ready!($($t)*) {
None => {}
Some(v) => panic!("expected `None`, got `Some({:?})`", v),
}
};
}

fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
Box::pin(s)
}

fuzz_target!(|data: &[u8]| {
use std::task::{Context, Poll};

struct DidPoll<T> {
did_poll: bool,
inner: T,
}

impl<T: Stream + Unpin> Stream for DidPoll<T> {
type Item = T::Item;

fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T::Item>> {
self.did_poll = true;
Pin::new(&mut self.inner).poll_next(cx)
}
}

for _ in 0..10 {
let mut map = task::spawn(StreamMap::new());
let mut expect = 0;

for (i, is_empty) in data.iter().map(|x| *x != 0).enumerate() {
let inner = if is_empty {
pin_box(stream::empty::<()>())
} else {
expect += 1;
pin_box(stream::pending::<()>())
};

let stream = DidPoll {
did_poll: false,
inner,
};

map.insert(i, stream);
}

if expect == 0 {
assert_ready_none!(map.poll_next());
} else {
assert_pending!(map.poll_next());

assert_eq!(expect, map.values().count());

for stream in map.values() {
assert!(stream.did_poll);
}
}
}
});
57 changes: 0 additions & 57 deletions tokio-stream/tests/stream_stream_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,63 +325,6 @@ fn one_ready_many_none() {
}
}

#[cfg(not(target_os = "wasi"))]
proptest::proptest! {
#[test]
fn fuzz_pending_complete_mix(kinds: Vec<bool>) {
use std::task::{Context, Poll};

struct DidPoll<T> {
did_poll: bool,
inner: T,
}

impl<T: Stream + Unpin> Stream for DidPoll<T> {
type Item = T::Item;

fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<Option<T::Item>>
{
self.did_poll = true;
Pin::new(&mut self.inner).poll_next(cx)
}
}

for _ in 0..10 {
let mut map = task::spawn(StreamMap::new());
let mut expect = 0;

for (i, &is_empty) in kinds.iter().enumerate() {
let inner = if is_empty {
pin_box(stream::empty::<()>())
} else {
expect += 1;
pin_box(stream::pending::<()>())
};

let stream = DidPoll {
did_poll: false,
inner,
};

map.insert(i, stream);
}

if expect == 0 {
assert_ready_none!(map.poll_next());
} else {
assert_pending!(map.poll_next());

assert_eq!(expect, map.values().count());

for stream in map.values() {
assert!(stream.did_poll);
}
}
}
}
}

fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
Box::pin(s)
}
1 change: 0 additions & 1 deletion tokio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ tempfile = "3.1.0"
async-stream = "0.3"

[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dev-dependencies]
proptest = "1"
socket2 = "0.4"

[target.'cfg(not(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown")))'.dev-dependencies]
Expand Down
4 changes: 4 additions & 0 deletions tokio/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
29 changes: 29 additions & 0 deletions tokio/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "tokio-fuzz"
version = "0.0.0"
publish = false
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"

[dependencies.tokio]
path = ".."
features = ["fs","net","process","rt","sync","signal","time"]


# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[profile.release]
debug = 1

[[bin]]
name = "fuzz_linked_list"
path = "fuzz_targets/fuzz_linked_list.rs"
test = false
doc = false
7 changes: 7 additions & 0 deletions tokio/fuzz/fuzz_targets/fuzz_linked_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
tokio::fuzz::fuzz_linked_list(data);
});
1 change: 1 addition & 0 deletions tokio/src/fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub use crate::util::linked_list::tests::fuzz_linked_list;
3 changes: 3 additions & 0 deletions tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,3 +631,6 @@ cfg_macros! {
#[cfg(feature = "io-util")]
#[cfg(test)]
fn is_unpin<T: Unpin>() {}

#[cfg(fuzzing)]
pub mod fuzz;
24 changes: 7 additions & 17 deletions tokio/src/util/linked_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,9 @@ impl<T> fmt::Debug for Pointers<T> {
}
}

#[cfg(test)]
#[cfg(any(test, fuzzing))]
#[cfg(not(loom))]
mod tests {
pub(crate) mod tests {
use super::*;

use std::pin::Pin;
Expand Down Expand Up @@ -623,31 +623,21 @@ mod tests {
}
}

#[cfg(not(tokio_wasm))]
proptest::proptest! {
#[test]
fn fuzz_linked_list(ops: Vec<usize>) {
run_fuzz(ops);
}
}

#[cfg(not(tokio_wasm))]
fn run_fuzz(ops: Vec<usize>) {
use std::collections::VecDeque;

#[derive(Debug)]
#[cfg(fuzzing)]
pub fn fuzz_linked_list(ops: &[u8]) {
enum Op {
Push,
Pop,
Remove(usize),
}
use std::collections::VecDeque;

let ops = ops
.iter()
.map(|i| match i % 3 {
.map(|i| match i % 3u8 {
0 => Op::Push,
1 => Op::Pop,
2 => Op::Remove(i / 3),
2 => Op::Remove((i / 3u8) as usize),
_ => unreachable!(),
})
.collect::<Vec<_>>();
Expand Down