use { Error, Signature, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder, MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeValue, RuntimeArgs, Module, TableDescriptor, MemoryDescriptor, Trap, }; use types::ValueType; use wabt::wat2wasm; #[derive(Debug, Clone, PartialEq)] struct HostErrorWithCode { error_code: u32, } impl ::std::fmt::Display for HostErrorWithCode { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::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, } impl TestHost { fn new() -> TestHost { TestHost { memory: Some(MemoryInstance::alloc(1, Some(1)).unwrap()), instance: 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; 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(Trap::Host(Box::new(error))) } 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(Trap::Host(Box::new(HostErrorWithCode { error_code: 123 }))); } Ok(Some(result)) } _ => 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)), _ => 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, _ => { 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), )) } } fn parse_wat(source: &str) -> Module { let wasm_binary = wat2wasm(source).expect("Failed to parse wat source"); Module::from_buffer(wasm_binary).expect("Failed to load parsed module") } #[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 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 host_error: Box = match error { Error::Trap(Trap::Host(err)) => err, err => panic!("Unexpected error {:?}", err), }; let error_with_code = 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, }; 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(1, Some(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(|_| Trap::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)) ); }