xref: /wasmtime-44.0.1/fuzz/build.rs (revision 4e31cb03)
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