|
| 1 | +use eyre::Result; |
| 2 | +use std::fmt::Write; |
| 3 | +use tinywasm::{ |
| 4 | + types::{FuncType, ValType, WasmValue}, |
| 5 | + Extern, FuncContext, Imports, Module, Store, |
| 6 | +}; |
| 7 | + |
| 8 | +const VAL_LISTS: &[&[WasmValue]] = &[ |
| 9 | + &[], |
| 10 | + &[WasmValue::I32(0)], |
| 11 | + &[WasmValue::I32(0), WasmValue::I32(0)], // 2 of the same |
| 12 | + &[WasmValue::I32(0), WasmValue::I32(0), WasmValue::F64(0.0)], // add another type |
| 13 | + &[WasmValue::I32(0), WasmValue::F64(0.0), WasmValue::I32(0)], // reorder |
| 14 | + &[WasmValue::RefExtern(0), WasmValue::F64(0.0), WasmValue::I32(0)], // all different types |
| 15 | +]; |
| 16 | +// (f64, i32, i32) and (f64) can be used to "match_none" |
| 17 | + |
| 18 | +fn get_type_lists() -> impl Iterator<Item = impl Iterator<Item = ValType> + Clone> + Clone { |
| 19 | + VAL_LISTS.iter().map(|l| l.iter().map(WasmValue::val_type)) |
| 20 | +} |
| 21 | +fn get_modules() -> Vec<(Module, FuncType, Vec<WasmValue>)> { |
| 22 | + let mut result = Vec::<(Module, FuncType, Vec<WasmValue>)>::new(); |
| 23 | + let val_and_tys = get_type_lists().zip(VAL_LISTS); |
| 24 | + for res_types in get_type_lists() { |
| 25 | + for (arg_types, arg_vals) in val_and_tys.clone() { |
| 26 | + let ty = FuncType { results: res_types.clone().collect(), params: arg_types.collect() }; |
| 27 | + result.push((proxy_module(&ty), ty, arg_vals.to_vec())); |
| 28 | + } |
| 29 | + } |
| 30 | + result |
| 31 | +} |
| 32 | + |
| 33 | +#[test] |
| 34 | +fn test_return_invalid_type() -> Result<()> { |
| 35 | + // try to return from host functions types that don't match their signatures |
| 36 | + let mod_list = get_modules(); |
| 37 | + |
| 38 | + for (module, func_ty, test_args) in mod_list { |
| 39 | + for result_to_try in VAL_LISTS { |
| 40 | + println!("trying"); |
| 41 | + let mut store = Store::default(); |
| 42 | + let mut imports = Imports::new(); |
| 43 | + imports |
| 44 | + .define("host", "hfn", Extern::func(&func_ty, |_: FuncContext<'_>, _| Ok(result_to_try.to_vec()))) |
| 45 | + .unwrap(); |
| 46 | + |
| 47 | + let instance = module.clone().instantiate(&mut store, Some(imports)).unwrap(); |
| 48 | + let caller = instance.exported_func_untyped(&store, "call_hfn").unwrap(); |
| 49 | + let res_types_returned = result_to_try.iter().map(WasmValue::val_type); |
| 50 | + dbg!(&res_types_returned, &func_ty); |
| 51 | + let res_types_expected = &func_ty.results; |
| 52 | + let should_succeed = res_types_returned.eq(res_types_expected.iter().cloned()); |
| 53 | + // Extern::func that returns wrong type(s) can only be detected when it runs |
| 54 | + let call_res = caller.call(&mut store, &test_args); |
| 55 | + dbg!(&call_res); |
| 56 | + assert_eq!(call_res.is_ok(), should_succeed); |
| 57 | + println!("this time ok"); |
| 58 | + } |
| 59 | + } |
| 60 | + Ok(()) |
| 61 | +} |
| 62 | + |
| 63 | +#[test] |
| 64 | +fn test_linking_invalid_untyped_func() -> Result<()> { |
| 65 | + // try to import host functions with function types no matching those expected by modules |
| 66 | + let mod_list = get_modules(); |
| 67 | + for (module, actual_func_ty, _) in &mod_list { |
| 68 | + for (_, func_ty_to_try, _) in &mod_list { |
| 69 | + let tried_fn = Extern::func(func_ty_to_try, |_: FuncContext<'_>, _| panic!("not intended to be called")); |
| 70 | + let mut store = Store::default(); |
| 71 | + let mut imports = Imports::new(); |
| 72 | + imports.define("host", "hfn", tried_fn).unwrap(); |
| 73 | + |
| 74 | + let should_succeed = func_ty_to_try == actual_func_ty; |
| 75 | + let link_res = module.clone().instantiate(&mut store, Some(imports)); |
| 76 | + |
| 77 | + assert_eq!(link_res.is_ok(), should_succeed); |
| 78 | + } |
| 79 | + } |
| 80 | + Ok(()) |
| 81 | +} |
| 82 | + |
| 83 | +#[test] |
| 84 | +fn test_linking_invalid_typed_func() -> Result<()> { |
| 85 | + type Existing = (i32, i32, f64); |
| 86 | + type NonMatchingOne = f64; |
| 87 | + type NonMatchingMul = (f64, i32, i32); |
| 88 | + const DONT_CALL: &str = "not meant to be called"; |
| 89 | + |
| 90 | + // they don't match any signature from get_modules() |
| 91 | + #[rustfmt::skip] // to make it table-like |
| 92 | + let matching_none= &[ |
| 93 | + Extern::typed_func(|_, _: NonMatchingMul| -> tinywasm::Result<Existing> { panic!("{DONT_CALL}") } ), |
| 94 | + Extern::typed_func(|_, _: NonMatchingMul| -> tinywasm::Result<()> { panic!("{DONT_CALL}") } ), |
| 95 | + Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result<Existing> { panic!("{DONT_CALL}") } ), |
| 96 | + Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result<()> { panic!("{DONT_CALL}") } ), |
| 97 | + Extern::typed_func(|_, _: Existing | -> tinywasm::Result<NonMatchingMul> { panic!("{DONT_CALL}") } ), |
| 98 | + Extern::typed_func(|_, _: Existing | -> tinywasm::Result<NonMatchingOne> { panic!("{DONT_CALL}") } ), |
| 99 | + Extern::typed_func(|_, _: () | -> tinywasm::Result<NonMatchingOne> { panic!("{DONT_CALL}") } ), |
| 100 | + Extern::typed_func(|_, _: () | -> tinywasm::Result<NonMatchingMul> { panic!("{DONT_CALL}") } ), |
| 101 | + Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result<NonMatchingMul> { panic!("{DONT_CALL}") } ), |
| 102 | + Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result<NonMatchingOne> { panic!("{DONT_CALL}") } ), |
| 103 | + ]; |
| 104 | + |
| 105 | + let mod_list = get_modules(); |
| 106 | + for (module, _, _) in mod_list { |
| 107 | + for typed_fn in matching_none.clone() { |
| 108 | + let mut store = Store::default(); |
| 109 | + let mut imports = Imports::new(); |
| 110 | + imports.define("host", "hfn", typed_fn).unwrap(); |
| 111 | + let link_failure = module.clone().instantiate(&mut store, Some(imports)); |
| 112 | + link_failure.expect_err("no func in matching_none list should link to any mod"); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + // the valid cases are well-checked in other tests |
| 117 | + Ok(()) |
| 118 | +} |
| 119 | + |
| 120 | +fn to_name(ty: &ValType) -> &str { |
| 121 | + match ty { |
| 122 | + ValType::I32 => "i32", |
| 123 | + ValType::I64 => "i64", |
| 124 | + ValType::F32 => "f32", |
| 125 | + ValType::F64 => "f64", |
| 126 | + ValType::V128 => "v128", |
| 127 | + ValType::RefFunc => "funcref", |
| 128 | + ValType::RefExtern => "externref", |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +// make a module with imported function {module:"host", name:"hfn"} that takes specified results and returns specified params |
| 133 | +// and 2 wasm functions: call_hfn takes params, passes them to hfn and returns it's results |
| 134 | +// and 2 wasm functions: call_hfn_discard takes params, passes them to hfn and drops it's results |
| 135 | +fn proxy_module(func_ty: &FuncType) -> Module { |
| 136 | + let results = func_ty.results.as_ref(); |
| 137 | + let params = func_ty.params.as_ref(); |
| 138 | + let join_surround = |list: &[ValType], keyword| { |
| 139 | + if list.is_empty() { |
| 140 | + return "".to_string(); |
| 141 | + } |
| 142 | + let step = list.iter().map(|ty| format!("{} ", to_name(ty)).to_string()).collect::<String>(); |
| 143 | + format!("({keyword} {step})") |
| 144 | + }; |
| 145 | + |
| 146 | + let results_text = join_surround(results, "result"); |
| 147 | + let params_text = join_surround(params, "param"); |
| 148 | + |
| 149 | + // let params_gets: String = params.iter().enumerate().map(|(num, _)| format!("(local.get {num})\n")).collect(); |
| 150 | + let params_gets: String = params.iter().enumerate().fold(String::new(), |mut acc, (num, _)| { |
| 151 | + let _ = writeln!(acc, "(local.get {num})", num = num); |
| 152 | + acc |
| 153 | + }); |
| 154 | + |
| 155 | + let result_drops = "(drop)\n".repeat(results.len()).to_string(); |
| 156 | + let wasm_text = format!( |
| 157 | + r#"(module |
| 158 | + (import "host" "hfn" (func $host_fn {params_text} {results_text})) |
| 159 | + (func (export "call_hfn") {params_text} {results_text} |
| 160 | + {params_gets} |
| 161 | + (call $host_fn) |
| 162 | + ) |
| 163 | + (func (export "call_hfn_discard") {params_text} |
| 164 | + {params_gets} |
| 165 | + (call $host_fn) |
| 166 | + {result_drops} |
| 167 | + ) |
| 168 | + ) |
| 169 | + "# |
| 170 | + ); |
| 171 | + let wasm = wat::parse_str(wasm_text).expect("failed to parse wat"); |
| 172 | + Module::parse_bytes(&wasm).expect("failed to make module") |
| 173 | +} |
0 commit comments