Cranelift CLIF-level differential fuzzer (#3038)
* cranelift: Initial fuzzer implementation * cranelift: Generate multiple test cases in fuzzer * cranelift: Separate function generator in fuzzer * cranelift: Insert random instructions in fuzzer * cranelift: Rename gen_testcase * cranelift: Implement div for unsigned values in interpreter * cranelift: Run all test cases in fuzzer * cranelift: Comment options in function_runner * cranelift: Improve fuzzgen README.md * cranelift: Fuzzgen remove unused variable * cranelift: Fuzzer code style fixes Thanks! @bjorn3 * cranelift: Fix nits in CLIF fuzzer Thanks @cfallin! * cranelift: Implement Arbitrary for TestCase * cranelift: Remove gen_testcase * cranelift: Move fuzzers to wasmtime fuzz directory * cranelift: CLIF-Fuzzer ignore tests that produce traps * cranelift: CLIF-Fuzzer create new fuzz target to validate generated testcases * cranelift: Store clif-fuzzer config in a separate struct * cranelift: Generate variables upfront per function * cranelift: Prevent publishing of fuzzgen crate
This commit is contained in:
11
fuzz/fuzz_targets/cranelift-fuzzgen-verify.rs
Normal file
11
fuzz/fuzz_targets/cranelift-fuzzgen-verify.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use cranelift_codegen::{settings, verify_function};
|
||||
use cranelift_fuzzgen::TestCase;
|
||||
|
||||
fuzz_target!(|testcase: TestCase| {
|
||||
let flags = settings::Flags::new(settings::builder());
|
||||
verify_function(&testcase.func, &flags).unwrap();
|
||||
});
|
||||
85
fuzz/fuzz_targets/cranelift-fuzzgen.rs
Normal file
85
fuzz/fuzz_targets/cranelift-fuzzgen.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use cranelift_codegen::data_value::DataValue;
|
||||
use cranelift_filetests::function_runner::{CompiledFunction, SingleFunctionCompiler};
|
||||
use cranelift_fuzzgen::*;
|
||||
use cranelift_interpreter::environment::FuncIndex;
|
||||
use cranelift_interpreter::environment::FunctionStore;
|
||||
use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
|
||||
use cranelift_interpreter::step::ControlFlow;
|
||||
use cranelift_interpreter::step::CraneliftTrap;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RunResult {
|
||||
Success(Vec<DataValue>),
|
||||
Trap(CraneliftTrap),
|
||||
Error(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
impl RunResult {
|
||||
pub fn unwrap(self) -> Vec<DataValue> {
|
||||
match self {
|
||||
RunResult::Success(d) => d,
|
||||
_ => panic!("Expected RunResult::Success in unwrap but got: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunResult {
|
||||
// The entrypoint function is always 0
|
||||
let index = FuncIndex::from_u32(0);
|
||||
let res = interpreter.call_by_index(index, args);
|
||||
|
||||
match res {
|
||||
Ok(ControlFlow::Return(results)) => RunResult::Success(results.to_vec()),
|
||||
Ok(ControlFlow::Trap(trap)) => RunResult::Trap(trap),
|
||||
Ok(cf) => RunResult::Error(format!("Unrecognized exit ControlFlow: {:?}", cf).into()),
|
||||
Err(e) => RunResult::Error(format!("InterpreterError: {:?}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_host(compiled_fn: &CompiledFunction, args: &[DataValue]) -> RunResult {
|
||||
let res = compiled_fn.call(args);
|
||||
RunResult::Success(res)
|
||||
}
|
||||
|
||||
fuzz_target!(|testcase: TestCase| {
|
||||
let mut interpreter = {
|
||||
let mut env = FunctionStore::default();
|
||||
env.add(testcase.func.name.to_string(), &testcase.func);
|
||||
|
||||
let state = InterpreterState::default().with_function_store(env);
|
||||
let interpreter = Interpreter::new(state);
|
||||
interpreter
|
||||
};
|
||||
|
||||
// Native fn
|
||||
let mut host_compiler = SingleFunctionCompiler::with_default_host_isa();
|
||||
let compiled_fn = host_compiler.compile(testcase.func.clone()).unwrap();
|
||||
|
||||
for args in &testcase.inputs {
|
||||
let int_res = run_in_interpreter(&mut interpreter, args);
|
||||
match int_res {
|
||||
RunResult::Success(_) => {}
|
||||
RunResult::Trap(_) => {
|
||||
// We currently ignore inputs that trap the interpreter
|
||||
// We could catch traps in the host run and compare them to the
|
||||
// interpreter traps, but since we already test trap cases with
|
||||
// wasm tests and wasm-level fuzzing, the amount of effort does
|
||||
// not justify implementing it again here.
|
||||
return;
|
||||
}
|
||||
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
||||
}
|
||||
|
||||
let host_res = run_in_host(&compiled_fn, args);
|
||||
match host_res {
|
||||
RunResult::Success(_) => {}
|
||||
_ => panic!("host failed: {:?}", host_res),
|
||||
}
|
||||
|
||||
assert_eq!(int_res.unwrap(), host_res.unwrap());
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user