Skip to content

Commit 4856685

Browse files
committed
fuzz: Replace shelling out in build.rs with Rust code
1 parent b7d0c47 commit 4856685

1 file changed

Lines changed: 92 additions & 73 deletions

File tree

fuzz/build.rs

Lines changed: 92 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ fn main() -> io::Result<ExitCode> {
3333

3434
let manifest_dir =
3535
PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR unset"));
36-
let fake_include_dir = manifest_dir.join("cxx/include");
3736
let target_dir = env::var_os("CARGO_TARGET_DIR")
3837
.map(PathBuf::from)
3938
.unwrap_or_else(|| manifest_dir.parent().unwrap().join("target"));
40-
let llvm_dir = target_dir.join(format!("llvm-downloads/llvm-project-{llvm_commit_hash}"));
41-
if !llvm_dir.try_exists().is_ok_and(|val| val) {
39+
let llvm_root = target_dir.join(format!("llvm-downloads/llvm-project-{llvm_commit_hash}"));
40+
41+
if !llvm_root.try_exists().is_ok_and(|val| val) {
4242
panic!(
43-
"llvm dir `{llvm_dir:?}` does not exist or cannot be reached. \
43+
"llvm dir `{llvm_root:?}` does not exist or cannot be reached. \
4444
Perhaps you need to run etc/download-llvm.sh?"
4545
)
4646
}
@@ -59,74 +59,93 @@ fn main() -> io::Result<ExitCode> {
5959
}
6060
}
6161

62-
let sh_script_exit_status = Command::new("bash")
63-
.args(["-c", SH_SCRIPT])
64-
.env("llvm", &llvm_dir.join("llvm"))
65-
.env("manifest_dir", &manifest_dir)
66-
.envs([
67-
("llvm_project_git_hash", llvm_commit_hash),
68-
("cxx_apf_fuzz_exports", &cxx_exported_symbols.join(",")),
69-
(
70-
"cxx_apf_fuzz_is_fuzzing",
71-
if cfg!(fuzzing) { "1" } else { "0" },
72-
),
62+
let llvm_dir = llvm_root.join("llvm");
63+
let bc_out = out_dir.join("cxx_apf_fuzz.bc");
64+
let bc_opt_out = out_dir.join("cxx_apf_fuzz.opt.bc");
65+
let obj_out = out_dir.join("cxx_apf_fuzz.o");
66+
let archive = out_dir.join("libcxx_apf_fuzz.a");
67+
68+
// Flags could probably be split between the frontend and backend.
69+
let clang_codegen_flags = ["-g", "-fPIC", "-fno-exceptions", "-O3", "-march=native"];
70+
71+
// HACK(eddyb) first compile all the source files into one `.bc` file:
72+
// - "unity build" (w/ `--include`) lets `-o` specify path (no `--out-dir` sadly)
73+
// - LLVM `.bc` intermediate allows the steps below to reduce dependencies
74+
let mut clang = Command::new("clang++");
75+
clang
76+
.env_clear()
77+
.args(["-xc++", "-", "-std=c++17"])
78+
.args(clang_codegen_flags)
79+
.arg("-I")
80+
.arg(llvm_dir.join("include"))
81+
.arg("-I")
82+
.arg(llvm_dir.join("lib/Support"))
83+
.arg("-I")
84+
.arg(manifest_dir.join("cxx/include"))
85+
.args([
86+
"-DNDEBUG",
87+
"-DHAVE_UNISTD_H",
88+
"-DLLVM_ON_UNIX",
89+
"-DLLVM_ENABLE_THREADS=0",
7390
])
74-
.status()?;
75-
Ok(if sh_script_exit_status.success() {
76-
ExitCode::SUCCESS
77-
} else {
78-
ExitCode::FAILURE
79-
})
80-
}
91+
.arg("--include")
92+
.arg(manifest_dir.join("cxx/fuzz_unity_build.cpp"))
93+
.arg("--include")
94+
.arg(out_dir.join("cxx_apf_fuzz.cpp"))
95+
.args(["-c", "-emit-llvm", "-o"])
96+
.arg(&bc_out);
97+
println!("+ {clang:?}");
98+
assert!(clang.status()?.success());
99+
100+
// Use the `internalize` pass (+ O3) to prune everything unexported.
101+
let mut opt = Command::new("opt");
102+
opt.env_clear()
103+
.arg("--internalize-public-api-list")
104+
.arg(cxx_exported_symbols.join(","))
105+
.arg(&bc_out)
106+
.arg("-o")
107+
.arg(&bc_opt_out);
108+
109+
// FIXME this was just the `internalize` hack, but had to move `sancov` here, to
110+
// replicate https://github.com/rust-fuzz/afl.rs/blob/8ece4f9/src/bin/cargo-afl.rs#L370-L372
111+
// *after* `internalize` & optimizations (to avoid instrumenting dead code).
112+
let mut passes = "--passes=internalize,default<O3>".to_string();
113+
if cfg!(fuzzing) {
114+
opt.args([
115+
"--sanitizer-coverage-level=3",
116+
"--sanitizer-coverage-trace-pc-guard",
117+
"--sanitizer-coverage-prune-blocks=0",
118+
]);
119+
passes.push_str(",sancov-module");
120+
}
81121

82-
// HACK(eddyb) should avoid shelling out, but for now this will suffice.
83-
const SH_SCRIPT: &str = r#"
84-
set -eux
85-
86-
# FIXME(eddyb) maybe split `$clang_codegen_flags` into front-end vs back-end.
87-
clang_codegen_flags="-g -fPIC -fno-exceptions -O3 -march=native"
88-
89-
# HACK(eddyb) first compile all the source files into one `.bc` file:
90-
# - "unity build" (w/ `--include`) lets `-o` specify path (no `--out-dir` sadly)
91-
# - LLVM `.bc` intermediate allows the steps below to reduce dependencies
92-
echo | clang++ -x c++ - -std=c++17 \
93-
$clang_codegen_flags \
94-
-I "$llvm"/include \
95-
-I "$llvm"/lib/Support \
96-
-I "$manifest_dir/cxx/include" \
97-
-DNDEBUG -DHAVE_UNISTD_H -DLLVM_ON_UNIX -DLLVM_ENABLE_THREADS=0 \
98-
--include="$manifest_dir/cxx/fuzz_unity_build.cpp" \
99-
--include="$OUT_DIR"/cxx_apf_fuzz.cpp \
100-
-c -emit-llvm -o "$OUT_DIR"/cxx_apf_fuzz.bc
101-
102-
# HACK(eddyb) use the `internalize` pass (+ O3) to prune everything unexported.
103-
opt_passes='internalize,default<O3>'
104-
opt_flags=""
105-
# FIXME(eddyb) this was just the `internalize` hack, but had to move `sancov` here, to
106-
# replicate https://github.com/rust-fuzz/afl.rs/blob/8ece4f9/src/bin/cargo-afl.rs#L370-L372
107-
# *after* `internalize` & optimizations (to avoid instrumenting dead code).
108-
if [ "$cxx_apf_fuzz_is_fuzzing" == "1" ]; then
109-
opt_passes="$opt_passes,sancov-module"
110-
opt_flags="--sanitizer-coverage-level=3 \
111-
--sanitizer-coverage-trace-pc-guard \
112-
--sanitizer-coverage-prune-blocks=0"
113-
fi
114-
opt \
115-
--internalize-public-api-list="$cxx_apf_fuzz_exports" \
116-
--passes="$opt_passes" \
117-
$opt_flags \
118-
"$OUT_DIR"/cxx_apf_fuzz.bc \
119-
-o "$OUT_DIR"/cxx_apf_fuzz.opt.bc
120-
121-
# HACK(eddyb) let Clang do the rest of the work, from the pruned `.bc`.
122-
# FIXME(eddyb) maybe split `$clang_codegen_flags` into front-end vs back-end.
123-
clang++ $clang_codegen_flags \
124-
"$OUT_DIR"/cxx_apf_fuzz.opt.bc \
125-
-c -o "$OUT_DIR"/cxx_apf_fuzz.o
126-
127-
llvm-ar rc "$OUT_DIR"/libcxx_apf_fuzz.a "$OUT_DIR"/cxx_apf_fuzz.o
128-
129-
echo cargo:rustc-link-search=native="$OUT_DIR"
130-
echo cargo:rustc-link-lib=cxx_apf_fuzz
131-
echo cargo:rustc-link-lib=stdc++
132-
"#;
122+
opt.arg(passes);
123+
println!("+ {opt:?}");
124+
assert!(opt.status()?.success());
125+
126+
// Let Clang do the rest of the work, from the pruned `.bc`.
127+
let mut clang_final = Command::new("clang++");
128+
clang_final
129+
.env_clear()
130+
.args(clang_codegen_flags)
131+
.arg(bc_opt_out)
132+
.args(["-c", "-o"])
133+
.arg(&obj_out);
134+
eprintln!("+ {clang_final:?}");
135+
assert!(clang_final.status()?.success());
136+
137+
// Construct a linkable archive.
138+
let mut ar = Command::new("ar");
139+
ar.env_clear().arg("rc").arg(archive).arg(&obj_out);
140+
println!("+ {ar:?}");
141+
assert!(ar.status()?.success());
142+
143+
println!(
144+
"cargo:rustc-link-search=native={}",
145+
out_dir.to_str().unwrap()
146+
);
147+
println!("cargo:rustc-link-lib=cxx_apf_fuzz");
148+
println!("cargo:rustc-link-lib=stdc++");
149+
150+
Ok(ExitCode::SUCCESS)
151+
}

0 commit comments

Comments
 (0)