use super::parse_wat; use memory_units::Pages; use types::ValueType; use { Error, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder, MemoryDescriptor, MemoryInstance, MemoryRef, ModuleImportResolver, ModuleInstance, ModuleRef, ResumableError, RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap, TrapKind, }; #[derive(Debug, Clone, PartialEq)] struct HostErrorWithCode { error_code: u32, } impl ::core::fmt::Display for HostErrorWithCode { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> Result<(), ::core::fmt::Error> { write!(f, "{}", self.error_code) } } impl HostError for HostErrorWithCode {} /// Host state for the test environment. /// /// This struct can be used as an external function executor and /// as imports provider. This has a drawback: this struct /// should be provided upon an instantiation of the module. /// /// However, this limitation can be lifted by implementing `Externals` /// and `ModuleImportResolver` traits for different structures. /// See `defer_providing_externals` test for details. struct TestHost { memory: Option, instance: Option, trap_sub_result: Option, } impl TestHost { fn new() -> TestHost { TestHost { memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap()), instance: None, trap_sub_result: None, } } } /// sub(a: i32, b: i32) -> i32 /// /// This function just substracts one integer from another, /// returning the subtraction result. const SUB_FUNC_INDEX: usize = 0; /// err(error_code: i32) -> ! /// /// This function traps upon a call. /// The trap have a special type - HostErrorWithCode. const ERR_FUNC_INDEX: usize = 1; /// inc_mem(ptr: *mut u8) /// /// Increments value at the given address in memory. This function /// requires attached memory. const INC_MEM_FUNC_INDEX: usize = 2; /// get_mem(ptr: *mut u8) -> u8 /// /// Returns value at the given address in memory. This function /// requires attached memory. const GET_MEM_FUNC_INDEX: usize = 3; /// recurse(val: T) -> T /// /// If called, resolves exported function named 'recursive' from the attached /// module instance and then calls into it with the provided argument. /// Note that this function is polymorphic over type T. /// This function requires attached module instance. const RECURSE_FUNC_INDEX: usize = 4; /// trap_sub(a: i32, b: i32) -> i32 /// /// This function is the same as sub(a, b), but it will send a Host trap which pauses the interpreter execution. const TRAP_SUB_FUNC_INDEX: usize = 5; impl Externals for TestHost { fn invoke_index( &mut self, index: usize, args: RuntimeArgs, ) -> Result, Trap> { match index { SUB_FUNC_INDEX => { let a: i32 = args.nth(0); let b: i32 = args.nth(1); let result: RuntimeValue = (a - b).into(); Ok(Some(result)) } ERR_FUNC_INDEX => { let error_code: u32 = args.nth(0); let error = HostErrorWithCode { error_code }; Err(TrapKind::Host(Box::new(error)).into()) } INC_MEM_FUNC_INDEX => { let ptr: u32 = args.nth(0); let memory = self .memory .as_ref() .expect("Function 'inc_mem' expects attached memory"); let mut buf = [0u8; 1]; memory.get_into(ptr, &mut buf).unwrap(); buf[0] += 1; memory.set(ptr, &buf).unwrap(); Ok(None) } GET_MEM_FUNC_INDEX => { let ptr: u32 = args.nth(0); let memory = self .memory .as_ref() .expect("Function 'get_mem' expects attached memory"); let mut buf = [0u8; 1]; memory.get_into(ptr, &mut buf).unwrap(); Ok(Some(RuntimeValue::I32(buf[0] as i32))) } RECURSE_FUNC_INDEX => { let val = args .nth_value_checked(0) .expect("Exactly one argument expected"); let instance = self .instance .as_ref() .expect("Function 'recurse' expects attached module instance") .clone(); let result = instance .invoke_export("recursive", &[val.into()], self) .expect("Failed to call 'recursive'") .expect("expected to be Some"); if val.value_type() != result.value_type() { return Err( TrapKind::Host(Box::new(HostErrorWithCode { error_code: 123 })).into(), ); } Ok(Some(result)) } TRAP_SUB_FUNC_INDEX => { let a: i32 = args.nth(0); let b: i32 = args.nth(1); let result: RuntimeValue = (a - b).into(); self.trap_sub_result = Some(result); return Err(TrapKind::Host(Box::new(HostErrorWithCode { error_code: 301 })).into()); } _ => panic!("env doesn't provide function at index {}", index), } } } impl TestHost { fn check_signature(&self, index: usize, signature: &Signature) -> bool { if index == RECURSE_FUNC_INDEX { // This function requires special handling because it is polymorphic. if signature.params().len() != 1 { return false; } let param_type = signature.params()[0]; return signature.return_type() == Some(param_type); } let (params, ret_ty): (&[ValueType], Option) = match index { SUB_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)), ERR_FUNC_INDEX => (&[ValueType::I32], None), INC_MEM_FUNC_INDEX => (&[ValueType::I32], None), GET_MEM_FUNC_INDEX => (&[ValueType::I32], Some(ValueType::I32)), TRAP_SUB_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)), _ => return false, }; signature.params() == params && signature.return_type() == ret_ty } } impl ModuleImportResolver for TestHost { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "sub" => SUB_FUNC_INDEX, "err" => ERR_FUNC_INDEX, "inc_mem" => INC_MEM_FUNC_INDEX, "get_mem" => GET_MEM_FUNC_INDEX, "recurse" => RECURSE_FUNC_INDEX, "trap_sub" => TRAP_SUB_FUNC_INDEX, _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))); } }; if !self.check_signature(index, signature) { return Err(Error::Instantiation(format!( "Export `{}` doesnt match expected type {:?}", field_name, signature ))); } Ok(FuncInstance::alloc_host(signature.clone(), index)) } fn resolve_memory( &self, field_name: &str, _memory_type: &MemoryDescriptor, ) -> Result { Err(Error::Instantiation(format!( "Export {} not found", field_name ))) } } #[test] fn call_host_func() { let module = parse_wat( r#" (module (import "env" "sub" (func $sub (param i32 i32) (result i32))) (func (export "test") (result i32) (call $sub (i32.const 5) (i32.const 7) ) ) ) "#, ); let mut env = TestHost::new(); let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); assert_eq!( instance .invoke_export("test", &[], &mut env) .expect("Failed to invoke 'test' function",), Some(RuntimeValue::I32(-2)) ); } #[test] fn resume_call_host_func() { let module = parse_wat( r#" (module (import "env" "trap_sub" (func $trap_sub (param i32 i32) (result i32))) (func (export "test") (result i32) (call $trap_sub (i32.const 5) (i32.const 7) ) ) ) "#, ); let mut env = TestHost::new(); let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); let export = instance.export_by_name("test").unwrap(); let func_instance = export.as_func().unwrap(); let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[]).unwrap(); let result = invocation.start_execution(&mut env); match result { Err(ResumableError::Trap(_)) => {} _ => panic!(), } assert!(invocation.is_resumable()); let trap_sub_result = env.trap_sub_result.take(); assert_eq!( invocation .resume_execution(trap_sub_result, &mut env) .expect("Failed to invoke 'test' function",), Some(RuntimeValue::I32(-2)) ); } #[test] fn host_err() { let module = parse_wat( r#" (module (import "env" "err" (func $err (param i32))) (func (export "test") (call $err (i32.const 228) ) ) ) "#, ); let mut env = TestHost::new(); let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); let error = instance .invoke_export("test", &[], &mut env) .expect_err("`test` expected to return error"); let error_with_code = error .as_host_error() .expect("Expected host error") .downcast_ref::() .expect("Failed to downcast to expected error type"); assert_eq!(error_with_code.error_code, 228); } #[test] fn modify_mem_with_host_funcs() { let module = parse_wat( r#" (module (import "env" "inc_mem" (func $inc_mem (param i32))) ;; (import "env" "get_mem" (func $get_mem (param i32) (result i32))) (func (export "modify_mem") ;; inc memory at address 12 for 4 times. (call $inc_mem (i32.const 12)) (call $inc_mem (i32.const 12)) (call $inc_mem (i32.const 12)) (call $inc_mem (i32.const 12)) ) ) "#, ); let mut env = TestHost::new(); let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); instance .invoke_export("modify_mem", &[], &mut env) .expect("Failed to invoke 'test' function"); // Check contents of memory at address 12. let mut buf = [0u8; 1]; env.memory.unwrap().get_into(12, &mut buf).unwrap(); assert_eq!(&buf, &[4]); } #[test] fn pull_internal_mem_from_module() { let module = parse_wat( r#" (module (import "env" "inc_mem" (func $inc_mem (param i32))) (import "env" "get_mem" (func $get_mem (param i32) (result i32))) ;; declare internal memory and export it under name "mem" (memory (export "mem") 1 1) (func (export "test") (result i32) ;; Increment value at address 1337 (call $inc_mem (i32.const 1337)) ;; Return value at address 1337 (call $get_mem (i32.const 1337)) ) ) "#, ); let mut env = TestHost { memory: None, instance: None, trap_sub_result: None, }; let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); // Get memory instance exported by name 'mem' from the module instance. let internal_mem = instance .export_by_name("mem") .expect("Module expected to have 'mem' export") .as_memory() .cloned() .expect("'mem' export should be a memory"); env.memory = Some(internal_mem); assert_eq!( instance.invoke_export("test", &[], &mut env).unwrap(), Some(RuntimeValue::I32(1)) ); } #[test] fn recursion() { let module = parse_wat( r#" (module ;; Import 'recurse' function. Upon a call it will call back inside ;; this module, namely to function 'recursive' defined below. (import "env" "recurse" (func $recurse (param i64) (result i64))) ;; Note that we import same function but with different type signature ;; this is possible since 'recurse' is a host function and it is defined ;; to be polymorphic. (import "env" "recurse" (func (param f32) (result f32))) (func (export "recursive") (param i64) (result i64) ;; return arg_0 + 42; (i64.add (get_local 0) (i64.const 42) ) ) (func (export "test") (result i64) (call $recurse (i64.const 321)) ) ) "#, ); let mut env = TestHost::new(); let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); // Put instance into the env, because $recurse function expects // attached module instance. env.instance = Some(instance.clone()); assert_eq!( instance .invoke_export("test", &[], &mut env) .expect("Failed to invoke 'test' function",), // 363 = 321 + 42 Some(RuntimeValue::I64(363)) ); } #[test] fn defer_providing_externals() { const INC_FUNC_INDEX: usize = 0; /// `HostImportResolver` will be passed at instantiation time. /// /// Main purpose of this struct is to statsify imports of /// the module being instantiated. struct HostImportResolver { mem: MemoryRef, } impl ModuleImportResolver for HostImportResolver { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { if field_name != "inc" { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))); } if signature.params() != &[ValueType::I32] || signature.return_type() != None { return Err(Error::Instantiation(format!( "Export `{}` doesnt match expected type {:?}", field_name, signature ))); } Ok(FuncInstance::alloc_host(signature.clone(), INC_FUNC_INDEX)) } fn resolve_memory( &self, field_name: &str, _memory_type: &MemoryDescriptor, ) -> Result { if field_name == "mem" { Ok(self.mem.clone()) } else { Err(Error::Instantiation(format!( "Export {} not found", field_name ))) } } } /// This struct implements external functions that can be called /// by wasm module. struct HostExternals<'a> { acc: &'a mut u32, } impl<'a> Externals for HostExternals<'a> { fn invoke_index( &mut self, index: usize, args: RuntimeArgs, ) -> Result, Trap> { match index { INC_FUNC_INDEX => { let a = args.nth::(0); *self.acc += a; Ok(None) } _ => panic!("env module doesn't provide function at index {}", index), } } } let module = parse_wat( r#" (module ;; Just to require 'mem' from 'host'. (import "host" "mem" (memory 1)) (import "host" "inc" (func $inc (param i32))) (func (export "test") (call $inc (i32.const 1)) ) ) "#, ); // Create HostImportResolver with some initialized memory instance. // This memory instance will be provided as 'mem' export. let host_import_resolver = HostImportResolver { mem: MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap(), }; // Instantiate module with `host_import_resolver` as import resolver for "host" module. let instance = ModuleInstance::new( &module, &ImportsBuilder::new().with_resolver("host", &host_import_resolver), ) .expect("Failed to instantiate module") .assert_no_start(); let mut acc = 89; { let mut host_externals = HostExternals { acc: &mut acc }; instance .invoke_export("test", &[], &mut host_externals) .unwrap(); // acc += 1; instance .invoke_export("test", &[], &mut host_externals) .unwrap(); // acc += 1; } assert_eq!(acc, 91); } #[test] fn two_envs_one_externals() { const PRIVILEGED_FUNC_INDEX: usize = 0; const ORDINARY_FUNC_INDEX: usize = 1; struct HostExternals; impl Externals for HostExternals { fn invoke_index( &mut self, index: usize, _args: RuntimeArgs, ) -> Result, Trap> { match index { PRIVILEGED_FUNC_INDEX => { println!("privileged!"); Ok(None) } ORDINARY_FUNC_INDEX => Ok(None), _ => panic!("env module doesn't provide function at index {}", index), } } } struct PrivilegedResolver; struct OrdinaryResolver; impl ModuleImportResolver for PrivilegedResolver { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "ordinary" => ORDINARY_FUNC_INDEX, "privileged" => PRIVILEGED_FUNC_INDEX, _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))); } }; Ok(FuncInstance::alloc_host(signature.clone(), index)) } } impl ModuleImportResolver for OrdinaryResolver { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "ordinary" => ORDINARY_FUNC_INDEX, "privileged" => { return Err(Error::Instantiation( "'priveleged' can be imported only in privileged context".into(), )); } _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))); } }; Ok(FuncInstance::alloc_host(signature.clone(), index)) } } let trusted_module = parse_wat( r#" (module ;; Trusted module can import both ordinary and privileged functions. (import "env" "ordinary" (func $ordinary)) (import "env" "privileged" (func $privileged)) (func (export "do_trusted_things") (call $ordinary) (call $privileged) ) ) "#, ); let untrusted_module = parse_wat( r#" (module ;; Untrusted module can import only ordinary functions. (import "env" "ordinary" (func $ordinary)) (import "trusted" "do_trusted_things" (func $do_trusted_things)) (func (export "test") (call $ordinary) (call $do_trusted_things) ) ) "#, ); let trusted_instance = ModuleInstance::new( &trusted_module, &ImportsBuilder::new().with_resolver("env", &PrivilegedResolver), ) .expect("Failed to instantiate module") .assert_no_start(); let untrusted_instance = ModuleInstance::new( &untrusted_module, &ImportsBuilder::new() .with_resolver("env", &OrdinaryResolver) .with_resolver("trusted", &trusted_instance), ) .expect("Failed to instantiate module") .assert_no_start(); untrusted_instance .invoke_export("test", &[], &mut HostExternals) .expect("Failed to invoke 'test' function"); } #[test] fn dynamically_add_host_func() { const ADD_FUNC_FUNC_INDEX: usize = 0; struct HostExternals { table: TableRef, added_funcs: u32, } impl Externals for HostExternals { fn invoke_index( &mut self, index: usize, _args: RuntimeArgs, ) -> Result, Trap> { match index { ADD_FUNC_FUNC_INDEX => { // Allocate indicies for the new function. // host_func_index is in host index space, and first index is occupied by ADD_FUNC_FUNC_INDEX. let table_index = self.added_funcs; let host_func_index = table_index + 1; self.added_funcs += 1; let added_func = FuncInstance::alloc_host( Signature::new(&[][..], Some(ValueType::I32)), host_func_index as usize, ); self.table .set(table_index, Some(added_func)) .map_err(|_| TrapKind::TableAccessOutOfBounds)?; Ok(Some(RuntimeValue::I32(table_index as i32))) } index if index as u32 <= self.added_funcs => { Ok(Some(RuntimeValue::I32(index as i32))) } _ => panic!("'env' module doesn't provide function at index {}", index), } } } impl ModuleImportResolver for HostExternals { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "add_func" => ADD_FUNC_FUNC_INDEX, _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))); } }; Ok(FuncInstance::alloc_host(signature.clone(), index)) } fn resolve_table( &self, field_name: &str, _table_type: &TableDescriptor, ) -> Result { if field_name == "table" { Ok(self.table.clone()) } else { Err(Error::Instantiation(format!( "Export {} not found", field_name ))) } } } let mut host_externals = HostExternals { table: TableInstance::alloc(10, None).unwrap(), added_funcs: 0, }; let module = parse_wat( r#" (module (type $t0 (func (result i32))) (import "env" "add_func" (func $add_func (result i32))) (import "env" "table" (table 10 anyfunc)) (func (export "test") (result i32) ;; Call add_func but discard the result call $add_func drop ;; Call add_func and then make an indirect call with the returned index call $add_func call_indirect (type $t0) ) ) "#, ); let instance = ModuleInstance::new( &module, &ImportsBuilder::new().with_resolver("env", &host_externals), ) .expect("Failed to instantiate module") .assert_no_start(); assert_eq!( instance .invoke_export("test", &[], &mut host_externals) .expect("Failed to invoke 'test' function"), Some(RuntimeValue::I32(2)) ); }