| use { |
| Error, Signature, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder, |
| MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef, |
| RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind, |
| }; |
| use types::ValueType; |
| use memory_units::Pages; |
| use super::parse_wat; |
| |
| #[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<MemoryRef>, |
| instance: Option<ModuleRef>, |
| } |
| |
| impl TestHost { |
| fn new() -> TestHost { |
| TestHost { |
| memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(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<T>(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<Option<RuntimeValue>, 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)) |
| } |
| _ => 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<ValueType>) = 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<FuncRef, Error> { |
| 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<MemoryRef, Error> { |
| 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 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::<HostErrorWithCode>().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<FuncRef, Error> { |
| 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<MemoryRef, Error> { |
| 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<Option<RuntimeValue>, Trap> { |
| match index { |
| INC_FUNC_INDEX => { |
| let a = args.nth::<u32>(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<Option<RuntimeValue>, 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<FuncRef, Error> { |
| 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<FuncRef, Error> { |
| 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<Option<RuntimeValue>, 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<FuncRef, Error> { |
| 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<TableRef, Error> { |
| 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)) |
| ); |
| } |