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