Skip to content

Commit 4aca99d

Browse files
committed
fuzz: cargo-afl-based fuzzer comparing Rust vs C++ (vs hardware floats).
1 parent e626135 commit 4aca99d

File tree

7 files changed

+915
-2
lines changed

7 files changed

+915
-2
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
/target
22
/Cargo.lock
3+
4+
# Fuzzing data/state.
5+
/fuzz/in*
6+
/fuzz/out*

Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
[package]
2-
name = "rustc_apfloat"
1+
[workspace]
2+
members = ["fuzz"]
3+
4+
[workspace.package]
35
version = "0.0.0"
46
edition = "2021"
57

8+
[package]
9+
name = "rustc_apfloat"
10+
version.workspace = true
11+
edition.workspace = true
12+
613
[dependencies]
714
bitflags = "1.3.2"
815
smallvec = { version = "1.11.0", features = ["const_generics", "union"] }

fuzz/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "rustc_apfloat-fuzz"
3+
version.workspace = true
4+
edition.workspace = true
5+
publish = false
6+
7+
[dependencies]
8+
afl = "0.12.16"
9+
clap = { version = "4.1.13", features = ["derive"] }
10+
num-traits = "0.2.15"
11+
rustc_apfloat = { path = ".." }

fuzz/build.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use std::process::{Command, ExitCode};
2+
3+
mod ops;
4+
5+
fn main() -> std::io::Result<ExitCode> {
6+
// HACK(eddyb) disable the default of re-running the build script on *any*
7+
// change to *the entire source tree* (i.e. the default is roughly `./`).
8+
println!("cargo:rerun-if-changed=build.rs");
9+
10+
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
11+
std::fs::write(out_dir.join("generated_fuzz_ops.rs"), ops::generate_rust())?;
12+
13+
// FIXME(eddyb) add a way to disable the C++ build below, or automatically
14+
// disable it if on an unsupported target (e.g. Windows).
15+
let cxx = true;
16+
if !cxx {
17+
return Ok(ExitCode::SUCCESS);
18+
}
19+
20+
let mut cxx_exported_symbols = vec![];
21+
std::fs::write(
22+
out_dir.join("cxx_apf_fuzz.cpp"),
23+
ops::generate_cxx(&mut cxx_exported_symbols),
24+
)?;
25+
26+
// HACK(eddyb) work around https://github.com/rust-lang/cargo/issues/3676,
27+
// by removing the env vars that Cargo appears to hardcode.
28+
const CARGO_HARDCODED_ENV_VARS: &[(&str, &str)] = &[
29+
("SSL_CERT_DIR", "/etc/pki/tls/certs"),
30+
("SSL_CERT_FILE", "/etc/pki/tls/certs/ca-bundle.crt"),
31+
];
32+
for &(var_name, cargo_hardcoded_value) in CARGO_HARDCODED_ENV_VARS {
33+
if let Ok(value) = std::env::var(var_name) {
34+
if value == cargo_hardcoded_value {
35+
std::env::remove_var(var_name);
36+
}
37+
}
38+
}
39+
40+
let sh_script_exit_status = Command::new("sh")
41+
.args(["-c", SH_SCRIPT])
42+
.envs([
43+
// FIXME(eddyb) ensure this is kept in sync.
44+
(
45+
"llvm_project_git_hash",
46+
"f3598e8fca83ccfb11f58ec7957c229e349765e3",
47+
),
48+
("cxx_apf_fuzz_exports", &cxx_exported_symbols.join(",")),
49+
(
50+
"cxx_apf_fuzz_is_fuzzing",
51+
if cfg!(fuzzing) { "1" } else { "0" },
52+
),
53+
])
54+
.status()?;
55+
Ok(if sh_script_exit_status.success() {
56+
ExitCode::SUCCESS
57+
} else {
58+
ExitCode::FAILURE
59+
})
60+
}
61+
62+
// HACK(eddyb) should avoid shelling out, but for now this will suffice.
63+
const SH_SCRIPT: &str = r#"
64+
set -e
65+
66+
llvm_project_tgz_url="https://codeload.github.com/llvm/llvm-project/tar.gz/$llvm_project_git_hash"
67+
curl -sS "$llvm_project_tgz_url" | tar -C "$OUT_DIR" -xz
68+
llvm="$OUT_DIR"/llvm-project-"$llvm_project_git_hash"/llvm
69+
70+
mkdir -p "$OUT_DIR"/fake-config/llvm/Config
71+
touch "$OUT_DIR"/fake-config/llvm/Config/{abi-breaking,llvm-config}.h
72+
73+
# HACK(eddyb) we want standard `assert`s to work, but `NDEBUG` also controls
74+
# unrelated LLVM facilities that are spread all over the place and it's harder
75+
# to compile all of them, than do this workaround where we shadow `assert.h`.
76+
echo -e '#undef NDEBUG\n#include_next <assert.h>\n#define NDEBUG' \
77+
> "$OUT_DIR"/fake-config/assert.h
78+
79+
# HACK(eddyb) bypass `$llvm/include/llvm/Support/DataTypes.h.cmake`.
80+
mkdir -p "$OUT_DIR"/fake-config/llvm/Support
81+
echo -e '#include <'{math,inttypes,stdint,sys/types}'.h>\n' \
82+
> "$OUT_DIR"/fake-config/llvm/Support/DataTypes.h
83+
84+
# FIXME(eddyb) maybe split `$clang_codegen_flags` into front-end vs back-end.
85+
clang_codegen_flags="-g -fPIC -fno-exceptions -O3 -march=native"
86+
87+
# HACK(eddyb) first compile all the source files into one `.bc` file:
88+
# - "unity build" (w/ `--include`) lets `-o` specify path (no `--out-dir` sadly)
89+
# - LLVM `.bc` intermediate allows the steps below to reduce dependencies
90+
echo | clang++ -x c++ - -std=c++17 \
91+
$clang_codegen_flags \
92+
-I "$llvm"/include \
93+
-I "$OUT_DIR"/fake-config \
94+
-DNDEBUG \
95+
--include="$llvm"/lib/Support/{APInt,APFloat,SmallVector}.cpp \
96+
--include="$OUT_DIR"/cxx_apf_fuzz.cpp \
97+
-c -emit-llvm -o "$OUT_DIR"/cxx_apf_fuzz.bc
98+
99+
# HACK(eddyb) use the `internalize` pass (+ O3) to prune everything unexported.
100+
opt_passes='internalize,default<O3>'
101+
opt_flags=""
102+
# FIXME(eddyb) this was just the `internalize` hack, but had to move `sancov` here, to
103+
# replicate https://github.com/rust-fuzz/afl.rs/blob/8ece4f9/src/bin/cargo-afl.rs#L370-L372
104+
# *after* `internalize` & optimizations (to avoid instrumenting dead code).
105+
if [ "$cxx_apf_fuzz_is_fuzzing" == "1" ]; then
106+
opt_passes="$opt_passes,sancov-module"
107+
opt_flags="--sanitizer-coverage-level=3 \
108+
--sanitizer-coverage-trace-pc-guard \
109+
--sanitizer-coverage-prune-blocks=0"
110+
fi
111+
opt \
112+
--internalize-public-api-list="$cxx_apf_fuzz_exports" \
113+
--passes="$opt_passes" \
114+
$opt_flags \
115+
"$OUT_DIR"/cxx_apf_fuzz.bc \
116+
-o "$OUT_DIR"/cxx_apf_fuzz.opt.bc
117+
118+
# HACK(eddyb) let Clang do the rest of the work, from the pruned `.bc`.
119+
# FIXME(eddyb) maybe split `$clang_codegen_flags` into front-end vs back-end.
120+
clang++ $clang_codegen_flags \
121+
"$OUT_DIR"/cxx_apf_fuzz.opt.bc \
122+
-c -o "$OUT_DIR"/cxx_apf_fuzz.o
123+
124+
llvm-ar rc "$OUT_DIR"/libcxx_apf_fuzz.a "$OUT_DIR"/cxx_apf_fuzz.o
125+
126+
echo cargo:rustc-link-search=native="$OUT_DIR"
127+
echo cargo:rustc-link-lib=cxx_apf_fuzz
128+
echo cargo:rustc-link-lib=stdc++
129+
"#;

0 commit comments

Comments
 (0)