Skip to content

Commit fa75163

Browse files
author
MarcoFalke
committed
contrib: Make deterministic-coverage error messages more readable
1 parent aa87e0b commit fa75163

File tree

3 files changed

+164
-115
lines changed

3 files changed

+164
-115
lines changed

contrib/devtools/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ deterministic-fuzz-coverage
88
A tool to check for non-determinism in fuzz coverage. To get the help, run:
99

1010
```
11-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- --help
11+
cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- --help
1212
```
1313

1414
To execute the tool, compilation has to be done with the build options:
@@ -22,7 +22,7 @@ repository must have been cloned. Finally, a fuzz target has to be picked
2222
before running the tool:
2323

2424
```
25-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- $PWD/build_dir $PWD/qa-assets/fuzz_corpora fuzz_target_name
25+
cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- $PWD/build_dir $PWD/qa-assets/fuzz_corpora fuzz_target_name
2626
```
2727

2828
deterministic-unittest-coverage
@@ -31,7 +31,7 @@ deterministic-unittest-coverage
3131
A tool to check for non-determinism in unit-test coverage. To get the help, run:
3232

3333
```
34-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- --help
34+
cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- --help
3535
```
3636

3737
To execute the tool, compilation has to be done with the build options:
@@ -43,7 +43,7 @@ To execute the tool, compilation has to be done with the build options:
4343
Both llvm-profdata and llvm-cov must be installed.
4444

4545
```
46-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- $PWD/build_dir <boost unittest filter>
46+
cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- $PWD/build_dir <boost unittest filter>
4747
```
4848

4949
clang-format-diff.py

contrib/devtools/deterministic-fuzz-coverage/src/main.rs

Lines changed: 92 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,113 +4,115 @@
44

55
use std::env;
66
use std::fs::{read_dir, File};
7-
use std::path::Path;
8-
use std::process::{exit, Command};
7+
use std::path::{Path, PathBuf};
8+
use std::process::{Command, ExitCode};
99
use std::str;
1010

11+
/// A type for a complete and readable error message.
12+
type AppError = String;
13+
type AppResult = Result<(), AppError>;
14+
1115
const LLVM_PROFDATA: &str = "llvm-profdata";
1216
const LLVM_COV: &str = "llvm-cov";
1317
const GIT: &str = "git";
1418

15-
fn exit_help(err: &str) -> ! {
16-
eprintln!("Error: {}", err);
17-
eprintln!();
18-
eprintln!("Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name");
19-
eprintln!();
20-
eprintln!("Refer to the devtools/README.md for more details.");
21-
exit(1)
19+
fn exit_help(err: &str) -> AppError {
20+
format!(
21+
r#"
22+
Error: {err}
23+
24+
Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name
25+
26+
Refer to the devtools/README.md for more details."#
27+
)
2228
}
2329

24-
fn sanity_check(corpora_dir: &Path, fuzz_exe: &Path) {
30+
fn sanity_check(corpora_dir: &Path, fuzz_exe: &Path) -> AppResult {
2531
for tool in [LLVM_PROFDATA, LLVM_COV, GIT] {
2632
let output = Command::new(tool).arg("--help").output();
2733
match output {
2834
Ok(output) if output.status.success() => {}
29-
_ => {
30-
exit_help(&format!("The tool {} is not installed", tool));
31-
}
35+
_ => Err(exit_help(&format!("The tool {} is not installed", tool)))?,
3236
}
3337
}
3438
if !corpora_dir.is_dir() {
35-
exit_help(&format!(
39+
Err(exit_help(&format!(
3640
"Fuzz corpora path ({}) must be a directory",
3741
corpora_dir.display()
38-
));
42+
)))?;
3943
}
4044
if !fuzz_exe.exists() {
41-
exit_help(&format!(
45+
Err(exit_help(&format!(
4246
"Fuzz executable ({}) not found",
4347
fuzz_exe.display()
44-
));
48+
)))?;
4549
}
50+
Ok(())
4651
}
4752

48-
fn main() {
53+
fn app() -> AppResult {
4954
// Parse args
5055
let args = env::args().collect::<Vec<_>>();
51-
let build_dir = args
52-
.get(1)
53-
.unwrap_or_else(|| exit_help("Must set build dir"));
56+
let build_dir = args.get(1).ok_or(exit_help("Must set build dir"))?;
5457
if build_dir == "--help" {
55-
exit_help("--help requested")
58+
Err(exit_help("--help requested"))?;
5659
}
57-
let corpora_dir = args
58-
.get(2)
59-
.unwrap_or_else(|| exit_help("Must set fuzz corpora dir"));
60+
let corpora_dir = args.get(2).ok_or(exit_help("Must set fuzz corpora dir"))?;
6061
let fuzz_target = args
6162
.get(3)
6263
// Require fuzz target for now. In the future it could be optional and the tool could
6364
// iterate over all compiled fuzz targets
64-
.unwrap_or_else(|| exit_help("Must set fuzz target"));
65+
.ok_or(exit_help("Must set fuzz target"))?;
6566
if args.get(4).is_some() {
66-
exit_help("Too many args")
67+
Err(exit_help("Too many args"))?;
6768
}
6869

6970
let build_dir = Path::new(build_dir);
7071
let corpora_dir = Path::new(corpora_dir);
7172
let fuzz_exe = build_dir.join("bin/fuzz");
7273

73-
sanity_check(corpora_dir, &fuzz_exe);
74+
sanity_check(corpora_dir, &fuzz_exe)?;
7475

75-
deterministic_coverage(build_dir, corpora_dir, &fuzz_exe, fuzz_target);
76+
deterministic_coverage(build_dir, corpora_dir, &fuzz_exe, fuzz_target)
7677
}
7778

78-
fn using_libfuzzer(fuzz_exe: &Path) -> bool {
79+
fn using_libfuzzer(fuzz_exe: &Path) -> Result<bool, AppError> {
7980
println!("Check if using libFuzzer ...");
8081
let stderr = Command::new(fuzz_exe)
8182
.arg("-help=1") // Will be interpreted as option (libfuzzer) or as input file
8283
.env("FUZZ", "addition_overflow") // Any valid target
8384
.output()
84-
.expect("fuzz failed")
85+
.map_err(|e| format!("fuzz failed with {e}"))?
8586
.stderr;
86-
let help_output = str::from_utf8(&stderr).expect("The -help=1 output must be valid text");
87-
help_output.contains("libFuzzer")
87+
let help_output = str::from_utf8(&stderr)
88+
.map_err(|e| format!("The libFuzzer -help=1 output must be valid text ({e})"))?;
89+
Ok(help_output.contains("libFuzzer"))
8890
}
8991

9092
fn deterministic_coverage(
9193
build_dir: &Path,
9294
corpora_dir: &Path,
9395
fuzz_exe: &Path,
9496
fuzz_target: &str,
95-
) {
96-
let using_libfuzzer = using_libfuzzer(fuzz_exe);
97+
) -> AppResult {
98+
let using_libfuzzer = using_libfuzzer(fuzz_exe)?;
9799
let profraw_file = build_dir.join("fuzz_det_cov.profraw");
98100
let profdata_file = build_dir.join("fuzz_det_cov.profdata");
99101
let corpus_dir = corpora_dir.join(fuzz_target);
100102
let mut entries = read_dir(&corpus_dir)
101-
.unwrap_or_else(|err| {
103+
.map_err(|err| {
102104
exit_help(&format!(
103105
"The fuzz target's input directory must exist! ({}; {})",
104106
corpus_dir.display(),
105107
err
106108
))
107-
})
109+
})?
108110
.map(|entry| entry.expect("IO error"))
109111
.collect::<Vec<_>>();
110112
entries.sort_by_key(|entry| entry.file_name());
111-
let run_single = |run_id: u8, entry: &Path| {
113+
let run_single = |run_id: u8, entry: &Path| -> Result<PathBuf, AppError> {
112114
let cov_txt_path = build_dir.join(format!("fuzz_det_cov.show.{run_id}.txt"));
113-
assert!({
115+
if !{
114116
{
115117
let mut cmd = Command::new(fuzz_exe);
116118
if using_libfuzzer {
@@ -122,20 +124,26 @@ fn deterministic_coverage(
122124
.env("FUZZ", fuzz_target)
123125
.arg(entry)
124126
.status()
125-
.expect("fuzz failed")
127+
.map_err(|e| format!("fuzz failed with {e}"))?
126128
.success()
127-
});
128-
assert!(Command::new(LLVM_PROFDATA)
129+
} {
130+
Err("fuzz failed".to_string())?;
131+
}
132+
if !Command::new(LLVM_PROFDATA)
129133
.arg("merge")
130134
.arg("--sparse")
131135
.arg(&profraw_file)
132136
.arg("-o")
133137
.arg(&profdata_file)
134138
.status()
135-
.expect("merge failed")
136-
.success());
137-
let cov_file = File::create(&cov_txt_path).expect("Failed to create coverage txt file");
138-
assert!(Command::new(LLVM_COV)
139+
.map_err(|e| format!("{LLVM_PROFDATA} merge failed with {e}"))?
140+
.success()
141+
{
142+
Err(format!("{LLVM_PROFDATA} merge failed. This can be a sign of compiling without code coverage support."))?;
143+
}
144+
let cov_file = File::create(&cov_txt_path)
145+
.map_err(|e| format!("Failed to create coverage txt file ({e})"))?;
146+
if !Command::new(LLVM_COV)
139147
.args([
140148
"show",
141149
"--show-line-counts-or-regions",
@@ -146,27 +154,31 @@ fn deterministic_coverage(
146154
.arg(fuzz_exe)
147155
.stdout(cov_file)
148156
.spawn()
149-
.expect("Failed to execute llvm-cov")
157+
.map_err(|e| format!("{LLVM_COV} show failed with {e}"))?
150158
.wait()
151-
.expect("Failed to execute llvm-cov")
152-
.success());
153-
cov_txt_path
159+
.map_err(|e| format!("{LLVM_COV} show failed with {e}"))?
160+
.success()
161+
{
162+
Err(format!("{LLVM_COV} show failed"))?;
163+
};
164+
Ok(cov_txt_path)
154165
};
155-
let check_diff = |a: &Path, b: &Path, err: &str| {
166+
let check_diff = |a: &Path, b: &Path, err: &str| -> AppResult {
156167
let same = Command::new(GIT)
157168
.args(["--no-pager", "diff", "--no-index"])
158169
.arg(a)
159170
.arg(b)
160171
.status()
161-
.expect("Failed to execute git command")
172+
.map_err(|e| format!("{GIT} diff failed with {e}"))?
162173
.success();
163174
if !same {
164-
eprintln!();
165-
eprintln!("The coverage was not deterministic between runs.");
166-
eprintln!("{}", err);
167-
eprintln!("Exiting.");
168-
exit(1);
175+
Err(format!(
176+
r#"
177+
The coverage was not deterministic between runs.
178+
{err}"#
179+
))?;
169180
}
181+
Ok(())
170182
};
171183
// First, check that each fuzz input is deterministic running by itself in a process.
172184
//
@@ -177,27 +189,42 @@ fn deterministic_coverage(
177189
// their overall coverage trace remains the same across runs and thus remains undetected.
178190
for entry in entries {
179191
let entry = entry.path();
180-
assert!(entry.is_file());
181-
let cov_txt_base = run_single(0, &entry);
182-
let cov_txt_repeat = run_single(1, &entry);
192+
if !entry.is_file() {
193+
Err(format!("{} should be a file", entry.display()))?;
194+
}
195+
let cov_txt_base = run_single(0, &entry)?;
196+
let cov_txt_repeat = run_single(1, &entry)?;
183197
check_diff(
184198
&cov_txt_base,
185199
&cov_txt_repeat,
186200
&format!("The fuzz target input was {}.", entry.display()),
187-
);
201+
)?;
188202
}
189203
// Finally, check that running over all fuzz inputs in one process is deterministic as well.
190204
// This can catch issues where mutable global state is leaked from one fuzz input execution to
191205
// the next.
192206
{
193-
assert!(corpus_dir.is_dir());
194-
let cov_txt_base = run_single(0, &corpus_dir);
195-
let cov_txt_repeat = run_single(1, &corpus_dir);
207+
if !corpus_dir.is_dir() {
208+
Err(format!("{} should be a folder", corpus_dir.display()))?;
209+
}
210+
let cov_txt_base = run_single(0, &corpus_dir)?;
211+
let cov_txt_repeat = run_single(1, &corpus_dir)?;
196212
check_diff(
197213
&cov_txt_base,
198214
&cov_txt_repeat,
199215
&format!("All fuzz inputs in {} were used.", corpus_dir.display()),
200-
);
216+
)?;
201217
}
202218
println!("Coverage test passed for {fuzz_target}.");
219+
Ok(())
220+
}
221+
222+
fn main() -> ExitCode {
223+
match app() {
224+
Ok(()) => ExitCode::SUCCESS,
225+
Err(err) => {
226+
eprintln!("{}", err);
227+
ExitCode::FAILURE
228+
}
229+
}
203230
}

0 commit comments

Comments
 (0)