diff --git a/crates/core/src/alloc/vec.rs b/crates/core/src/alloc/vec.rs index 4c8ce6b4e17d..a0590f1de62b 100644 --- a/crates/core/src/alloc/vec.rs +++ b/crates/core/src/alloc/vec.rs @@ -191,6 +191,20 @@ impl TryVec { Ok(()) } + /// Same as [`std::vec::Vec::resize_with`] but returns an error on + /// allocation failure. + pub fn resize_with(&mut self, new_len: usize, f: F) -> Result<(), OutOfMemory> + where + F: FnMut() -> T, + { + let len = self.len(); + if new_len > len { + self.reserve(new_len - len)?; + } + self.inner.resize_with(new_len, f); + Ok(()) + } + /// Same as [`std::vec::Vec::retain`]. pub fn retain(&mut self, f: F) where diff --git a/crates/fuzzing/tests/oom/caller.rs b/crates/fuzzing/tests/oom/caller.rs new file mode 100644 index 000000000000..c15071fa7bfa --- /dev/null +++ b/crates/fuzzing/tests/oom/caller.rs @@ -0,0 +1,120 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Func, FuncType, Module, Result, Store}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn caller_get_export() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module + (import "" "f" (func)) + (memory (export "m") 1) + (func (export "run") (call 0)) + )"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let host_func = Func::try_new( + &mut store, + FuncType::try_new(&engine, [], [])?, + |mut caller, _params, _results| { + let mem = caller.get_export("m"); + assert!(mem.is_some()); + Ok(()) + }, + )?; + let instance = wasmtime::Instance::new(&mut store, &module, &[host_func.into()])?; + let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; + run.call(&mut store, ())?; + Ok(()) + }) +} + +#[test] +fn caller_data() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module + (import "" "f" (func)) + (func (export "run") (call 0)) + )"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, 42u32)?; + let host_func = Func::try_new( + &mut store, + FuncType::try_new(&engine, [], [])?, + |caller, _params, _results| { + assert_eq!(*caller.data(), 42u32); + Ok(()) + }, + )?; + let instance = wasmtime::Instance::new(&mut store, &module, &[host_func.into()])?; + let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; + run.call(&mut store, ())?; + Ok(()) + }) +} + +#[test] +fn caller_engine() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module + (import "" "f" (func)) + (func (export "run") (call 0)) + )"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let host_func = Func::try_new( + &mut store, + FuncType::try_new(&engine, [], [])?, + |caller, _params, _results| { + let _engine = caller.engine(); + Ok(()) + }, + )?; + let instance = wasmtime::Instance::new(&mut store, &module, &[host_func.into()])?; + let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; + run.call(&mut store, ())?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/func.rs b/crates/fuzzing/tests/oom/func.rs index 8942476dadd6..8388d765a3ac 100644 --- a/crates/fuzzing/tests/oom/func.rs +++ b/crates/fuzzing/tests/oom/func.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, Func, Result, Store}; +use wasmtime::{Config, Engine, Func, FuncType, Linker, Module, Result, Store, Val, ValType}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -16,3 +16,99 @@ fn func_new() -> Result<()> { Ok(()) }) } + +#[test] +fn func_new_with_type() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = FuncType::try_new(&engine, [ValType::I32], [ValType::I32])?; + let _func = Func::try_new(&mut store, ty, |_caller, params, results| { + results[0] = params[0].clone(); + Ok(()) + })?; + Ok(()) + }) +} + +#[test] +fn func_call() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module (func (export "id") (param i32) (result i32) (local.get 0)))"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let id = instance.get_func(&mut store, "id").unwrap(); + let mut results = [Val::I32(0)]; + id.call(&mut store, &[Val::I32(42)], &mut results)?; + assert_eq!(results[0].unwrap_i32(), 42); + Ok(()) + }) +} + +#[test] +fn func_typed() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module (func (export "id") (param i32) (result i32) (local.get 0)))"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let id = instance.get_typed_func::(&mut store, "id")?; + let result = id.call(&mut store, 42)?; + assert_eq!(result, 42); + Ok(()) + }) +} + +#[test] +fn func_ty() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let func = Func::try_wrap(&mut store, |x: i32| x * 2)?; + let ty = func.ty(&store); + assert_eq!(ty.params().len(), 1); + assert_eq!(ty.results().len(), 1); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/global.rs b/crates/fuzzing/tests/oom/global.rs index ddcf882877cb..08f9031a639d 100644 --- a/crates/fuzzing/tests/oom/global.rs +++ b/crates/fuzzing/tests/oom/global.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, GlobalType, Mutability, Result, Store, Val, ValType}; +use wasmtime::{Config, Engine, Global, GlobalType, Mutability, Result, Store, Val, ValType}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -17,3 +17,55 @@ fn global_new() -> Result<()> { Ok(()) }) } + +#[test] +fn global_get() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = GlobalType::new(ValType::I32, Mutability::Var); + let global = Global::new(&mut store, ty, Val::I32(42))?; + let val = global.get(&mut store); + assert_eq!(val.unwrap_i32(), 42); + Ok(()) + }) +} + +#[test] +fn global_set() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = GlobalType::new(ValType::I32, Mutability::Var); + let global = Global::new(&mut store, ty, Val::I32(42))?; + global.set(&mut store, Val::I32(99))?; + let val = global.get(&mut store); + assert_eq!(val.unwrap_i32(), 99); + Ok(()) + }) +} + +#[test] +fn global_ty() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = GlobalType::new(ValType::I32, Mutability::Var); + let global = Global::new(&mut store, ty, Val::I32(42))?; + let ty = global.ty(&store); + assert!(ty.content().is_i32()); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/instance.rs b/crates/fuzzing/tests/oom/instance.rs index 24e99231a077..bf191a0599fd 100644 --- a/crates/fuzzing/tests/oom/instance.rs +++ b/crates/fuzzing/tests/oom/instance.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, Linker, Module, Result, Store}; +use wasmtime::{Config, Engine, Func, Instance, Linker, Module, Result, Store}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -40,3 +40,218 @@ fn call_exported_func() -> Result<()> { Ok(()) }) } + +#[test] +fn instance_new() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + "(module (import \"\" \"f\" (func)) (func (export \"g\") (call 0)))", + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let func = Func::try_wrap(&mut store, || {})?; + let _instance = Instance::new(&mut store, &module, &[func.into()])?; + Ok(()) + }) +} + +#[test] +fn instance_get_export() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, "(module (func (export \"f\")))")?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let _export = instance.get_export(&mut store, "f"); + Ok(()) + }) +} + +#[test] +fn instance_exports() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + "(module (func (export \"f\")) (memory (export \"m\") 1) (global (export \"g\") i32 (i32.const 0)))", + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let count = instance.try_exports(&mut store)?.count(); + assert_eq!(count, 3); + Ok(()) + }) +} + +#[test] +fn instance_get_func() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + "(module (func (export \"f\") (param i32) (result i32) (local.get 0)))", + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let f = instance.get_func(&mut store, "f"); + assert!(f.is_some()); + Ok(()) + }) +} + +#[test] +fn instance_get_typed_func() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + "(module (func (export \"f\") (param i32) (result i32) (local.get 0)))", + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let _f = instance.get_typed_func::(&mut store, "f")?; + Ok(()) + }) +} + +#[test] +fn instance_get_table() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, "(module (table (export \"t\") 1 funcref))")?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let t = instance.get_table(&mut store, "t"); + assert!(t.is_some()); + Ok(()) + }) +} + +#[test] +fn instance_get_memory() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, "(module (memory (export \"m\") 1))")?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let m = instance.get_memory(&mut store, "m"); + assert!(m.is_some()); + Ok(()) + }) +} + +#[test] +fn instance_get_global() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + "(module (global (export \"g\") i32 (i32.const 42)))", + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let g = instance.get_global(&mut store, "g"); + assert!(g.is_some()); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/linker.rs b/crates/fuzzing/tests/oom/linker.rs index b767a86677aa..325712ae05fb 100644 --- a/crates/fuzzing/tests/oom/linker.rs +++ b/crates/fuzzing/tests/oom/linker.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, Linker, Module, Result}; +use wasmtime::{Config, Engine, Func, FuncType, Linker, Module, Result, Store}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -69,3 +69,190 @@ fn linker_instantiate_pre() -> Result<()> { Ok(()) }) } + +#[test] +fn linker_define() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let mut linker = Linker::<()>::new(&engine); + let func = Func::try_wrap(&mut store, || {})?; + linker.define(&store, "mod", "func", func)?; + Ok(()) + }) +} + +#[test] +fn linker_func_new() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut linker = Linker::<()>::new(&engine); + linker.func_new( + "mod", + "func", + FuncType::try_new(&engine, [], [])?, + |_caller, _params, _results| Ok(()), + )?; + Ok(()) + }) +} + +#[test] +fn linker_instance() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module (func (export "f")) (memory (export "m") 1))"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let pre_linker = Linker::<()>::new(&engine); + let instance_pre = pre_linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let mut linker = Linker::<()>::new(&engine); + linker.instance(&mut store, "inst", instance)?; + Ok(()) + }) +} + +#[test] +fn linker_get() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + let mut linker = Linker::<()>::new(&engine); + linker.func_wrap("mod", "func", || {})?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let ext = linker.try_get(&mut store, "mod", "func")?; + assert!(ext.is_some()); + Ok(()) + }) +} + +#[test] +fn linker_get_by_import() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, r#"(module (import "mod" "func" (func)))"#)?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + let mut linker = Linker::<()>::new(&engine); + linker.func_wrap("mod", "func", || {})?; + + let import = module.imports().next().unwrap(); + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let ext = linker.try_get_by_import(&mut store, &import)?; + assert!(ext.is_some()); + Ok(()) + }) +} + +#[test] +fn linker_get_default() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, r#"(module (func (export "_start")))"#)?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate(&mut store)?; + let mut linker = Linker::<()>::new(&engine); + linker.instance(&mut store, "inst", instance)?; + let _default = linker.get_default(&mut store, "inst")?; + Ok(()) + }) +} + +#[test] +fn linker_define_name() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + let mut linker = Linker::<()>::new(&engine); + let func = Func::try_wrap(&mut store, || {})?; + linker.define_name(&store, "func", func)?; + Ok(()) + }) +} + +// Note: linker_define_unknown_imports_as_traps and +// linker_define_unknown_imports_as_default_values are not tested under OOM +// because UnknownImportError::new uses infallible String allocations +// (to_string) that cannot be made fallible without changing the public API. + +#[test] +fn linker_alias() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut linker = Linker::<()>::new(&engine); + linker.func_wrap("mod", "func", || {})?; + linker.alias("mod", "func", "mod2", "func2")?; + Ok(()) + }) +} + +#[test] +fn linker_alias_module() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let mut linker = Linker::<()>::new(&engine); + linker.func_wrap("mod", "func1", || {})?; + linker.func_wrap("mod", "func2", || {})?; + linker.alias_module("mod", "mod2")?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/main.rs b/crates/fuzzing/tests/oom/main.rs index 5ebe0c8f1da5..df0a13425595 100644 --- a/crates/fuzzing/tests/oom/main.rs +++ b/crates/fuzzing/tests/oom/main.rs @@ -4,6 +4,7 @@ mod bforest_set; mod bit_set; mod boxed; mod btree_map; +mod caller; mod config; mod engine; mod entity_set; @@ -19,12 +20,17 @@ mod instance_pre; mod linker; mod memory; mod module; +mod module_read; mod primary_map; mod secondary_map; +mod shared_memory; mod smoke; mod store; mod string; mod table; +mod tag; +mod types; +mod val; mod vec; use wasmtime_fuzzing::oom::OomTestAllocator; diff --git a/crates/fuzzing/tests/oom/memory.rs b/crates/fuzzing/tests/oom/memory.rs index 9cd78c115bb4..5f5f884133c6 100644 --- a/crates/fuzzing/tests/oom/memory.rs +++ b/crates/fuzzing/tests/oom/memory.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, MemoryType, Result, Store}; +use wasmtime::{Config, Engine, Memory, MemoryType, Result, Store}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -16,7 +16,118 @@ fn memory_new() -> Result<()> { .allow_alloc_after_oom(true) .test(|| { let mut store = Store::try_new(&engine, ())?; - let _memory = wasmtime::Memory::new(&mut store, MemoryType::new(1, None))?; + let _memory = Memory::new(&mut store, MemoryType::new(1, None))?; Ok(()) }) } + +#[test] +fn memory_grow() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + let _old_size = memory.grow(&mut store, 1)?; + Ok(()) + }) +} + +#[test] +fn memory_ty() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + let _ty = memory.ty(&store); + Ok(()) + }) +} + +#[test] +fn memory_size() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + assert_eq!(memory.size(&store), 1); + Ok(()) + }) +} + +#[test] +fn memory_data_size() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + assert_eq!(memory.data_size(&store), 65536); + Ok(()) + }) +} + +#[test] +fn memory_read_write() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + memory.write(&mut store, 0, &[1, 2, 3, 4])?; + let mut buf = [0u8; 4]; + memory.read(&store, 0, &mut buf)?; + assert_eq!(buf, [1, 2, 3, 4]); + Ok(()) + }) +} + +#[test] +fn memory_data() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + let data = memory.data(&store); + assert_eq!(data.len(), 65536); + Ok(()) + }) +} + +#[test] +fn memory_data_mut() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + let data = memory.data_mut(&mut store); + data[0] = 42; + assert_eq!(data[0], 42); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/module_read.rs b/crates/fuzzing/tests/oom/module_read.rs new file mode 100644 index 000000000000..60cd3f96aec8 --- /dev/null +++ b/crates/fuzzing/tests/oom/module_read.rs @@ -0,0 +1,117 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Module, Result}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn module_name() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, r#"(module $my_module (func (export "f")))"#)?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let _name = module.name(); + Ok(()) + }) +} + +#[test] +fn module_imports() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module (import "mod" "func" (func)) (func (export "f")))"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let count = module.imports().count(); + assert_eq!(count, 1); + Ok(()) + }) +} + +#[test] +fn module_exports() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new( + &engine, + r#"(module (func (export "f")) (memory (export "m") 1))"#, + )? + .serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let count = module.exports().count(); + assert_eq!(count, 2); + Ok(()) + }) +} + +#[test] +fn module_get_export() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, r#"(module (func (export "f")))"#)?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let export = module.get_export("f"); + assert!(export.is_some()); + let missing = module.get_export("nonexistent"); + assert!(missing.is_none()); + Ok(()) + }) +} + +#[test] +fn module_engine() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + Module::new(&engine, "(module)")?.serialize()? + }; + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let _engine = module.engine(); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/shared_memory.rs b/crates/fuzzing/tests/oom/shared_memory.rs new file mode 100644 index 000000000000..c8dcf84d4108 --- /dev/null +++ b/crates/fuzzing/tests/oom/shared_memory.rs @@ -0,0 +1,90 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, MemoryTypeBuilder, Result, SharedMemory}; +use wasmtime_fuzzing::oom::OomTest; + +fn shared_memory_engine() -> Result { + let mut config = Config::new(); + config.enable_compiler(false); + config.shared_memory(true); + Engine::new(&config) +} + +#[test] +fn shared_memory_new() -> Result<()> { + let engine = shared_memory_engine()?; + + OomTest::new().test(|| { + let ty = MemoryTypeBuilder::new() + .min(1) + .max(Some(2)) + .shared(true) + .build()?; + let _mem = SharedMemory::new(&engine, ty)?; + Ok(()) + }) +} + +#[test] +fn shared_memory_ty() -> Result<()> { + let engine = shared_memory_engine()?; + + OomTest::new().test(|| { + let ty = MemoryTypeBuilder::new() + .min(1) + .max(Some(2)) + .shared(true) + .build()?; + let mem = SharedMemory::new(&engine, ty)?; + let _ty = mem.ty(); + Ok(()) + }) +} + +#[test] +fn shared_memory_size() -> Result<()> { + let engine = shared_memory_engine()?; + + OomTest::new().test(|| { + let ty = MemoryTypeBuilder::new() + .min(1) + .max(Some(2)) + .shared(true) + .build()?; + let mem = SharedMemory::new(&engine, ty)?; + assert_eq!(mem.size(), 1); + Ok(()) + }) +} + +#[test] +fn shared_memory_grow() -> Result<()> { + let engine = shared_memory_engine()?; + + OomTest::new().test(|| { + let ty = MemoryTypeBuilder::new() + .min(1) + .max(Some(4)) + .shared(true) + .build()?; + let mem = SharedMemory::new(&engine, ty)?; + let _old = mem.grow(1)?; + Ok(()) + }) +} + +#[test] +fn shared_memory_data_size() -> Result<()> { + let engine = shared_memory_engine()?; + + OomTest::new().test(|| { + let ty = MemoryTypeBuilder::new() + .min(1) + .max(Some(2)) + .shared(true) + .build()?; + let mem = SharedMemory::new(&engine, ty)?; + assert_eq!(mem.data_size(), 65536); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/store.rs b/crates/fuzzing/tests/oom/store.rs index 2f94401cd298..f4805a06d7f9 100644 --- a/crates/fuzzing/tests/oom/store.rs +++ b/crates/fuzzing/tests/oom/store.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, Result, Store}; +use wasmtime::{Config, Engine, Result, Store, StoreLimitsBuilder}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -14,3 +14,86 @@ fn store_try_new() -> Result<()> { Ok(()) }) } + +#[test] +fn store_data() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let store = Store::try_new(&engine, 42u32)?; + assert_eq!(*store.data(), 42); + Ok(()) + }) +} + +#[test] +fn store_data_mut() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, 42u32)?; + *store.data_mut() = 99; + assert_eq!(*store.data(), 99); + Ok(()) + }) +} + +#[test] +fn store_engine() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let store = Store::try_new(&engine, ())?; + let _engine = store.engine(); + Ok(()) + }) +} + +#[test] +fn store_limiter() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, StoreLimitsBuilder::new().build())?; + store.limiter(|limits| limits); + Ok(()) + }) +} + +#[test] +fn store_fuel() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + config.consume_fuel(true); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + store.set_fuel(100)?; + let fuel = store.get_fuel()?; + assert_eq!(fuel, 100); + Ok(()) + }) +} + +#[test] +fn store_epoch_deadline() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + config.epoch_interruption(true); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let mut store = Store::try_new(&engine, ())?; + store.set_epoch_deadline(10); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/table.rs b/crates/fuzzing/tests/oom/table.rs index 6482ee0bfb70..5e783aeb9552 100644 --- a/crates/fuzzing/tests/oom/table.rs +++ b/crates/fuzzing/tests/oom/table.rs @@ -1,6 +1,6 @@ #![cfg(arc_try_new)] -use wasmtime::{Config, Engine, Ref, RefType, Result, Store, TableType}; +use wasmtime::{Config, Engine, Ref, RefType, Result, Store, Table, TableType}; use wasmtime_fuzzing::oom::OomTest; #[test] @@ -21,3 +21,116 @@ fn table_new() -> Result<()> { Ok(()) }) } + +#[test] +fn table_grow() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 1, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + let _old_size = table.grow(&mut store, 4, Ref::Func(None))?; + Ok(()) + }) +} + +#[test] +fn table_set() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 1, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + table.set(&mut store, 0, Ref::Func(None))?; + Ok(()) + }) +} + +#[test] +fn table_copy() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 4, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + Table::copy(&mut store, &table, 0, &table, 2, 2)?; + Ok(()) + }) +} + +#[test] +fn table_fill() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 4, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + table.fill(&mut store, 0, Ref::Func(None), 4)?; + Ok(()) + }) +} + +#[test] +fn table_get() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 1, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + let val = table.get(&mut store, 0); + assert!(val.is_some()); + Ok(()) + }) +} + +#[test] +fn table_ty() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 1, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + let _ty = table.ty(&store); + Ok(()) + }) +} + +#[test] +fn table_size() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let ty = TableType::new(RefType::FUNCREF, 2, None); + let table = Table::new(&mut store, ty, Ref::Func(None))?; + assert_eq!(table.size(&store), 2); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/tag.rs b/crates/fuzzing/tests/oom/tag.rs new file mode 100644 index 000000000000..416c8e122dce --- /dev/null +++ b/crates/fuzzing/tests/oom/tag.rs @@ -0,0 +1,56 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, FuncType, Result, Store, Tag, TagType}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn tag_new() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let func_ty = FuncType::try_new(&engine, [], [])?; + let tag_ty = TagType::new(func_ty); + let _tag = Tag::new(&mut store, &tag_ty)?; + Ok(()) + }) +} + +#[test] +fn tag_ty() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let func_ty = FuncType::try_new(&engine, [], [])?; + let tag_ty = TagType::new(func_ty); + let tag = Tag::new(&mut store, &tag_ty)?; + let _ty = tag.ty(&store); + Ok(()) + }) +} + +#[test] +fn tag_eq() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().allow_alloc_after_oom(true).test(|| { + let mut store = Store::try_new(&engine, ())?; + let func_ty = FuncType::try_new(&engine, [], [])?; + let tag_ty = TagType::new(func_ty); + let tag1 = Tag::new(&mut store, &tag_ty)?; + let tag2 = Tag::new(&mut store, &tag_ty)?; + assert!(Tag::eq(&tag1, &tag1, &store)); + assert!(!Tag::eq(&tag1, &tag2, &store)); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/types.rs b/crates/fuzzing/tests/oom/types.rs new file mode 100644 index 000000000000..ff5a156bbcd0 --- /dev/null +++ b/crates/fuzzing/tests/oom/types.rs @@ -0,0 +1,57 @@ +#![cfg(arc_try_new)] + +use wasmtime::{ + Config, Engine, FuncType, GlobalType, MemoryType, Mutability, RefType, Result, TableType, + ValType, +}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn func_type_params_results() -> Result<()> { + OomTest::new().test(|| { + let mut config = Config::new(); + config.enable_compiler(false); + let engine = Engine::new(&config)?; + let ty = FuncType::try_new(&engine, [ValType::I32, ValType::I64], [ValType::F32])?; + assert_eq!(ty.params().len(), 2); + assert_eq!(ty.results().len(), 1); + Ok(()) + }) +} + +#[test] +fn table_type_accessors() -> Result<()> { + OomTest::new().test(|| { + let ty = TableType::new(RefType::FUNCREF, 1, Some(10)); + assert_eq!(ty.minimum(), 1); + assert_eq!(ty.maximum(), Some(10)); + Ok(()) + }) +} + +#[test] +fn memory_type_accessors() -> Result<()> { + OomTest::new().test(|| { + let ty = MemoryType::new(1, Some(10)); + assert_eq!(ty.minimum(), 1); + assert_eq!(ty.maximum(), Some(10)); + assert!(!ty.is_64()); + assert!(!ty.is_shared()); + Ok(()) + }) +} + +#[test] +fn global_type_accessors() -> Result<()> { + OomTest::new().test(|| { + let ty = GlobalType::new(ValType::I32, Mutability::Var); + assert!(ty.content().is_i32()); + assert_eq!(ty.mutability(), Mutability::Var); + Ok(()) + }) +} + +// Note: ExnType::new, StructType::new, and ArrayType::new are not tested under +// OOM yet because their construction goes through the type registry and GC +// layout computation which has additional .panic_on_oom() calls deep in +// crates/environ/src/gc.rs that need to be addressed first. diff --git a/crates/fuzzing/tests/oom/val.rs b/crates/fuzzing/tests/oom/val.rs new file mode 100644 index 000000000000..fb8fceb7b89f --- /dev/null +++ b/crates/fuzzing/tests/oom/val.rs @@ -0,0 +1,38 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Result, Store, Val}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn val_i32_conversions() -> Result<()> { + OomTest::new().test(|| { + let val = Val::I32(42); + assert_eq!(val.unwrap_i32(), 42); + Ok(()) + }) +} + +#[test] +fn val_i64_conversions() -> Result<()> { + OomTest::new().test(|| { + let val = Val::I64(42); + assert_eq!(val.unwrap_i64(), 42); + Ok(()) + }) +} + +#[test] +fn val_null_func_ref() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new().test(|| { + let store = Store::try_new(&engine, ())?; + let val = Val::null_func_ref(); + let _ = &store; + assert!(val.ref_().is_some()); + Ok(()) + }) +} diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index 153400adfb54..c18d11fd1470 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -372,16 +372,26 @@ impl Func { /// Panics if the given function type is not associated with this store's /// engine. pub fn new( - mut store: impl AsContextMut, + store: impl AsContextMut, ty: FuncType, func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, ) -> Self { + Self::try_new(store, ty, func).expect("out of memory") + } + + /// Same as [`Func::new`] but returns an error instead of panicking on + /// allocation failure. + pub fn try_new( + mut store: impl AsContextMut, + ty: FuncType, + func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static, + ) -> Result { let store = store.as_context_mut().0; - let host = HostFunc::new(store.engine(), ty, func).panic_on_oom(); + let host = HostFunc::new(store.engine(), ty, func)?; // SAFETY: the `T` used by `func` matches the `T` of the store we're // inserting into via this function's type signature. - unsafe { host.into_func(store).panic_on_oom() } + Ok(unsafe { host.into_func(store)? }) } /// Creates a new [`Func`] with the given arguments, although has fewer @@ -1143,7 +1153,7 @@ impl Func { let values_vec_size = params.len().max(ty.results().len()); let mut values_vec = store.0.take_wasm_val_raw_storage(); debug_assert!(values_vec.is_empty()); - values_vec.resize_with(values_vec_size, || ValRaw::v128(0)); + values_vec.resize_with(values_vec_size, || ValRaw::v128(0))?; for (arg, slot) in params.iter().cloned().zip(&mut values_vec) { *slot = arg.to_raw(&mut *store)?; } diff --git a/crates/wasmtime/src/runtime/instance.rs b/crates/wasmtime/src/runtime/instance.rs index 5036ab3a3020..47eeb4891951 100644 --- a/crates/wasmtime/src/runtime/instance.rs +++ b/crates/wasmtime/src/runtime/instance.rs @@ -407,29 +407,45 @@ impl Instance { /// /// # Panics /// - /// Panics if `store` does not own this instance. + /// Panics if `store` does not own this instance, or if memory allocation + /// fails. pub fn exports<'a, T: 'static>( &'a self, store: impl Into>, ) -> impl ExactSizeIterator> + 'a { - self._exports(store.into().0) + self.try_exports(store).expect("out of memory") + } + + /// Returns the list of exported items from this [`Instance`]. + /// + /// Returns an error if memory allocation fails. + /// + /// # Panics + /// + /// Panics if `store` does not own this instance. + pub fn try_exports<'a, T: 'static>( + &'a self, + store: impl Into>, + ) -> Result> + 'a> { + Ok(self._try_exports(store.into().0)?) } - fn _exports<'a>( + fn _try_exports<'a>( &'a self, store: &'a mut StoreOpaque, - ) -> impl ExactSizeIterator> + 'a { + ) -> Result> + 'a, OutOfMemory> { let module = store[self.id].env_module().clone(); - let mut items = Vec::new(); + let num_exports = module.exports.len(); + let mut items = TryVec::with_capacity(num_exports)?; for (_name, entity) in module.exports.iter() { - items.push(self._get_export(store, *entity)); + items.push(self._get_export(store, *entity))?; } let module = store[self.id].env_module(); - module + Ok(module .exports .iter() .zip(items) - .map(|((name, _), item)| Export::new(&module.strings[name], item)) + .map(|((name, _), item)| Export::new(&module.strings[name], item))) } /// Looks up an exported [`Extern`] value by name. diff --git a/crates/wasmtime/src/runtime/linker.rs b/crates/wasmtime/src/runtime/linker.rs index 0ceb3e9bdcef..8fe004e86435 100644 --- a/crates/wasmtime/src/runtime/linker.rs +++ b/crates/wasmtime/src/runtime/linker.rs @@ -634,7 +634,7 @@ impl Linker { { let mut store = store.as_context_mut(); let exports: TryVec<_> = instance - .exports(&mut store) + .try_exports(&mut store)? .map(|e| { Ok(( self.import_key(module_name, Some(e.name()))?, @@ -1216,7 +1216,7 @@ impl Linker { &self.pool[key.module], &self.pool[key.name.unwrap()], // Should be safe since `T` is connecting the linker and store - unsafe { item.to_extern(store.0) }, + unsafe { item.to_extern(store.0).panic_on_oom() }, ) }) } @@ -1233,16 +1233,33 @@ impl Linker { /// same [`Engine`] that this linker was created with. pub fn get( &self, - mut store: impl AsContextMut, + store: impl AsContextMut, module: &str, name: &str, ) -> Option + where + T: 'static, + { + self.try_get(store, module, name).expect("out of memory") + } + + /// Same as [`Linker::get`] but returns an error instead of panicking on + /// allocation failure. + pub fn try_get( + &self, + mut store: impl AsContextMut, + module: &str, + name: &str, + ) -> Result> where T: 'static, { let store = store.as_context_mut().0; - // Should be safe since `T` is connecting the linker and store - Some(unsafe { self._get(module, name)?.to_extern(store) }) + match self._get(module, name) { + // Should be safe since `T` is connecting the linker and store + Some(def) => Ok(Some(unsafe { def.to_extern(store)? })), + None => Ok(None), + } } fn _get(&self, module: &str, name: &str) -> Option<&Definition> { @@ -1264,15 +1281,32 @@ impl Linker { /// same [`Engine`] that this linker was created with. pub fn get_by_import( &self, - mut store: impl AsContextMut, + store: impl AsContextMut, import: &ImportType, ) -> Option + where + T: 'static, + { + self.try_get_by_import(store, import) + .expect("out of memory") + } + + /// Same as [`Linker::get_by_import`] but returns an error instead of + /// panicking on allocation failure. + pub fn try_get_by_import( + &self, + mut store: impl AsContextMut, + import: &ImportType, + ) -> Result> where T: 'static, { let store = store.as_context_mut().0; - // Should be safe since `T` is connecting the linker and store - Some(unsafe { self._get_by_import(import).ok()?.to_extern(store) }) + match self._get_by_import(import) { + // Should be safe since `T` is connecting the linker and store + Ok(def) => Ok(Some(unsafe { def.to_extern(store)? })), + Err(_) => Ok(None), + } } fn _get_by_import(&self, import: &ImportType) -> Result { @@ -1342,13 +1376,13 @@ impl Definition { /// Note the unsafety here is due to calling `HostFunc::to_func`. The /// requirement here is that the `T` that was originally used to create the /// `HostFunc` matches the `T` on the store. - pub(crate) unsafe fn to_extern(&self, store: &mut StoreOpaque) -> Extern { + pub(crate) unsafe fn to_extern(&self, store: &mut StoreOpaque) -> Result { match self { - Definition::Extern(e, _) => e.clone(), + Definition::Extern(e, _) => Ok(e.clone()), // SAFETY: the contract of this function is the same as what's // required of `to_func`, that `T` of the store matches the `T` of // this original definition. - Definition::HostFunc(func) => unsafe { func.to_func(store).panic_on_oom().into() }, + Definition::HostFunc(func) => unsafe { Ok(func.to_func(store)?.into()) }, } } diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 556934abc2fb..033f4f0a0a05 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -527,7 +527,7 @@ pub struct StoreOpaque { hostcall_val_storage: Vec, /// Same as `hostcall_val_storage`, but for the direction of the host /// calling wasm. - wasm_val_raw_storage: Vec, + wasm_val_raw_storage: TryVec, /// Keep track of what protection key is being used during allocation so /// that the right memory pages can be enabled when entering WebAssembly @@ -771,7 +771,7 @@ impl Store { traitobj: StorePtr(None), default_caller_vmctx: SendSyncPtr::new(NonNull::dangling()), hostcall_val_storage: Vec::new(), - wasm_val_raw_storage: Vec::new(), + wasm_val_raw_storage: TryVec::new(), pkey, executor: Executor::new(engine)?, #[cfg(feature = "debug")] @@ -2377,14 +2377,14 @@ impl StoreOpaque { /// Same as `take_hostcall_val_storage`, but for the direction of the host /// calling wasm. #[inline] - pub fn take_wasm_val_raw_storage(&mut self) -> Vec { + pub fn take_wasm_val_raw_storage(&mut self) -> TryVec { mem::take(&mut self.wasm_val_raw_storage) } /// Same as `save_hostcall_val_storage`, but for the direction of the host /// calling wasm. #[inline] - pub fn save_wasm_val_raw_storage(&mut self, storage: Vec) { + pub fn save_wasm_val_raw_storage(&mut self, storage: TryVec) { if storage.capacity() > self.wasm_val_raw_storage.capacity() { self.wasm_val_raw_storage = storage; } diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs index 7fdd30dc484a..a45f5d997624 100644 --- a/crates/wasmtime/src/runtime/types.rs +++ b/crates/wasmtime/src/runtime/types.rs @@ -1925,8 +1925,8 @@ impl StructType { // from being reclaimed while constructing this struct type. let mut registrations = smallvec::SmallVec::<[_; 4]>::new(); - let fields = fields - .map(|ty: FieldType| { + let fields: Box<[WasmFieldType]> = fields + .map(|ty: FieldType| -> Result<_, Error> { assert!(ty.comes_from_same_engine(engine)); if supertype.is_some() { @@ -1939,9 +1939,9 @@ impl StructType { } } - ty.to_wasm_field_type() + Ok(ty.to_wasm_field_type()) }) - .collect(); + .try_collect()?; if let Some(supertype) = supertype { ensure!( @@ -2206,7 +2206,7 @@ impl ArrayType { finality.is_final(), supertype.map(|ty| ty.type_index().into()), wasm_ty, - )) + )?) } /// Get the engine that this array type is associated with. @@ -2323,7 +2323,7 @@ impl ArrayType { is_final: bool, supertype: Option, ty: WasmArrayType, - ) -> ArrayType { + ) -> Result { let ty = RegisteredType::new( engine, WasmSubType { @@ -2334,11 +2334,10 @@ impl ArrayType { inner: WasmCompositeInnerType::Array(ty), }, }, - ) - .panic_on_oom(); - Self { + )?; + Ok(Self { registered_type: ty, - } + }) } pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> ArrayType { @@ -2818,13 +2817,13 @@ impl ExnType { /// signature implies a tag type, and when instantiated at /// runtime, it must be associated with a tag of that type. pub fn new(engine: &Engine, fields: impl IntoIterator) -> Result { - let fields = fields.into_iter().collect::>(); + let fields: TryVec<_> = fields.into_iter().try_collect()?; // First, construct/intern a FuncType: we need this to exist // so we can hand out a TagType, and it also roots any nested registrations. - let func_ty = FuncType::new(engine, fields.clone(), []); + let func_ty = FuncType::try_new(engine, fields.iter().cloned(), [])?; - Ok(Self::_new(engine, fields, func_ty)) + Self::_new(engine, fields, func_ty) } /// Create a new `ExnType` from an existing `TagType`. @@ -2842,28 +2841,22 @@ impl ExnType { "Cannot create an exception type from a tag type with results in the signature" ); - Ok(Self::_new( - tag.ty.engine(), - func_ty.params(), - func_ty.clone(), - )) + Self::_new(tag.ty.engine(), func_ty.params(), func_ty.clone()) } fn _new( engine: &Engine, fields: impl IntoIterator, func_ty: FuncType, - ) -> ExnType { - let fields = fields - .into_iter() - .map(|ty| { - assert!(ty.comes_from_same_engine(engine)); - WasmFieldType { - element_type: WasmStorageType::Val(ty.to_wasm_type()), - mutable: false, - } - }) - .collect(); + ) -> Result { + let mut wasm_fields = TryVec::new(); + for ty in fields.into_iter() { + assert!(ty.comes_from_same_engine(engine)); + wasm_fields.push(WasmFieldType { + element_type: WasmStorageType::Val(ty.to_wasm_type()), + mutable: false, + })?; + } let ty = RegisteredType::new( engine, @@ -2874,17 +2867,16 @@ impl ExnType { shared: false, inner: WasmCompositeInnerType::Exn(WasmExnType { func_ty: EngineOrModuleTypeIndex::Engine(func_ty.type_index()), - fields, + fields: wasm_fields.into_boxed_slice()?, }), }, }, - ) - .panic_on_oom(); + )?; - Self { + Ok(ExnType { func_ty, registered_type: ty, - } + }) } /// Get the tag type that this exception type is associated with. diff --git a/crates/wasmtime/src/runtime/vm/memory/shared_memory.rs b/crates/wasmtime/src/runtime/vm/memory/shared_memory.rs index 5cafea369394..5eb5df27be7d 100644 --- a/crates/wasmtime/src/runtime/vm/memory/shared_memory.rs +++ b/crates/wasmtime/src/runtime/vm/memory/shared_memory.rs @@ -37,11 +37,9 @@ impl SharedMemory { // `assert_ready` should never panic. let (minimum_bytes, maximum_bytes) = vm::assert_ready(Memory::limit_new(ty, None))?; let mmap_memory = MmapMemory::new(ty, tunables, minimum_bytes, maximum_bytes)?; - Self::wrap( - engine, - ty, - LocalMemory::new(ty, tunables, Box::new(mmap_memory), None)?, - ) + let boxed: Box = + try_new::>(mmap_memory).map_err(|_| OutOfMemory::new(0))?; + Self::wrap(engine, ty, LocalMemory::new(ty, tunables, boxed, None)?) } /// Wrap an existing [Memory] with the locking provided by a [SharedMemory]. @@ -58,12 +56,12 @@ impl SharedMemory { if !ty.shared { bail!("shared memory must have a `shared` memory type"); } - Ok(Self(Arc::new(SharedMemoryInner { + Ok(Self(try_new::>(SharedMemoryInner { ty: *ty, spot: ParkingSpot::default(), def: LongTermVMMemoryDefinition(memory.vmmemory()), memory: RwLock::new(memory), - }))) + })?)) } /// Return the memory type for this [`SharedMemory`]. diff --git a/crates/wasmtime/src/runtime/vm/table.rs b/crates/wasmtime/src/runtime/vm/table.rs index 7cee563c54b6..49cb9d702543 100644 --- a/crates/wasmtime/src/runtime/vm/table.rs +++ b/crates/wasmtime/src/runtime/vm/table.rs @@ -185,7 +185,7 @@ impl From for DynamicTable { pub struct DynamicFuncTable { /// Dynamically managed storage space for this table. The length of this /// vector is the current size of the table. - elements: Vec, + elements: TryVec, /// Maximum size that `elements` can grow to. maximum: Option, /// Whether elements of this table are initialized lazily. @@ -195,7 +195,7 @@ pub struct DynamicFuncTable { pub struct DynamicGcRefTable { /// Dynamically managed storage space for this table. The length of this /// vector is the current size of the table. - elements: Vec>, + elements: TryVec>, /// Maximum size that `elements` can grow to. maximum: Option, } @@ -203,7 +203,7 @@ pub struct DynamicGcRefTable { pub struct DynamicContTable { /// Dynamically managed storage space for this table. The length of this /// vector is the current size of the table. - elements: Vec, + elements: TryVec, /// Maximum size that `elements` can grow to. maximum: Option, } @@ -291,7 +291,7 @@ pub(crate) fn wasm_to_table_type(ty: WasmRefType) -> TableElementType { /// /// Should only ever be called with a `T` that is a table element type and where /// `Option`'s `None` variant is represented with zero. -unsafe fn alloc_dynamic_table_elements(len: usize) -> Result>> { +unsafe fn alloc_dynamic_table_elements(len: usize) -> Result>> { debug_assert!( unsafe { core::mem::MaybeUninit::>::zeroed() @@ -302,7 +302,7 @@ unsafe fn alloc_dynamic_table_elements(len: usize) -> Result>> ); if len == 0 { - return Ok(vec![]); + return Ok(TryVec::new()); } let align = mem::align_of::>(); @@ -318,7 +318,7 @@ unsafe fn alloc_dynamic_table_elements(len: usize) -> Result>> return Err(OutOfMemory::new(size).into()); } - let elems = unsafe { Vec::>::from_raw_parts(ptr.cast(), len, len) }; + let elems = unsafe { TryVec::>::from_raw_parts(ptr.cast(), len, len) }; debug_assert!(elems.iter().all(|e| e.is_none())); Ok(elems) @@ -342,10 +342,11 @@ impl Table { elements: unsafe { alloc_dynamic_table_elements(minimum)? }, maximum, })), - TableElementType::Cont => Ok(Self::from(DynamicContTable { - elements: vec![None; minimum], - maximum, - })), + TableElementType::Cont => { + let mut elements = TryVec::new(); + elements.resize_with(minimum, || None)?; + Ok(Self::from(DynamicContTable { elements, maximum })) + } } } @@ -730,13 +731,13 @@ impl Table { // that delta is non-zero and the new size doesn't exceed the // maximum mean we can't get here. Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => { - elements.resize(new_size, None); + elements.resize_with(new_size, || None)?; } Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => { - elements.resize_with(new_size, || None); + elements.resize_with(new_size, || None)?; } Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => { - elements.resize(new_size, None); + elements.resize_with(new_size, || None)?; } }