1 fn main() -> anyhow::Result<()> { 2 component::generate_static_api_tests()?; 3 4 Ok(()) 5 } 6 7 mod component { 8 use anyhow::{Context, Error, Result, anyhow}; 9 use arbitrary::Unstructured; 10 use proc_macro2::TokenStream; 11 use quote::quote; 12 use rand::rngs::StdRng; 13 use rand::{Rng, SeedableRng}; 14 use std::collections::HashMap; 15 use std::env; 16 use std::fmt::Write; 17 use std::fs; 18 use std::iter; 19 use std::path::PathBuf; 20 use std::process::Command; 21 use wasmtime_test_util::component_fuzz::{Declarations, MAX_TYPE_DEPTH, TestCase, Type}; 22 23 pub fn generate_static_api_tests() -> Result<()> { 24 println!("cargo:rerun-if-changed=build.rs"); 25 let out_dir = PathBuf::from( 26 env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"), 27 ); 28 29 let mut out = String::new(); 30 write_static_api_tests(&mut out)?; 31 32 let output = out_dir.join("static_component_api.rs"); 33 fs::write(&output, out)?; 34 35 drop(Command::new("rustfmt").arg(&output).status()); 36 37 Ok(()) 38 } 39 40 fn write_static_api_tests(out: &mut String) -> Result<()> { 41 println!("cargo:rerun-if-env-changed=WASMTIME_FUZZ_SEED"); 42 let seed = if let Ok(seed) = env::var("WASMTIME_FUZZ_SEED") { 43 seed.parse::<u64>() 44 .with_context(|| anyhow!("expected u64 in WASMTIME_FUZZ_SEED"))? 45 } else { 46 StdRng::from_entropy().r#gen() 47 }; 48 49 eprintln!( 50 "using seed {seed} (set WASMTIME_FUZZ_SEED={seed} in your environment to reproduce)" 51 ); 52 53 let mut rng = StdRng::seed_from_u64(seed); 54 55 const TYPE_COUNT: usize = 50; 56 const TEST_CASE_COUNT: usize = 100; 57 58 let mut type_fuel = 1000; 59 let mut types = Vec::new(); 60 let mut rust_type_names = Vec::new(); 61 let name_counter = &mut 0; 62 let mut declarations = TokenStream::new(); 63 let mut tests = TokenStream::new(); 64 65 // First generate a set of type to select from. 66 for _ in 0..TYPE_COUNT { 67 let ty = generate(&mut rng, |u| { 68 // Only discount fuel if the generation was successful, 69 // otherwise we'll get more random data and try again. 70 let mut fuel = type_fuel; 71 let ret = Type::generate(u, MAX_TYPE_DEPTH, &mut fuel); 72 if ret.is_ok() { 73 type_fuel = fuel; 74 } 75 ret 76 })?; 77 78 let rust_ty_name = 79 wasmtime_test_util::component_fuzz::rust_type(&ty, name_counter, &mut declarations); 80 types.push(ty); 81 rust_type_names.push(rust_ty_name); 82 } 83 84 fn hash_key(ty: &Type) -> usize { 85 let ty: *const Type = ty; 86 ty.addr() 87 } 88 89 let type_to_name_map = types 90 .iter() 91 .map(hash_key) 92 .zip(rust_type_names.iter().cloned()) 93 .collect::<HashMap<_, _>>(); 94 95 // Next generate a set of static API test cases driven by the above 96 // types. 97 for index in 0..TEST_CASE_COUNT { 98 let (case, rust_params, rust_results) = generate(&mut rng, |u| { 99 let mut rust_params = TokenStream::new(); 100 let mut rust_results = TokenStream::new(); 101 let case = TestCase::generate(&types, u)?; 102 for ty in case.params.iter() { 103 let name = &type_to_name_map[&hash_key(ty)]; 104 rust_params.extend(name.clone()); 105 rust_params.extend(quote!(,)); 106 } 107 if let Some(ty) = &case.result { 108 let name = &type_to_name_map[&hash_key(ty)]; 109 rust_results.extend(name.clone()); 110 rust_results.extend(quote!(,)); 111 } 112 Ok((case, rust_params, rust_results)) 113 })?; 114 115 let Declarations { 116 types, 117 type_instantiation_args, 118 params, 119 results, 120 caller_module, 121 callee_module, 122 options, 123 } = case.declarations(); 124 125 let test = quote!(#index => component_api::static_api_test::<(#rust_params), (#rust_results)>( 126 input, 127 { 128 static DECLS: Declarations = Declarations { 129 types: Cow::Borrowed(#types), 130 type_instantiation_args: Cow::Borrowed(#type_instantiation_args), 131 params: Cow::Borrowed(#params), 132 results: Cow::Borrowed(#results), 133 caller_module: Cow::Borrowed(#caller_module), 134 callee_module: Cow::Borrowed(#callee_module), 135 options: #options, 136 }; 137 &DECLS 138 } 139 ),); 140 141 tests.extend(test); 142 } 143 144 let module = quote! { 145 #[allow(unused_imports, reason = "macro-generated code")] 146 fn static_component_api_target(input: &mut libfuzzer_sys::arbitrary::Unstructured) -> libfuzzer_sys::arbitrary::Result<()> { 147 use anyhow::Result; 148 use wasmtime_test_util::component_fuzz::Declarations; 149 use wasmtime_test_util::component::{Float32, Float64}; 150 use libfuzzer_sys::arbitrary::{self, Arbitrary}; 151 use std::borrow::Cow; 152 use std::sync::{Arc, Once}; 153 use wasmtime::component::{ComponentType, Lift, Lower}; 154 use wasmtime_fuzzing::oracles::component_api; 155 156 const SEED: u64 = #seed; 157 158 static ONCE: Once = Once::new(); 159 160 ONCE.call_once(|| { 161 eprintln!( 162 "Seed {SEED} was used to generate static component API fuzz tests.\n\ 163 Set WASMTIME_FUZZ_SEED={SEED} in your environment at build time to reproduce." 164 ); 165 }); 166 167 #declarations 168 169 match input.int_in_range(0..=(#TEST_CASE_COUNT-1))? { 170 #tests 171 _ => unreachable!() 172 } 173 } 174 }; 175 176 write!(out, "{module}")?; 177 178 Ok(()) 179 } 180 181 fn generate<T>( 182 rng: &mut StdRng, 183 mut f: impl FnMut(&mut Unstructured<'_>) -> arbitrary::Result<T>, 184 ) -> Result<T> { 185 let mut bytes = Vec::new(); 186 loop { 187 let count = rng.gen_range(1000..2000); 188 bytes.extend(iter::repeat_with(|| rng.r#gen::<u8>()).take(count)); 189 190 match f(&mut Unstructured::new(&bytes)) { 191 Ok(ret) => break Ok(ret), 192 Err(arbitrary::Error::NotEnoughData) => (), 193 Err(error) => break Err(Error::from(error)), 194 } 195 } 196 } 197 } 198