Halide 19.0.0
Halide compiler and libraries
|
Halide has a set of fuzz-testing harnesses that can be used to find those tricky to find, edge cases and bugs that would otherwise not be caught by a regular unit-testing suite. At the moment these fuzz-tests are housed in the test/fuzz
directory. The fuzz testing suite use the common, libfuzzer interface for fuzz-tests.
Fuzz testing requires specific instrumentation across the entire build; to do this we make use of a fuzzing-specific-toolchain/preset. e.g.
Note that the LLVM install that you use must be built with -D LLVM_ENABLE_RUNTIMES="compiler-rt"
set if you want to build the fuzzer tests (failing to do so will fail at configure time); not all prebuilt LLVM installs include this, so you may need to build LLVM from source to run the fuzz tests locally.
Fuzz-testing harnesses are a little different to a more traditional unit-test and don't have a definitive end of test. In other words, a fuzz test will run:
Once you have built the fuzz testing suite using the commands listed above you can list the fuzz testing harnesses using the command:
To run a fuzzer simply run the fuzz-testing harness with no arguments. e.g.
./build/test/fuzz/fuzz_simplify
By default this will run the fuzz test on a single core and discard whatever. temporary corpus is created.
To reuse a given corpus (recommended) create a new directory to store the corpus generated by your fuzz testing harness and pass that directory into your fuzzer e.g.
This will save the state of the fuzzer between runs, this way any progress that your fuzzer makes improving code-coverage will remain persistent on your disk.
Up until this point the fuzzer has only been running on a single core. To speed things up a little, let's run the fuzzer in parallel across all available cores on our machine.
An important part of fuzz testing is reproducing the crashing input. To handle this, a libfuzzer-based fuzz harness will create a crash file whenever the fuzzer exits unexpectedly. This will look something like:
crash-<some_random_hash>
To reproduce a crash we simply rerun our fuzz harness with our crash file as the first argument.
./build/test/fuzz/fuzz_simplify crash-<some_random_hash>
So long as your fuzz harness and library are deterministic this should reproduce the original crash.
A bare-bones fuzzer will look something like the following:
This assumes that our function foo takes in a buffer and the size of said buffer. But in many cases we would like to make use of more structured data. e.g. a string or a vector of integers etc. Thankfully libfuzzer provides a handy helper to convert a raw buffer into common structured data types, the FuzzedDataProvider class. For examples on how to use this class see test/fuzz/simplify.cpp
.