You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
6.5 KiB
192 lines
6.5 KiB
fn main() -> anyhow::Result<()> {
|
|
component::generate_static_api_tests()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
mod component {
|
|
use anyhow::{anyhow, Context, Error, Result};
|
|
use arbitrary::Unstructured;
|
|
use component_fuzz_util::{Declarations, TestCase, Type, MAX_TYPE_DEPTH};
|
|
use proc_macro2::TokenStream;
|
|
use quote::quote;
|
|
use rand::rngs::StdRng;
|
|
use rand::{Rng, SeedableRng};
|
|
use std::env;
|
|
use std::fmt::Write;
|
|
use std::fs;
|
|
use std::iter;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
pub fn generate_static_api_tests() -> Result<()> {
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
let out_dir = PathBuf::from(
|
|
env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
|
|
);
|
|
|
|
let mut out = String::new();
|
|
write_static_api_tests(&mut out)?;
|
|
|
|
let output = out_dir.join("static_component_api.rs");
|
|
fs::write(&output, out)?;
|
|
|
|
drop(Command::new("rustfmt").arg(&output).status());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_static_api_tests(out: &mut String) -> Result<()> {
|
|
let seed = if let Ok(seed) = env::var("WASMTIME_FUZZ_SEED") {
|
|
seed.parse::<u64>()
|
|
.with_context(|| anyhow!("expected u64 in WASMTIME_FUZZ_SEED"))?
|
|
} else {
|
|
StdRng::from_entropy().gen()
|
|
};
|
|
|
|
eprintln!(
|
|
"using seed {seed} (set WASMTIME_FUZZ_SEED={seed} in your environment to reproduce)"
|
|
);
|
|
|
|
let mut rng = StdRng::seed_from_u64(seed);
|
|
|
|
const TYPE_COUNT: usize = 50;
|
|
const MAX_ARITY: u32 = 5;
|
|
const TEST_CASE_COUNT: usize = 100;
|
|
|
|
let mut type_fuel = 1000;
|
|
let mut types = Vec::new();
|
|
let name_counter = &mut 0;
|
|
let mut declarations = TokenStream::new();
|
|
let mut tests = TokenStream::new();
|
|
|
|
// First generate a set of type to select from.
|
|
for _ in 0..TYPE_COUNT {
|
|
let ty = gen(&mut rng, |u| {
|
|
// Only discount fuel if the generation was successful,
|
|
// otherwise we'll get more random data and try again.
|
|
let mut fuel = type_fuel;
|
|
let ret = Type::generate(u, MAX_TYPE_DEPTH, &mut fuel);
|
|
if ret.is_ok() {
|
|
type_fuel = fuel;
|
|
}
|
|
ret
|
|
})?;
|
|
|
|
let name = component_fuzz_util::rust_type(&ty, name_counter, &mut declarations);
|
|
types.push((name, ty));
|
|
}
|
|
|
|
// Next generate a set of static API test cases driven by the above
|
|
// types.
|
|
for index in 0..TEST_CASE_COUNT {
|
|
let (case, rust_params, rust_results) = gen(&mut rng, |u| {
|
|
let mut params = Vec::new();
|
|
let mut results = Vec::new();
|
|
let mut rust_params = TokenStream::new();
|
|
let mut rust_results = TokenStream::new();
|
|
for _ in 0..u.int_in_range(0..=MAX_ARITY)? {
|
|
let (name, ty) = u.choose(&types)?;
|
|
params.push(ty);
|
|
rust_params.extend(name.clone());
|
|
rust_params.extend(quote!(,));
|
|
}
|
|
for _ in 0..u.int_in_range(0..=MAX_ARITY)? {
|
|
let (name, ty) = u.choose(&types)?;
|
|
results.push(ty);
|
|
rust_results.extend(name.clone());
|
|
rust_results.extend(quote!(,));
|
|
}
|
|
|
|
let case = TestCase {
|
|
params,
|
|
results,
|
|
encoding1: u.arbitrary()?,
|
|
encoding2: u.arbitrary()?,
|
|
};
|
|
Ok((case, rust_params, rust_results))
|
|
})?;
|
|
|
|
let Declarations {
|
|
types,
|
|
type_instantiation_args,
|
|
params,
|
|
results,
|
|
import_and_export,
|
|
encoding1,
|
|
encoding2,
|
|
} = case.declarations();
|
|
|
|
let test = quote!(#index => component_types::static_api_test::<(#rust_params), (#rust_results)>(
|
|
input,
|
|
{
|
|
static DECLS: Declarations = Declarations {
|
|
types: Cow::Borrowed(#types),
|
|
type_instantiation_args: Cow::Borrowed(#type_instantiation_args),
|
|
params: Cow::Borrowed(#params),
|
|
results: Cow::Borrowed(#results),
|
|
import_and_export: Cow::Borrowed(#import_and_export),
|
|
encoding1: #encoding1,
|
|
encoding2: #encoding2,
|
|
};
|
|
&DECLS
|
|
}
|
|
),);
|
|
|
|
tests.extend(test);
|
|
}
|
|
|
|
let module = quote! {
|
|
#[allow(unused_imports)]
|
|
fn static_component_api_target(input: &mut libfuzzer_sys::arbitrary::Unstructured) -> libfuzzer_sys::arbitrary::Result<()> {
|
|
use anyhow::Result;
|
|
use component_fuzz_util::Declarations;
|
|
use component_test_util::{self, Float32, Float64};
|
|
use libfuzzer_sys::arbitrary::{self, Arbitrary};
|
|
use std::borrow::Cow;
|
|
use std::sync::{Arc, Once};
|
|
use wasmtime::component::{ComponentType, Lift, Lower};
|
|
use wasmtime_fuzzing::generators::component_types;
|
|
|
|
const SEED: u64 = #seed;
|
|
|
|
static ONCE: Once = Once::new();
|
|
|
|
ONCE.call_once(|| {
|
|
eprintln!(
|
|
"Seed {SEED} was used to generate static component API fuzz tests.\n\
|
|
Set WASMTIME_FUZZ_SEED={SEED} in your environment at build time to reproduce."
|
|
);
|
|
});
|
|
|
|
#declarations
|
|
|
|
match input.int_in_range(0..=(#TEST_CASE_COUNT-1))? {
|
|
#tests
|
|
_ => unreachable!()
|
|
}
|
|
}
|
|
};
|
|
|
|
write!(out, "{module}")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn gen<T>(
|
|
rng: &mut StdRng,
|
|
mut f: impl FnMut(&mut Unstructured<'_>) -> arbitrary::Result<T>,
|
|
) -> Result<T> {
|
|
let mut bytes = Vec::new();
|
|
loop {
|
|
let count = rng.gen_range(1000..2000);
|
|
bytes.extend(iter::repeat_with(|| rng.gen::<u8>()).take(count));
|
|
|
|
match f(&mut Unstructured::new(&bytes)) {
|
|
Ok(ret) => break Ok(ret),
|
|
Err(arbitrary::Error::NotEnoughData) => (),
|
|
Err(error) => break Err(Error::from(error)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|