main() -> wasmtime::Result<()>1 fn main() -> wasmtime::Result<()> {
2 component::generate_static_api_tests()?;
3
4 Ok(())
5 }
6
7 mod component {
8 use arbitrary::Unstructured;
9 use proc_macro2::TokenStream;
10 use quote::quote;
11 use rand::rngs::StdRng;
12 use rand::{Rng, SeedableRng};
13 use std::collections::HashMap;
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::{Error, Result, error::Context as _, format_err};
21 use wasmtime_test_util::component_fuzz::{Declarations, MAX_TYPE_DEPTH, TestCase, Type};
22
generate_static_api_tests() -> Result<()>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
write_static_api_tests(out: &mut String) -> Result<()>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(|| format_err!("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 wasmtime::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
generate<T>( rng: &mut StdRng, mut f: impl FnMut(&mut Unstructured<'_>) -> arbitrary::Result<T>, ) -> Result<T>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