Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.

Commit 22d2330

Browse files
committed
chore: add integration test for ensuring host funtion types are checked
1 parent b43bdea commit 22d2330

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

tests/host_func_signature_check.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use eyre::Result;
2+
use tinywasm::{
3+
types::{FuncType, ValType, WasmValue},
4+
Extern, FuncContext, Imports, Module, Store,
5+
};
6+
use wat;
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.iter().cloned().collect()));
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.clone()).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+
// the valid cases are well-checked in other tests
116+
Ok(())
117+
}
118+
119+
fn to_name(ty: &ValType) -> &str {
120+
match ty {
121+
ValType::I32 => "i32",
122+
ValType::I64 => "i64",
123+
ValType::F32 => "f32",
124+
ValType::F64 => "f64",
125+
ValType::V128 => "v128",
126+
ValType::RefFunc => "funcref",
127+
ValType::RefExtern => "externref",
128+
}
129+
}
130+
131+
// make a module with imported function {module:"host", name:"hfn"} that takes specified results and returns specified params
132+
// and 2 wasm functions: call_hfn takes params, passes them to hfn and returns it's results
133+
// and 2 wasm functions: call_hfn_discard takes params, passes them to hfn and drops it's results
134+
fn proxy_module(func_ty: &FuncType) -> Module {
135+
let results = func_ty.results.as_ref();
136+
let params = func_ty.params.as_ref();
137+
let join_surround = |list: &[ValType], keyword| {
138+
if list.is_empty() {
139+
return "".to_string();
140+
}
141+
let step = list.iter().map(|ty| format!("{} ", to_name(ty)).to_string()).collect::<String>();
142+
format!("({keyword} {step})")
143+
};
144+
145+
let results_text = join_surround(results, "result");
146+
let params_text = join_surround(params, "param");
147+
148+
let params_gets: String = params.iter().enumerate().map(|(num, _)| format!("(local.get {num})\n")).collect();
149+
150+
let result_drops = "(drop)\n".repeat(results.len()).to_string();
151+
let wasm_text = format!(
152+
r#"(module
153+
(import "host" "hfn" (func $host_fn {params_text} {results_text}))
154+
(func (export "call_hfn") {params_text} {results_text}
155+
{params_gets}
156+
(call $host_fn)
157+
)
158+
(func (export "call_hfn_discard") {params_text}
159+
{params_gets}
160+
(call $host_fn)
161+
{result_drops}
162+
)
163+
)
164+
"#
165+
);
166+
let wasm = wat::parse_str(wasm_text).expect("failed to parse wat");
167+
let res = Module::parse_bytes(&wasm).expect("failed to make module");
168+
res
169+
}

0 commit comments

Comments
 (0)