* Implement variant translation in fused adapters This commit implements the most general case of variants for fused adapter trampolines. Additionally a number of other primitive types are filled out here to assist with testing variants. The implementation internally was relatively straightforward given the shape of variants, but there's room for future optimization as necessary especially around converting locals to various types. This commit also introduces a "one off" fuzzer for adapters to ensure that the generated adapter is valid. I hope to extend this fuzz generator as more types are implemented to assist in various corner cases that might arise. For now the fuzzer simply tests that the output wasm module is valid, not that it actually executes correctly. I hope to integrate with a fuzzer along the lines of #4307 one day to test the run-time-correctness of the generated adapters as well, at which point this fuzzer would become obsolete. Finally this commit also fixes an issue with `u8` translation where upper bits weren't zero'd out and were passed raw across modules. Instead smaller-than-32 types now all mask out their upper bits and do sign-extension as appropriate for unsigned/signed variants. * Fuzz memory64 in the new trampoline fuzzer Currently memory64 isn't supported elsewhere in the component model implementation of Wasmtime but the trampoline compiler seems as good a place as any to ensure that it at least works in isolation. This plumbs through fuzz input into a `memory64` boolean which gets fed into compilation. Some miscellaneous bugs were fixed as a result to ensure that memory64 trampolines all validate correctly. * Tweak manifest for doc build
253 lines
7.8 KiB
Rust
253 lines
7.8 KiB
Rust
//! A simple fuzzer for FACT
|
|
//!
|
|
//! This is an intentionally small fuzzer which is intended to only really be
|
|
//! used during the development of FACT itself when generating adapter modules.
|
|
//! This creates arbitrary adapter signatures and then generates the required
|
|
//! trampoline for that adapter ensuring that the final output wasm module is a
|
|
//! valid wasm module. This doesn't actually validate anything about the
|
|
//! correctness of the trampoline, only that it's valid wasm.
|
|
|
|
#![no_main]
|
|
|
|
use arbitrary::{Arbitrary, Unstructured};
|
|
use libfuzzer_sys::fuzz_target;
|
|
use std::fmt;
|
|
use wasmparser::{Validator, WasmFeatures};
|
|
use wasmtime_environ::component::*;
|
|
use wasmtime_environ::fact::Module;
|
|
|
|
#[derive(Arbitrary, Debug)]
|
|
struct GenAdapterModule {
|
|
debug: bool,
|
|
adapters: Vec<GenAdapter>,
|
|
}
|
|
|
|
#[derive(Arbitrary, Debug)]
|
|
struct GenAdapter {
|
|
ty: FuncType,
|
|
post_return: bool,
|
|
lift_memory64: bool,
|
|
lower_memory64: bool,
|
|
lift_encoding: GenStringEncoding,
|
|
lower_encoding: GenStringEncoding,
|
|
}
|
|
|
|
#[derive(Arbitrary, Debug)]
|
|
struct FuncType {
|
|
params: Vec<ValType>,
|
|
result: ValType,
|
|
}
|
|
|
|
#[derive(Arbitrary, Debug)]
|
|
enum ValType {
|
|
Unit,
|
|
U8,
|
|
S8,
|
|
U16,
|
|
S16,
|
|
U32,
|
|
S32,
|
|
U64,
|
|
S64,
|
|
Float32,
|
|
Float64,
|
|
Record(Vec<ValType>),
|
|
Tuple(Vec<ValType>),
|
|
Variant(NonZeroLenVec<ValType>),
|
|
}
|
|
|
|
#[derive(Copy, Clone, Arbitrary, Debug)]
|
|
enum GenStringEncoding {
|
|
Utf8,
|
|
Utf16,
|
|
CompactUtf16,
|
|
}
|
|
|
|
pub struct NonZeroLenVec<T>(Vec<T>);
|
|
|
|
impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for NonZeroLenVec<T> {
|
|
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
|
let mut items = Vec::arbitrary(u)?;
|
|
if items.is_empty() {
|
|
items.push(u.arbitrary()?);
|
|
}
|
|
Ok(NonZeroLenVec(items))
|
|
}
|
|
}
|
|
|
|
impl<T: fmt::Debug> fmt::Debug for NonZeroLenVec<T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
fuzz_target!(|module: GenAdapterModule| {
|
|
drop(env_logger::try_init());
|
|
|
|
let mut types = ComponentTypesBuilder::default();
|
|
|
|
// Manufactures a unique `CoreDef` so all function imports get unique
|
|
// function imports.
|
|
let mut next_def = 0;
|
|
let mut dummy_def = || {
|
|
next_def += 1;
|
|
CoreDef::Adapter(AdapterIndex::from_u32(next_def))
|
|
};
|
|
|
|
// Manufactures a `CoreExport` for a memory with the shape specified. Note
|
|
// that we can't import as many memories as functions so these are
|
|
// intentionally limited. Once a handful of memories are generated of each
|
|
// type then they start getting reused.
|
|
let mut next_memory = 0;
|
|
let mut memories32 = Vec::new();
|
|
let mut memories64 = Vec::new();
|
|
let mut dummy_memory = |memory64: bool| {
|
|
let dst = if memory64 {
|
|
&mut memories64
|
|
} else {
|
|
&mut memories32
|
|
};
|
|
let idx = if dst.len() < 5 {
|
|
next_memory += 1;
|
|
dst.push(next_memory - 1);
|
|
next_memory - 1
|
|
} else {
|
|
dst[0]
|
|
};
|
|
CoreExport {
|
|
instance: RuntimeInstanceIndex::from_u32(idx),
|
|
item: ExportItem::Name(String::new()),
|
|
}
|
|
};
|
|
|
|
let mut adapters = Vec::new();
|
|
for adapter in module.adapters.iter() {
|
|
let mut params = Vec::new();
|
|
for param in adapter.ty.params.iter() {
|
|
params.push((None, intern(&mut types, param)));
|
|
}
|
|
let result = intern(&mut types, &adapter.ty.result);
|
|
let signature = types.add_func_type(TypeFunc {
|
|
params: params.into(),
|
|
result,
|
|
});
|
|
adapters.push(Adapter {
|
|
lift_ty: signature,
|
|
lower_ty: signature,
|
|
lower_options: AdapterOptions {
|
|
instance: RuntimeComponentInstanceIndex::from_u32(0),
|
|
string_encoding: adapter.lower_encoding.into(),
|
|
memory64: adapter.lower_memory64,
|
|
// Pessimistically assume that memory/realloc are going to be
|
|
// required for this trampoline and provide it. Avoids doing
|
|
// calculations to figure out whether they're necessary and
|
|
// simplifies the fuzzer here without reducing coverage within FACT
|
|
// itself.
|
|
memory: Some(dummy_memory(adapter.lower_memory64)),
|
|
realloc: Some(dummy_def()),
|
|
// Lowering never allows `post-return`
|
|
post_return: None,
|
|
},
|
|
lift_options: AdapterOptions {
|
|
instance: RuntimeComponentInstanceIndex::from_u32(1),
|
|
string_encoding: adapter.lift_encoding.into(),
|
|
memory64: adapter.lift_memory64,
|
|
memory: Some(dummy_memory(adapter.lift_memory64)),
|
|
realloc: Some(dummy_def()),
|
|
post_return: if adapter.post_return {
|
|
Some(dummy_def())
|
|
} else {
|
|
None
|
|
},
|
|
},
|
|
func: dummy_def(),
|
|
});
|
|
}
|
|
let types = types.finish();
|
|
let mut fact_module = Module::new(&types, module.debug);
|
|
for (i, adapter) in adapters.iter().enumerate() {
|
|
fact_module.adapt(&format!("adapter{i}"), adapter);
|
|
}
|
|
let wasm = fact_module.encode();
|
|
let result = Validator::new_with_features(WasmFeatures {
|
|
multi_memory: true,
|
|
memory64: true,
|
|
..WasmFeatures::default()
|
|
})
|
|
.validate_all(&wasm);
|
|
|
|
let err = match result {
|
|
Ok(_) => return,
|
|
Err(e) => e,
|
|
};
|
|
eprintln!("invalid wasm module: {err:?}");
|
|
for adapter in module.adapters.iter() {
|
|
eprintln!("adapter: {adapter:?}");
|
|
}
|
|
std::fs::write("invalid.wasm", &wasm).unwrap();
|
|
match wasmprinter::print_bytes(&wasm) {
|
|
Ok(s) => std::fs::write("invalid.wat", &s).unwrap(),
|
|
Err(_) => drop(std::fs::remove_file("invalid.wat")),
|
|
}
|
|
|
|
panic!()
|
|
});
|
|
|
|
fn intern(types: &mut ComponentTypesBuilder, ty: &ValType) -> InterfaceType {
|
|
match ty {
|
|
ValType::Unit => InterfaceType::Unit,
|
|
ValType::U8 => InterfaceType::U8,
|
|
ValType::S8 => InterfaceType::S8,
|
|
ValType::U16 => InterfaceType::U16,
|
|
ValType::S16 => InterfaceType::S16,
|
|
ValType::U32 => InterfaceType::U32,
|
|
ValType::S32 => InterfaceType::S32,
|
|
ValType::U64 => InterfaceType::U64,
|
|
ValType::S64 => InterfaceType::S64,
|
|
ValType::Float32 => InterfaceType::Float32,
|
|
ValType::Float64 => InterfaceType::Float64,
|
|
ValType::Record(tys) => {
|
|
let ty = TypeRecord {
|
|
fields: tys
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, ty)| RecordField {
|
|
name: format!("f{i}"),
|
|
ty: intern(types, ty),
|
|
})
|
|
.collect(),
|
|
};
|
|
InterfaceType::Record(types.add_record_type(ty))
|
|
}
|
|
ValType::Tuple(tys) => {
|
|
let ty = TypeTuple {
|
|
types: tys.iter().map(|ty| intern(types, ty)).collect(),
|
|
};
|
|
InterfaceType::Tuple(types.add_tuple_type(ty))
|
|
}
|
|
ValType::Variant(NonZeroLenVec(cases)) => {
|
|
let ty = TypeVariant {
|
|
cases: cases
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, ty)| VariantCase {
|
|
name: format!("c{i}"),
|
|
ty: intern(types, ty),
|
|
})
|
|
.collect(),
|
|
};
|
|
InterfaceType::Variant(types.add_variant_type(ty))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GenStringEncoding> for StringEncoding {
|
|
fn from(gen: GenStringEncoding) -> StringEncoding {
|
|
match gen {
|
|
GenStringEncoding::Utf8 => StringEncoding::Utf8,
|
|
GenStringEncoding::Utf16 => StringEncoding::Utf16,
|
|
GenStringEncoding::CompactUtf16 => StringEncoding::CompactUtf16,
|
|
}
|
|
}
|
|
}
|