@@ -40,7 +40,16 @@ pub struct FluxWasmCompiledModuleIdentity {
4040pub struct WasmI32HostFunction {
4141 module : & ' static str ,
4242 name : & ' static str ,
43- callback : Arc < dyn Fn ( i32 , i32 ) -> Result < i32 , String > + Send + Sync > ,
43+ callback : WasmI32HostCallback ,
44+ }
45+
46+ type WasmI32HostCallback2 = dyn Fn ( i32 , i32 ) -> Result < i32 , String > + Send + Sync ;
47+ type WasmI32HostCallback3 = dyn Fn ( i32 , i32 , i32 ) -> Result < i32 , String > + Send + Sync ;
48+
49+ #[ derive( Clone ) ]
50+ enum WasmI32HostCallback {
51+ Two ( Arc < WasmI32HostCallback2 > ) ,
52+ Three ( Arc < WasmI32HostCallback3 > ) ,
4453}
4554
4655#[ derive( Debug , Clone , Eq , PartialEq ) ]
@@ -122,7 +131,19 @@ impl WasmI32HostFunction {
122131 Self {
123132 module,
124133 name,
125- callback : Arc :: new ( callback) ,
134+ callback : WasmI32HostCallback :: Two ( Arc :: new ( callback) ) ,
135+ }
136+ }
137+
138+ pub fn new_i32x3 (
139+ module : & ' static str ,
140+ name : & ' static str ,
141+ callback : impl Fn ( i32 , i32 , i32 ) -> Result < i32 , String > + Send + Sync + ' static ,
142+ ) -> Self {
143+ Self {
144+ module,
145+ name,
146+ callback : WasmI32HostCallback :: Three ( Arc :: new ( callback) ) ,
126147 }
127148 }
128149}
@@ -192,6 +213,8 @@ pub enum WasmExecutionError {
192213 ExecutionTimeout { timeout_ms : u128 } ,
193214 #[ error( "wasm module instantiation failed: {0}" ) ]
194215 Instantiate ( String ) ,
216+ #[ error( "wasm host import {module}.{name} is not available in the selected namespace" ) ]
217+ UnsupportedHostImport { module : String , name : String } ,
195218 #[ error( "wasm exported function {function:?} is missing or has the wrong type: {message}" ) ]
196219 FunctionType { function : String , message : String } ,
197220 #[ error( "wasm execution trapped or exceeded limits: {0}" ) ]
@@ -366,6 +389,16 @@ impl FluxWasmRuntime {
366389 function : & str ,
367390 host_functions : Vec < WasmI32HostFunction > ,
368391 ) -> Result < WasmExecutionOutcome , WasmExecutionError > {
392+ if let Some ( import) = module. module . imports ( ) . find ( |import| {
393+ !host_functions. iter ( ) . any ( |host_function| {
394+ host_function. module == import. module ( ) && host_function. name == import. name ( )
395+ } )
396+ } ) {
397+ return Err ( WasmExecutionError :: UnsupportedHostImport {
398+ module : import. module ( ) . to_owned ( ) ,
399+ name : import. name ( ) . to_owned ( ) ,
400+ } ) ;
401+ }
369402 let state = RuntimeStoreState {
370403 limits : StoreLimitsBuilder :: new ( )
371404 . memory_size ( self . limits . max_memory_bytes )
@@ -406,16 +439,26 @@ impl FluxWasmRuntime {
406439 let mut linker = Linker :: new ( & self . engine ) ;
407440 let has_host_functions = !host_functions. is_empty ( ) ;
408441 for host_function in host_functions {
409- let callback = host_function. callback . clone ( ) ;
410- linker
411- . func_wrap (
412- host_function. module ,
413- host_function. name ,
414- move |left : i32 , right : i32 | -> wasmtime:: Result < i32 > {
415- callback ( left, right) . map_err ( wasmtime:: Error :: msg)
416- } ,
417- )
418- . map_err ( |error| WasmExecutionError :: RuntimeSetup ( error. to_string ( ) ) ) ?;
442+ match host_function. callback {
443+ WasmI32HostCallback :: Two ( callback) => linker
444+ . func_wrap (
445+ host_function. module ,
446+ host_function. name ,
447+ move |left : i32 , right : i32 | -> wasmtime:: Result < i32 > {
448+ callback ( left, right) . map_err ( wasmtime:: Error :: msg)
449+ } ,
450+ )
451+ . map_err ( |error| WasmExecutionError :: RuntimeSetup ( error. to_string ( ) ) ) ?,
452+ WasmI32HostCallback :: Three ( callback) => linker
453+ . func_wrap (
454+ host_function. module ,
455+ host_function. name ,
456+ move |first : i32 , second : i32 , third : i32 | -> wasmtime:: Result < i32 > {
457+ callback ( first, second, third) . map_err ( wasmtime:: Error :: msg)
458+ } ,
459+ )
460+ . map_err ( |error| WasmExecutionError :: RuntimeSetup ( error. to_string ( ) ) ) ?,
461+ } ;
419462 }
420463 let instance = if has_host_functions {
421464 linker
@@ -554,6 +597,37 @@ mod tests {
554597 assert_eq ! ( outcome. plugin_sha256. len( ) , 64 ) ;
555598 }
556599
600+ #[ test]
601+ fn runtime_rejects_unbound_host_import_before_instantiation ( ) {
602+ let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
603+ let plugin_path = write_wat_plugin (
604+ & directory,
605+ r#"
606+ (module
607+ (import "env" "unexpected_host_call" (func $unexpected (param i32 i32) (result i32)))
608+ (func (export "decision") (result i32)
609+ i32.const 0
610+ i32.const 0
611+ call $unexpected))
612+ "# ,
613+ ) ;
614+ let limits = WasmSandboxLimits :: default ( ) ;
615+ let plugin =
616+ load_plugin_file ( & plugin_path, & [ directory. path ( ) . to_path_buf ( ) ] , limits) . unwrap ( ) ;
617+ let runtime = FluxWasmRuntime :: new ( limits) . unwrap ( ) ;
618+ let module = runtime. compile_plugin_module ( & plugin) . unwrap ( ) ;
619+
620+ let error = runtime
621+ . run_compiled_i32_no_args ( & module, "decision" )
622+ . unwrap_err ( ) ;
623+
624+ assert ! ( matches!(
625+ error,
626+ WasmExecutionError :: UnsupportedHostImport { module, name }
627+ if module == "env" && name == "unexpected_host_call"
628+ ) ) ;
629+ }
630+
557631 #[ test]
558632 fn compiled_module_execution_does_not_need_compile_slots ( ) {
559633 let counter = Box :: leak ( Box :: new ( AtomicUsize :: new ( 0 ) ) ) ;
0 commit comments