fuzzgen: Move Arbitrary structs into the fuzzers (#5820)
* fuzzgen: Move `FunctionWithIsa` to icache fuzzer * fuzzgen: Move `Testcase` to fuzzgen fuzzer * fuzzgen: Move allowed libcalls to fuzzers * fuzzgen: Centralize printing of testcases
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
#![no_main]
|
||||
|
||||
use cranelift_codegen::ir::Function;
|
||||
use cranelift_codegen::ir::Signature;
|
||||
use cranelift_codegen::ir::UserExternalName;
|
||||
use cranelift_codegen::ir::UserFuncName;
|
||||
use libfuzzer_sys::arbitrary;
|
||||
use libfuzzer_sys::arbitrary::Arbitrary;
|
||||
use libfuzzer_sys::arbitrary::Unstructured;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use cranelift_codegen::data_value::DataValue;
|
||||
use cranelift_codegen::ir::{LibCall, TrapCode};
|
||||
use cranelift_codegen::isa;
|
||||
use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline};
|
||||
use cranelift_fuzzgen::*;
|
||||
use cranelift_interpreter::environment::FuncIndex;
|
||||
@@ -17,6 +26,7 @@ use cranelift_interpreter::interpreter::{
|
||||
};
|
||||
use cranelift_interpreter::step::ControlFlow;
|
||||
use cranelift_interpreter::step::CraneliftTrap;
|
||||
use cranelift_native::builder_with_options;
|
||||
use smallvec::smallvec;
|
||||
|
||||
const INTERPRETER_FUEL: u64 = 4096;
|
||||
@@ -120,6 +130,87 @@ impl PartialEq for RunResult {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestCase {
|
||||
/// TargetIsa to use when compiling this test case
|
||||
pub isa: isa::OwnedTargetIsa,
|
||||
/// Functions under test
|
||||
/// By convention the first function is the main function.
|
||||
pub functions: Vec<Function>,
|
||||
/// Generate multiple test inputs for each test case.
|
||||
/// This allows us to get more coverage per compilation, which may be somewhat expensive.
|
||||
pub inputs: Vec<TestCaseInput>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TestCase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
PrintableTestCase::run(&self.isa, &self.functions, &self.inputs).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Arbitrary<'a> for TestCase {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCase {
|
||||
pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {
|
||||
let mut gen = FuzzGen::new(u);
|
||||
|
||||
// TestCase is meant to be consumed by a runner, so we make the assumption here that we're
|
||||
// generating a TargetIsa for the host.
|
||||
let builder =
|
||||
builder_with_options(true).expect("Unable to build a TargetIsa for the current host");
|
||||
let flags = gen.generate_flags(builder.triple().architecture)?;
|
||||
let isa = builder.finish(flags)?;
|
||||
|
||||
// When generating functions, we allow each function to call any function that has
|
||||
// already been generated. This guarantees that we never have loops in the call graph.
|
||||
// We generate these backwards, and then reverse them so that the main function is at
|
||||
// the start.
|
||||
let func_count = gen.u.int_in_range(gen.config.testcase_funcs.clone())?;
|
||||
let mut functions: Vec<Function> = Vec::with_capacity(func_count);
|
||||
for i in (0..func_count).rev() {
|
||||
// Function name must be in a different namespace than TESTFILE_NAMESPACE (0)
|
||||
let fname = UserFuncName::user(1, i as u32);
|
||||
|
||||
let usercalls: Vec<(UserExternalName, Signature)> = functions
|
||||
.iter()
|
||||
.map(|f| {
|
||||
(
|
||||
f.name.get_user().unwrap().clone(),
|
||||
f.stencil.signature.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let func = gen.generate_func(
|
||||
fname,
|
||||
isa.triple().clone(),
|
||||
usercalls,
|
||||
ALLOWED_LIBCALLS.to_vec(),
|
||||
)?;
|
||||
functions.push(func);
|
||||
}
|
||||
// Now reverse the functions so that the main function is at the start.
|
||||
functions.reverse();
|
||||
|
||||
let main = &functions[0];
|
||||
let inputs = gen.generate_test_inputs(&main.signature)?;
|
||||
|
||||
Ok(TestCase {
|
||||
isa,
|
||||
functions,
|
||||
inputs,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the main function of this test case.
|
||||
pub fn main(&self) -> &Function {
|
||||
&self.functions[0]
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunResult {
|
||||
// The entrypoint function is always 0
|
||||
let index = FuncIndex::from_u32(0);
|
||||
@@ -139,6 +230,16 @@ fn run_in_host(trampoline: &Trampoline, args: &[DataValue]) -> RunResult {
|
||||
RunResult::Success(res)
|
||||
}
|
||||
|
||||
/// These libcalls need a interpreter implementation in `build_interpreter`
|
||||
const ALLOWED_LIBCALLS: &'static [LibCall] = &[
|
||||
LibCall::CeilF32,
|
||||
LibCall::CeilF64,
|
||||
LibCall::FloorF32,
|
||||
LibCall::FloorF64,
|
||||
LibCall::TruncF32,
|
||||
LibCall::TruncF64,
|
||||
];
|
||||
|
||||
fn build_interpreter(testcase: &TestCase) -> Interpreter {
|
||||
let mut env = FunctionStore::default();
|
||||
for func in testcase.functions.iter() {
|
||||
|
||||
@@ -3,13 +3,103 @@
|
||||
use cranelift_codegen::{
|
||||
cursor::{Cursor, FuncCursor},
|
||||
incremental_cache as icache,
|
||||
ir::{self, immediates::Imm64, ExternalName},
|
||||
Context,
|
||||
ir::{
|
||||
self, immediates::Imm64, ExternalName, Function, LibCall, Signature, UserExternalName,
|
||||
UserFuncName,
|
||||
},
|
||||
isa, Context,
|
||||
};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{
|
||||
arbitrary::{self, Arbitrary, Unstructured},
|
||||
fuzz_target,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use cranelift_fuzzgen::*;
|
||||
|
||||
/// TODO: This *almost* could be replaced with `LibCall::all()`, but
|
||||
/// `LibCall::signature` panics for some libcalls, so we need to avoid that.
|
||||
const ALLOWED_LIBCALLS: &'static [LibCall] = &[
|
||||
LibCall::CeilF32,
|
||||
LibCall::CeilF64,
|
||||
LibCall::FloorF32,
|
||||
LibCall::FloorF64,
|
||||
LibCall::TruncF32,
|
||||
LibCall::TruncF64,
|
||||
LibCall::NearestF32,
|
||||
LibCall::NearestF64,
|
||||
LibCall::FmaF32,
|
||||
LibCall::FmaF64,
|
||||
];
|
||||
|
||||
/// A generated function with an ISA that targets one of cranelift's backends.
|
||||
pub struct FunctionWithIsa {
|
||||
/// TargetIsa to use when compiling this test case
|
||||
pub isa: isa::OwnedTargetIsa,
|
||||
|
||||
/// Function under test
|
||||
pub func: Function,
|
||||
}
|
||||
|
||||
impl FunctionWithIsa {
|
||||
pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {
|
||||
// We filter out targets that aren't supported in the current build
|
||||
// configuration after randomly choosing one, instead of randomly choosing
|
||||
// a supported one, so that the same fuzz input works across different build
|
||||
// configurations.
|
||||
let target = u.choose(isa::ALL_ARCHITECTURES)?;
|
||||
let builder = isa::lookup_by_name(target).map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||
let architecture = builder.triple().architecture;
|
||||
|
||||
let mut gen = FuzzGen::new(u);
|
||||
let flags = gen
|
||||
.generate_flags(architecture)
|
||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||
let isa = builder
|
||||
.finish(flags)
|
||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||
|
||||
// Function name must be in a different namespace than TESTFILE_NAMESPACE (0)
|
||||
let fname = UserFuncName::user(1, 0);
|
||||
|
||||
// We don't actually generate these functions, we just simulate their signatures and names
|
||||
let func_count = gen.u.int_in_range(gen.config.testcase_funcs.clone())?;
|
||||
let usercalls = (0..func_count)
|
||||
.map(|i| {
|
||||
let name = UserExternalName::new(2, i as u32);
|
||||
let sig = gen.generate_signature(architecture)?;
|
||||
Ok((name, sig))
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<(UserExternalName, Signature)>>>()
|
||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||
|
||||
let func = gen
|
||||
.generate_func(
|
||||
fname,
|
||||
isa.triple().clone(),
|
||||
usercalls,
|
||||
ALLOWED_LIBCALLS.to_vec(),
|
||||
)
|
||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||
|
||||
Ok(FunctionWithIsa { isa, func })
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FunctionWithIsa {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// TODO: We could avoid the clone here.
|
||||
let funcs = &[self.func.clone()];
|
||||
PrintableTestCase::compile(&self.isa, funcs).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Arbitrary<'a> for FunctionWithIsa {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
Self::generate(u).map_err(|_| arbitrary::Error::IncorrectFormat)
|
||||
}
|
||||
}
|
||||
|
||||
fuzz_target!(|func: FunctionWithIsa| {
|
||||
let FunctionWithIsa { mut func, isa } = func;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user