Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or https://opensource.org/license/mit/.5use std::collections::VecDeque;6use std::env;7use std::fs::{read_dir, DirEntry, File};8use std::path::{Path, PathBuf};9use std::process::{Command, ExitCode};10use std::str;11use std::thread;13/// A type for a complete and readable error message.14type AppError = String;15type AppResult = Result<(), AppError>;17const LLVM_PROFDATA: &str = "llvm-profdata";18const LLVM_COV: &str = "llvm-cov";19const GIT: &str = "git";21const DEFAULT_PAR: usize = 1;23fn exit_help(err: &str) -> AppError {24format!(25r#"26Error: {err}28Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name [parallelism={DEFAULT_PAR}]30Refer to the devtools/README.md for more details."#31)32}34fn sanity_check(corpora_dir: &Path, fuzz_exe: &Path) -> AppResult {35for tool in [LLVM_PROFDATA, LLVM_COV, GIT] {36let output = Command::new(tool).arg("--help").output();37match output {38Ok(output) if output.status.success() => {}39_ => Err(exit_help(&format!("The tool {tool} is not installed")))?,40}41}42if !corpora_dir.is_dir() {43Err(exit_help(&format!(44"Fuzz corpora path ({}) must be a directory",45corpora_dir.display()46)))?;47}48if !fuzz_exe.exists() {49Err(exit_help(&format!(50"Fuzz executable ({}) not found",51fuzz_exe.display()52)))?;53}54Ok(())55}57fn app() -> AppResult {58// Parse args59let args = env::args().collect::<Vec<_>>();60let build_dir = args.get(1).ok_or(exit_help("Must set build dir"))?;61if build_dir == "--help" {62Err(exit_help("--help requested"))?;63}64let corpora_dir = args.get(2).ok_or(exit_help("Must set fuzz corpora dir"))?;65let fuzz_target = args66.get(3)67// Require fuzz target for now. In the future it could be optional and the tool could68// iterate over all compiled fuzz targets69.ok_or(exit_help("Must set fuzz target"))?;70let par = match args.get(4) {71Some(s) => s72.parse::<usize>()73.map_err(|e| exit_help(&format!("Could not parse parallelism as usize ({s}): {e}")))?,74None => DEFAULT_PAR,75}76.max(1);77if args.get(5).is_some() {78Err(exit_help("Too many args"))?;79}81let build_dir = Path::new(build_dir);82let corpora_dir = Path::new(corpora_dir);83let fuzz_exe = build_dir.join("bin/fuzz");85sanity_check(corpora_dir, &fuzz_exe)?;87deterministic_coverage(build_dir, corpora_dir, &fuzz_exe, fuzz_target, par)88}90fn using_libfuzzer(fuzz_exe: &Path) -> Result<bool, AppError> {91println!("Check if using libFuzzer ...");92let stderr = Command::new(fuzz_exe)93.arg("-help=1") // Will be interpreted as option (libfuzzer) or as input file94.env("FUZZ", "addition_overflow") // Any valid target95.output()96.map_err(|e| format!("fuzz failed with {e}"))?97.stderr;98let help_output = str::from_utf8(&stderr)99.map_err(|e| format!("The libFuzzer -help=1 output must be valid text ({e})"))?;100Ok(help_output.contains("libFuzzer"))101}103fn deterministic_coverage(104build_dir: &Path,105corpora_dir: &Path,106fuzz_exe: &Path,107fuzz_target: &str,108par: usize,109) -> AppResult {110let using_libfuzzer = using_libfuzzer(fuzz_exe)?;111if using_libfuzzer {112println!("Warning: The fuzz executable was compiled with libFuzzer as sanitizer.");113println!("This tool may be tripped by libFuzzer misbehavior.");114println!("It is recommended to compile without libFuzzer.");115}116let corpus_dir = corpora_dir.join(fuzz_target);117let mut entries = read_dir(&corpus_dir)118.map_err(|err| {119exit_help(&format!(120"The fuzz target's input directory must exist! ({}; {})",121corpus_dir.display(),122err123))124})?125.map(|entry| entry.expect("IO error"))126.collect::<Vec<_>>();127entries.sort_by_key(|entry| entry.file_name());128let run_single = |run_id: char, entry: &Path, thread_id: usize| -> Result<PathBuf, AppError> {129let cov_txt_path = build_dir.join(format!("fuzz_det_cov.show.t{thread_id}.{run_id}.txt"));130let profraw_file = build_dir.join(format!("fuzz_det_cov.t{thread_id}.{run_id}.profraw"));131let profdata_file = build_dir.join(format!("fuzz_det_cov.t{thread_id}.{run_id}.profdata"));132{133let output = {134let mut cmd = Command::new(fuzz_exe);135if using_libfuzzer {136cmd.args(["-runs=1", "-shuffle=1", "-prefer_small=0"]);137}138cmd139}140.env("LLVM_PROFILE_FILE", &profraw_file)141.env("FUZZ", fuzz_target)142.arg(entry)143.output()144.map_err(|e| format!("fuzz failed: {e}"))?;145if !output.status.success() {146Err(format!(147"fuzz failed!\nstdout:\n{}\nstderr:\n{}\n",148String::from_utf8_lossy(&output.stdout),149String::from_utf8_lossy(&output.stderr)150))?;151}152}153if !Command::new(LLVM_PROFDATA)154.arg("merge")155.arg("--sparse")156.arg(&profraw_file)157.arg("-o")158.arg(&profdata_file)159.status()160.map_err(|e| format!("{LLVM_PROFDATA} merge failed with {e}"))?161.success()162{163Err(format!("{LLVM_PROFDATA} merge failed. This can be a sign of compiling without code coverage support."))?;164}165let cov_file = File::create(&cov_txt_path)166.map_err(|e| format!("Failed to create coverage txt file ({e})"))?;167if !Command::new(LLVM_COV)168.args([169"show",170"--show-line-counts-or-regions",171"--show-branches=count",172"--show-expansions",173"--show-instantiation-summary",174"-Xdemangler=llvm-cxxfilt",175&format!("--instr-profile={}", profdata_file.display()),176])177.arg(fuzz_exe)178.stdout(cov_file)179.spawn()180.map_err(|e| format!("{LLVM_COV} show failed with {e}"))?181.wait()182.map_err(|e| format!("{LLVM_COV} show failed with {e}"))?183.success()184{185Err(format!("{LLVM_COV} show failed"))?;186};187Ok(cov_txt_path)188};189let check_diff = |a: &Path, b: &Path, err: &str| -> AppResult {190let same = Command::new(GIT)191.args(["--no-pager", "diff", "--no-index"])192.arg(a)193.arg(b)194.status()195.map_err(|e| format!("{GIT} diff failed with {e}"))?196.success();197if !same {198Err(format!(199r#"200The coverage was not deterministic between runs.201{err}"#202))?;203}204Ok(())205};206// First, check that each fuzz input is deterministic running by itself in a process.207//208// This can catch issues and isolate where a single fuzz input triggers non-determinism, but209// all other fuzz inputs are deterministic.210//211// Also, This can catch issues where several fuzz inputs are non-deterministic, but the sum of212// their overall coverage trace remains the same across runs and thus remains undetected.213println!(214"Check each fuzz input individually ... ({} inputs with parallelism {par})",215entries.len()216);217let check_individual = |entry: &DirEntry, thread_id: usize| -> AppResult {218let entry = entry.path();219if !entry.is_file() {220Err(format!("{} should be a file", entry.display()))?;221}222let cov_txt_base = run_single('a', &entry, thread_id)?;223let cov_txt_repeat = run_single('b', &entry, thread_id)?;224check_diff(225&cov_txt_base,226&cov_txt_repeat,227&format!("The fuzz target input was {}.", entry.display()),228)?;229Ok(())230};231thread::scope(|s| -> AppResult {232let mut handles = VecDeque::with_capacity(par);233let mut res = Ok(());234for (i, entry) in entries.iter().enumerate() {235println!("[{}/{}]", i + 1, entries.len());236handles.push_back(s.spawn(move || check_individual(entry, i % par)));237while handles.len() >= par || i == (entries.len() - 1) || res.is_err() {238if let Some(th) = handles.pop_front() {239let thread_result = match th.join() {240Err(_e) => Err("A scoped thread panicked".to_string()),241Ok(r) => r,242};243if thread_result.is_err() {244res = thread_result;245}246} else {247return res;248}249}250}
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.