From 04a7e73826126e7bce0dfa7a4e1060510359408c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 4 Mar 2026 16:53:20 -0800 Subject: [PATCH] Split `crates/fuzzing/tests/oom.rs` into multiple modules It was getting big and unwieldy. --- crates/fuzzing/tests/oom.rs | 1116 --------------------- crates/fuzzing/tests/oom/arc.rs | 14 + crates/fuzzing/tests/oom/bforest_map.rs | 19 + crates/fuzzing/tests/oom/bforest_set.rs | 19 + crates/fuzzing/tests/oom/bit_set.rs | 20 + crates/fuzzing/tests/oom/boxed.rs | 11 + crates/fuzzing/tests/oom/btree_map.rs | 49 + crates/fuzzing/tests/oom/config.rs | 11 + crates/fuzzing/tests/oom/engine.rs | 14 + crates/fuzzing/tests/oom/entity_set.rs | 22 + crates/fuzzing/tests/oom/error.rs | 109 ++ crates/fuzzing/tests/oom/func_type.rs | 47 + crates/fuzzing/tests/oom/hash_map.rs | 47 + crates/fuzzing/tests/oom/hash_set.rs | 34 + crates/fuzzing/tests/oom/index_map.rs | 178 ++++ crates/fuzzing/tests/oom/linker.rs | 71 ++ crates/fuzzing/tests/oom/main.rs | 32 + crates/fuzzing/tests/oom/module.rs | 50 + crates/fuzzing/tests/oom/primary_map.rs | 30 + crates/fuzzing/tests/oom/secondary_map.rs | 30 + crates/fuzzing/tests/oom/smoke.rs | 91 ++ crates/fuzzing/tests/oom/store.rs | 16 + crates/fuzzing/tests/oom/string.rs | 101 ++ crates/fuzzing/tests/oom/vec.rs | 185 ++++ 24 files changed, 1200 insertions(+), 1116 deletions(-) delete mode 100644 crates/fuzzing/tests/oom.rs create mode 100644 crates/fuzzing/tests/oom/arc.rs create mode 100644 crates/fuzzing/tests/oom/bforest_map.rs create mode 100644 crates/fuzzing/tests/oom/bforest_set.rs create mode 100644 crates/fuzzing/tests/oom/bit_set.rs create mode 100644 crates/fuzzing/tests/oom/boxed.rs create mode 100644 crates/fuzzing/tests/oom/btree_map.rs create mode 100644 crates/fuzzing/tests/oom/config.rs create mode 100644 crates/fuzzing/tests/oom/engine.rs create mode 100644 crates/fuzzing/tests/oom/entity_set.rs create mode 100644 crates/fuzzing/tests/oom/error.rs create mode 100644 crates/fuzzing/tests/oom/func_type.rs create mode 100644 crates/fuzzing/tests/oom/hash_map.rs create mode 100644 crates/fuzzing/tests/oom/hash_set.rs create mode 100644 crates/fuzzing/tests/oom/index_map.rs create mode 100644 crates/fuzzing/tests/oom/linker.rs create mode 100644 crates/fuzzing/tests/oom/main.rs create mode 100644 crates/fuzzing/tests/oom/module.rs create mode 100644 crates/fuzzing/tests/oom/primary_map.rs create mode 100644 crates/fuzzing/tests/oom/secondary_map.rs create mode 100644 crates/fuzzing/tests/oom/smoke.rs create mode 100644 crates/fuzzing/tests/oom/store.rs create mode 100644 crates/fuzzing/tests/oom/string.rs create mode 100644 crates/fuzzing/tests/oom/vec.rs diff --git a/crates/fuzzing/tests/oom.rs b/crates/fuzzing/tests/oom.rs deleted file mode 100644 index af3bb117900b..000000000000 --- a/crates/fuzzing/tests/oom.rs +++ /dev/null @@ -1,1116 +0,0 @@ -use cranelift_bitset::CompoundBitSet; -use std::{ - alloc::{Layout, alloc, dealloc}, - fmt::{self, Write}, - iter, - ops::Deref, - sync::atomic::{AtomicU32, Ordering::SeqCst}, -}; -use wasmtime::{error::OutOfMemory, *}; -use wasmtime_core::alloc::TryCollect; -use wasmtime_environ::{ - PrimaryMap, SecondaryMap, - collections::{self, *}, -}; -use wasmtime_fuzzing::oom::{OomTest, OomTestAllocator}; - -#[global_allocator] -static GLOBAL_ALOCATOR: OomTestAllocator = OomTestAllocator::new(); - -/// RAII wrapper around a raw allocation to deallocate it on drop. -struct Alloc { - layout: Layout, - ptr: *mut u8, -} - -impl Drop for Alloc { - fn drop(&mut self) { - if !self.ptr.is_null() { - unsafe { - dealloc(self.ptr, self.layout); - } - } - } -} - -impl Deref for Alloc { - type Target = *mut u8; - - fn deref(&self) -> &Self::Target { - &self.ptr - } -} - -impl Alloc { - /// Safety: same as `std::alloc::alloc`. - unsafe fn new(layout: Layout) -> Self { - let ptr = unsafe { alloc(layout) }; - Alloc { layout, ptr } - } -} - -#[test] -fn smoke_test_ok() -> Result<()> { - OomTest::new().test(|| Ok(())) -} - -#[test] -fn smoke_test_missed_oom() -> Result<()> { - let err = OomTest::new() - .test(|| unsafe { - let _ = Alloc::new(Layout::new::()); - Ok(()) - }) - .unwrap_err(); - let err = format!("{err:?}"); - assert!( - err.contains("OOM test function missed an OOM"), - "should have missed an OOM, got: {err}" - ); - Ok(()) -} - -#[test] -fn smoke_test_disallow_alloc_after_oom() -> Result<()> { - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let _ = OomTest::new().test(|| unsafe { - let layout = Layout::new::(); - let p = Alloc::new(layout); - let _q = Alloc::new(layout); - if p.is_null() { - Err(OutOfMemory::new(layout.size()).into()) - } else { - Ok(()) - } - }); - })); - assert!(result.is_err()); - Ok(()) -} - -#[test] -fn smoke_test_allow_alloc_after_oom() -> Result<()> { - OomTest::new().allow_alloc_after_oom(true).test(|| unsafe { - let layout = Layout::new::(); - let p = Alloc::new(layout); - let q = Alloc::new(layout); - if p.is_null() || q.is_null() { - Err(OutOfMemory::new(layout.size()).into()) - } else { - Ok(()) - } - }) -} - -#[test] -#[cfg(arc_try_new)] -fn try_new_arc() -> Result<()> { - use std::sync::Arc; - - OomTest::new().test(|| { - let _arc = try_new::>(42)?; - Ok(()) - }) -} - -#[test] -fn try_new_box() -> Result<()> { - OomTest::new().test(|| { - let _box = try_new::>(36)?; - Ok(()) - }) -} - -#[test] -fn compound_bit_set_try_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _bitset = CompoundBitSet::::try_with_capacity(32)?; - Ok(()) - }) -} - -#[test] -fn compound_bit_set_try_ensure_capacity() -> Result<()> { - OomTest::new().test(|| { - let mut bitset = CompoundBitSet::new(); - bitset.try_ensure_capacity(100)?; - Ok(()) - }) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Key(u32); -wasmtime_environ::entity_impl!(Key); - -#[test] -fn primary_map_try_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _map = PrimaryMap::::try_with_capacity(32)?; - Ok(()) - }) -} - -#[test] -fn primary_map_try_reserve() -> Result<()> { - OomTest::new().test(|| { - let mut map = PrimaryMap::::new(); - map.try_reserve(100)?; - Ok(()) - }) -} - -#[test] -fn primary_map_try_reserve_exact() -> Result<()> { - OomTest::new().test(|| { - let mut map = PrimaryMap::::new(); - map.try_reserve_exact(13)?; - Ok(()) - }) -} - -#[test] -fn secondary_map_try_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _map = SecondaryMap::::try_with_capacity(32)?; - Ok(()) - }) -} - -#[test] -fn secondary_map_try_resize() -> Result<()> { - OomTest::new().test(|| { - let mut map = SecondaryMap::::new(); - map.try_resize(100)?; - Ok(()) - }) -} - -#[test] -fn secondary_map_try_insert() -> Result<()> { - OomTest::new().test(|| { - let mut map = SecondaryMap::::new(); - map.try_insert(Key::from_u32(42), 100)?; - Ok(()) - }) -} - -#[test] -fn try_entity_set_ensure_capacity() -> Result<()> { - OomTest::new().test(|| { - let mut set = TryEntitySet::::new(); - set.ensure_capacity(100)?; - Ok(()) - }) -} - -#[test] -fn try_entity_set_insert() -> Result<()> { - OomTest::new().test(|| { - let mut set = TryEntitySet::::new(); - set.insert(Key::from_u32(256))?; - Ok(()) - }) -} - -#[test] -fn try_hash_set_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _s = TryHashSet::::with_capacity(100)?; - Ok(()) - }) -} - -#[test] -fn try_hash_set_reserve() -> Result<()> { - OomTest::new().test(|| { - let mut set = TryHashSet::::new(); - set.reserve(100)?; - Ok(()) - }) -} - -#[test] -fn try_hash_set_insert() -> Result<()> { - OomTest::new().test(|| { - let mut set = TryHashSet::::new(); - for i in 0..1024 { - set.insert(i)?; - } - for i in 0..1024 { - set.insert(i)?; - } - Ok(()) - }) -} - -#[test] -fn try_hash_map_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _s = TryHashMap::::with_capacity(100)?; - Ok(()) - }) -} - -#[test] -fn try_hash_map_reserve() -> Result<()> { - OomTest::new().test(|| { - let mut map = TryHashMap::::new(); - map.reserve(100)?; - Ok(()) - }) -} - -#[test] -fn try_hash_map_insert() -> Result<()> { - OomTest::new().test(|| { - let mut map = TryHashMap::::new(); - for i in 0..1024 { - map.insert(i, i * 2)?; - } - for i in 0..1024 { - map.insert(i, i * 2)?; - } - Ok(()) - }) -} - -#[test] -fn try_hash_map_try_clone() -> Result<()> { - OomTest::new().test(|| { - let mut map = TryHashMap::new(); - for i in 0..10 { - map.insert(i, i * 2)?; - } - let map2 = map.try_clone()?; - assert_eq!(map, map2); - Ok(()) - }) -} - -#[test] -fn try_vec_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _v = wasmtime_environ::collections::TryVec::::with_capacity(100)?; - Ok(()) - }) -} - -#[test] -fn try_vec_reserve() -> Result<()> { - OomTest::new().test(|| { - let mut v = wasmtime_environ::collections::TryVec::::new(); - v.reserve(10)?; - Ok(()) - }) -} - -#[test] -fn try_vec_reserve_exact() -> Result<()> { - OomTest::new().test(|| { - let mut v = wasmtime_environ::collections::TryVec::::new(); - v.reserve_exact(3)?; - Ok(()) - }) -} - -#[test] -fn try_vec_push() -> Result<()> { - OomTest::new().test(|| { - let mut v = wasmtime_environ::collections::TryVec::new(); - v.push(42)?; - Ok(()) - }) -} - -#[test] -fn try_string_with_capacity() -> Result<()> { - OomTest::new().test(|| { - let _s = TryString::with_capacity(100)?; - Ok(()) - }) -} - -#[test] -fn try_string_reserve() -> Result<()> { - OomTest::new().test(|| { - let mut s = TryString::new(); - s.reserve(10)?; - Ok(()) - }) -} - -#[test] -fn try_string_reserve_exact() -> Result<()> { - OomTest::new().test(|| { - let mut s = TryString::new(); - s.reserve_exact(3)?; - Ok(()) - }) -} - -#[test] -fn try_string_push() -> Result<()> { - OomTest::new().test(|| { - let mut s = TryString::new(); - s.push('c')?; - Ok(()) - }) -} - -#[test] -fn try_string_push_str() -> Result<()> { - OomTest::new().test(|| { - let mut s = TryString::new(); - s.push_str("hello")?; - Ok(()) - }) -} - -#[test] -fn try_string_shrink_to_fit() -> Result<()> { - OomTest::new().test(|| { - // len == cap == 0 - let mut s = TryString::new(); - s.shrink_to_fit()?; - - // len == 0 < cap - let mut s = TryString::with_capacity(4)?; - s.shrink_to_fit()?; - - // 0 < len < cap - let mut s = TryString::with_capacity(4)?; - s.push('a')?; - s.shrink_to_fit()?; - - // 0 < len == cap - let mut s = TryString::new(); - s.reserve_exact(2)?; - s.push('a')?; - s.push('a')?; - s.shrink_to_fit()?; - - Ok(()) - }) -} - -#[test] -fn try_string_into_boxed_str() -> Result<()> { - OomTest::new().test(|| { - // len == cap == 0 - let s = TryString::new(); - let _ = s.into_boxed_str()?; - - // len == 0 < cap - let s = TryString::with_capacity(4)?; - let _ = s.into_boxed_str()?; - - // 0 < len < cap - let mut s = TryString::with_capacity(4)?; - s.push('a')?; - let _ = s.into_boxed_str()?; - - // 0 < len == cap - let mut s = TryString::new(); - s.reserve_exact(2)?; - s.push('a')?; - s.push('a')?; - let _ = s.into_boxed_str()?; - - Ok(()) - }) -} - -#[test] -fn config_new() -> Result<()> { - OomTest::new().test(|| { - let mut config = Config::new(); - config.enable_compiler(false); - Ok(()) - }) -} - -#[test] -#[cfg(arc_try_new)] -fn engine_new() -> Result<()> { - OomTest::new().test(|| { - let mut config = Config::new(); - config.enable_compiler(false); - let _ = Engine::new(&config)?; - Ok(()) - }) -} - -#[test] -#[cfg(arc_try_new)] -fn func_type_try_new() -> Result<()> { - let mut config = Config::new(); - config.enable_compiler(false); - let engine = Engine::new(&config)?; - - // Run this OOM test a few times to make sure that we leave the engine's - // type registry in a good state when failing to register new types. - for i in 1..6 { - OomTest::new().test(|| { - let ty1 = FuncType::try_new( - &engine, - std::iter::repeat(ValType::ANYREF).take(i), - std::iter::repeat(ValType::ANYREF).take(i), - )?; - assert_eq!(ty1.params().len(), i); - assert_eq!(ty1.results().len(), i); - - let ty2 = FuncType::try_new( - &engine, - std::iter::repeat(ValType::ANYREF).take(i), - std::iter::repeat(ValType::ANYREF).take(i), - )?; - assert_eq!(ty2.params().len(), i); - assert_eq!(ty2.results().len(), i); - - let ty3 = FuncType::try_new(&engine, [], [])?; - assert_eq!(ty3.params().len(), 0); - assert_eq!(ty3.results().len(), 0); - - assert!( - !FuncType::eq(&ty2, &ty3), - "{ty2:?} should not be equal to {ty3:?}" - ); - - Ok(()) - })?; - } - - Ok(()) -} - -#[test] -#[cfg(arc_try_new)] -fn linker_new() -> Result<()> { - OomTest::new().test(|| { - let mut config = Config::new(); - config.enable_compiler(false); - let engine = Engine::new(&config)?; - let _linker = Linker::<()>::new(&engine); - Ok(()) - }) -} - -#[test] -#[cfg(arc_try_new)] -fn linker_func_wrap() -> Result<()> { - OomTest::new().test(|| { - let mut config = Config::new(); - config.enable_compiler(false); - let engine = Engine::new(&config)?; - let mut linker = Linker::<()>::new(&engine); - linker.func_wrap("module", "func", |x: i32| x * 2)?; - Ok(()) - }) -} - -#[test] -#[cfg(arc_try_new)] -fn linker_instantiate_pre() -> Result<()> { - let module_bytes = { - let mut config = Config::new(); - config.concurrency_support(false); - let engine = Engine::new(&config)?; - let module = Module::new( - &engine, - r#" - (module - (import "module" "func" (func (param i32) (result i32))) - - (memory (export "memory") 1) - (data (i32.const 0) "a") - - (table (export "table") 1 funcref) - (elem (i32.const 0) func 1) - - (func (export "func") (param i32) (result i32) - (call 0 (local.get 0)) - ) - ) - "#, - )?; - module.serialize()? - }; - - 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("module", "func", |x: i32| x * 2)?; - - let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; - - OomTest::new().test(|| { - let _ = linker.instantiate_pre(&module)?; - Ok(()) - }) -} - -#[test] -#[cfg(arc_try_new)] -fn module_deserialize() -> Result<()> { - let module_bytes = { - let mut config = Config::new(); - config.concurrency_support(false); - let engine = Engine::new(&config)?; - let module = Module::new( - &engine, - r#" - (module - (import "module" "func" (func (param i32) (result i32))) - - (memory (export "memory") 1) - (data (i32.const 0) "a") - - (table (export "table") 1 funcref) - (elem (i32.const 0) func 1) - - (func (export "func") (param i32) (result i32) - (call 0 (local.get 0)) - ) - ) - "#, - )?; - module.serialize()? - }; - - let mut config = Config::new(); - config.enable_compiler(false); - config.concurrency_support(false); - let engine = Engine::new(&config)?; - - OomTest::new() - // NB: We use `postcard` to deserialize module metadata, and it will - // return a `postcard::Error::SerdeDeCustom` when we generate an - // `OutOfMemory` error during deserialization. That is then converted - // into a `wasmtime::Error`, and in the process we will attempt to box - // that into an `Error` trait object. There is no good way to avoid all - // this, so just allow allocation attempts after OOM here. - .allow_alloc_after_oom(true) - .test(|| unsafe { - let _ = Module::deserialize(&engine, &module_bytes)?; - Ok(()) - }) -} - -#[test] -#[cfg(arc_try_new)] -fn store_try_new() -> Result<()> { - let mut config = Config::new(); - config.enable_compiler(false); - config.concurrency_support(false); - let engine = Engine::new(&config)?; - OomTest::new().test(|| { - let _ = Store::try_new(&engine, ())?; - Ok(()) - }) -} - -fn ok_if_not_oom(error: Error) -> Result<()> { - if error.is::() { - Err(error) - } else { - Ok(()) - } -} - -#[test] -fn error_new() -> Result<()> { - OomTest::new().test(|| { - let error = Error::new(u8::try_from(u32::MAX).unwrap_err()); - ok_if_not_oom(error) - }) -} - -#[test] -fn error_msg() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("ouch"); - ok_if_not_oom(error) - }) -} - -static X: AtomicU32 = AtomicU32::new(42); - -#[test] -fn error_fmt() -> Result<()> { - OomTest::new().test(|| { - let x = X.load(SeqCst); - let error = format_err!("ouch: {x}"); - ok_if_not_oom(error) - }) -} - -#[test] -fn error_context() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("hello"); - let error = error.context("goodbye"); - ok_if_not_oom(error) - }) -} - -#[test] -fn error_chain() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("hello"); - let error = error.context("goodbye"); - for _ in error.chain() { - // Nothing to do here, just exercising the iteration. - } - ok_if_not_oom(error) - }) -} - -struct Null; -impl Write for Null { - fn write_str(&mut self, _s: &str) -> fmt::Result { - Ok(()) - } -} - -#[test] -fn display_fmt_error() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("hello"); - let error = error.context("goodbye"); - write!(&mut Null, "{error}").unwrap(); - ok_if_not_oom(error) - }) -} - -#[test] -fn alternate_display_fmt_error() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("hello"); - let error = error.context("goodbye"); - write!(&mut Null, "{error:?}").unwrap(); - ok_if_not_oom(error) - }) -} - -#[test] -fn debug_fmt_error() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("hello"); - let error = error.context("goodbye"); - write!(&mut Null, "{error:?}").unwrap(); - ok_if_not_oom(error) - }) -} - -#[test] -fn alternate_debug_fmt_error() -> Result<()> { - OomTest::new().test(|| { - let error = Error::msg("hello"); - let error = error.context("goodbye"); - write!(&mut Null, "{error:#?}").unwrap(); - ok_if_not_oom(error) - }) -} - -#[test] -fn try_vec_and_boxed_slice() -> Result<()> { - use wasmtime_core::alloc::TryVec; - - OomTest::new().test(|| { - // Nonzero-sized type. - let mut vec = TryVec::new(); - vec.push(1)?; - let slice = vec.into_boxed_slice()?; // len > 0, cap > 0 - - let mut vec = TryVec::from(slice); - vec.pop(); - let slice = vec.into_boxed_slice()?; // len = 0, cap > 0 - - let vec = TryVec::from(slice); - let _slice = vec.into_boxed_slice()?; // len = 0, cap = 0 - - let mut vec = TryVec::new(); - vec.reserve_exact(3)?; - vec.push(2)?; - vec.push(2)?; - vec.push(2)?; - let _slice = vec.into_boxed_slice()?; // len = cap, len > 0 - - for i in 0..12 { - let mut vec = TryVec::new(); - for j in 0..i { - vec.push(j)?; - } - let _slice = vec.into_boxed_slice()?; // len ?= cap - } - - // Zero-sized type. - let mut vec = TryVec::new(); - vec.push(())?; - let slice = vec.into_boxed_slice()?; // len > 0, cap > 0 - let mut vec = TryVec::from(slice); - vec.pop(); - let slice = vec.into_boxed_slice()?; // len = 0, cap > 0 - let vec = TryVec::from(slice); - let _ = vec.into_boxed_slice()?; // len = 0, cap = 0 - - Ok(()) - }) -} - -#[test] -fn try_vec_shrink_to_fit() -> Result<()> { - use wasmtime_core::alloc::TryVec; - - #[derive(Default)] - struct ZeroSized; - - #[derive(Default)] - struct NonZeroSized { - _unused: usize, - } - - fn do_test() -> Result<()> { - // len == cap == 0 - let mut v = TryVec::::new(); - v.shrink_to_fit()?; - - // len == 0 < cap - let mut v = TryVec::::with_capacity(4)?; - v.shrink_to_fit()?; - - // 0 < len < cap - let mut v = TryVec::with_capacity(4)?; - v.push(T::default())?; - v.shrink_to_fit()?; - - // 0 < len == cap - let mut v = TryVec::new(); - v.reserve_exact(2)?; - v.push(T::default())?; - v.push(T::default())?; - v.shrink_to_fit()?; - - Ok(()) - } - - OomTest::new().test(|| do_test::())?; - OomTest::new().test(|| do_test::())?; - Ok(()) -} - -#[test] -fn try_vec_resize() -> Result<()> { - OomTest::new().test(|| { - let mut v = TryVec::new(); - v.resize(10, 'a')?; // Grow. - v.resize(1, 'b')?; // Truncate. - v.resize(1, 'c')?; // Same length. - v.resize(3, 'd')?; // Grow again. - assert_eq!(&*v, &['a', 'd', 'd']); - Ok(()) - }) -} - -#[test] -fn try_vec_try_collect() -> Result<()> { - OomTest::new().test(|| { - iter::repeat(1).take(0).try_collect::, _>()?; - iter::repeat(1).take(1).try_collect::, _>()?; - iter::repeat(1).take(100).try_collect::, _>()?; - iter::repeat(()).take(100).try_collect::, _>()?; - Ok(()) - }) -} - -#[test] -fn try_vec_extend() -> Result<()> { - use wasmtime_core::alloc::{TryExtend, TryVec}; - OomTest::new().test(|| { - let mut vec = TryVec::new(); - vec.try_extend([])?; - vec.try_extend([1])?; - vec.try_extend([1, 2, 3, 4])?; - - let mut vec = TryVec::new(); - vec.try_extend([])?; - vec.try_extend([()])?; - vec.try_extend([(), (), ()])?; - Ok(()) - }) -} - -#[test] -fn try_vec_macro_elems() -> Result<()> { - OomTest::new().test(|| { - let v = collections::try_vec![100, 200, 300, 400]?; - assert_eq!(&*v, &[100, 200, 300, 400]); - Ok(()) - }) -} - -#[test] -fn try_vec_macro_elem_len() -> Result<()> { - OomTest::new().test(|| { - let v = collections::try_vec![100; 3]?; - assert_eq!(&*v, &[100, 100, 100]); - Ok(()) - }) -} - -#[test] -fn try_index_map_try_clone() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map1 = TryIndexMap::new(); - map1.insert("a", try_new::>(42)?)?; - map1.insert("b", try_new::>(36)?)?; - let map2 = map1.try_clone()?; - assert_eq!(map1, map2); - Ok(()) - }) -} - -#[test] -fn try_index_map_with_capacity() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let _map = TryIndexMap::<&str, usize>::with_capacity(100)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_split_off() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map1 = TryIndexMap::new(); - map1.insert("a", 42)?; - map1.insert("b", 36)?; - - let map2 = map1.split_off(1)?; - - assert_eq!(map1.len(), 1); - assert_eq!(map2.len(), 1); - assert_eq!(map1[&"a"], 42); - assert_eq!(map1[0], 42); - assert_eq!(map2[&"b"], 36); - assert_eq!(map2[0], 36); - - Ok(()) - }) -} - -#[test] -fn try_index_map_reserve() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::::new(); - map.reserve(100)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_reserve_exact() -> Result<()> { - OomTest::new().test(|| { - let mut map = TryIndexMap::::new(); - map.reserve_exact(100)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_insert() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert(10, 20)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_insert_full() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert_full(10, 20)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_insert_sorted() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert_sorted(10, 20)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_insert_sorted_by() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert_sorted_by(10, 20, |_k, _v, _k2, _v2| core::cmp::Ordering::Less)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_insert_sorted_by_key() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert_sorted_by_key(10, 20, |_k, v| *v)?; - Ok(()) - }) -} - -#[test] -fn try_index_map_insert_before() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert("a", 20)?; - map.insert("b", 30)?; - map.insert_before(1, "c", 40)?; - assert_eq!(map[0], 20); - assert_eq!(map[1], 40); - assert_eq!(map[2], 30); - Ok(()) - }) -} - -#[test] -fn try_index_map_shift_insert() -> Result<()> { - OomTest::new() - // `indexmap` will first try to double its capacity, and, if that fails, - // will then try to allocate only as much as it absolutely must. - .allow_alloc_after_oom(true) - .test(|| { - let mut map = TryIndexMap::new(); - map.insert("a", 20)?; - map.insert("b", 30)?; - map.shift_insert(1, "c", 40)?; - assert_eq!(map[0], 20); - assert_eq!(map[1], 40); - assert_eq!(map[2], 30); - Ok(()) - }) -} - -#[test] -fn bforest_map() -> Result<()> { - use cranelift_bforest::*; - OomTest::new().test(|| { - let mut forest = MapForest::new(); - let mut map = Map::new(); - for i in 0..100 { - map.try_insert(Key(i), i, &mut forest, &())?; - } - for i in 0..100 { - assert_eq!(map.get(Key(i), &forest, &()), Some(i)); - } - Ok(()) - }) -} - -#[test] -fn bforest_set() -> Result<()> { - use cranelift_bforest::*; - OomTest::new().test(|| { - let mut forest = SetForest::new(); - let mut set = Set::new(); - for i in 0..100 { - set.try_insert(Key(i), &mut forest, &())?; - } - for i in 0..100 { - assert!(set.contains(Key(i), &forest, &())); - } - Ok(()) - }) -} - -#[test] -fn btree_map() -> Result<()> { - use collections::btree_map::Entry; - type M = collections::BTreeMap; - OomTest::new().test(|| { - let mut m = M::new(); - - m.insert(100, 100.0)?; - - m.entry(0).or_insert(99.0)?; - m.entry(0).or_default()?; - - match m.entry(1) { - Entry::Occupied(_) => unreachable!(), - Entry::Vacant(e) => { - let e = e.insert_entry(42.0)?; - e.insert(43.0); - } - }; - - match m.entry(1) { - Entry::Occupied(e) => { - e.remove_entry(); - } - Entry::Vacant(_) => unreachable!(), - } - - match m.entry(2) { - Entry::Occupied(_) => unreachable!(), - Entry::Vacant(e) => { - e.insert(99.0)?; - } - }; - - let _ = m.iter().count(); - let _ = m.iter_mut().count(); - let _ = m.keys().count(); - let _ = m.values().count(); - let _ = m.values_mut().count(); - let _ = m.range(..3).count(); - let _ = m.range_mut(..3).count(); - - Ok(()) - }) -} diff --git a/crates/fuzzing/tests/oom/arc.rs b/crates/fuzzing/tests/oom/arc.rs new file mode 100644 index 000000000000..ccc351ef1364 --- /dev/null +++ b/crates/fuzzing/tests/oom/arc.rs @@ -0,0 +1,14 @@ +#![cfg(arc_try_new)] + +use std::sync::Arc; +use wasmtime::Result; +use wasmtime_environ::collections::try_new; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +pub(crate) fn try_new_arc() -> Result<()> { + OomTest::new().test(|| { + let _arc = try_new::>(42)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/bforest_map.rs b/crates/fuzzing/tests/oom/bforest_map.rs new file mode 100644 index 000000000000..c3104822f00b --- /dev/null +++ b/crates/fuzzing/tests/oom/bforest_map.rs @@ -0,0 +1,19 @@ +use super::Key; +use cranelift_bforest::{Map, MapForest}; +use wasmtime::Result; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn bforest_map() -> Result<()> { + OomTest::new().test(|| { + let mut forest = MapForest::new(); + let mut map = Map::new(); + for i in 0..100 { + map.try_insert(Key(i), i, &mut forest, &())?; + } + for i in 0..100 { + assert_eq!(map.get(Key(i), &forest, &()), Some(i)); + } + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/bforest_set.rs b/crates/fuzzing/tests/oom/bforest_set.rs new file mode 100644 index 000000000000..452fc6d7b974 --- /dev/null +++ b/crates/fuzzing/tests/oom/bforest_set.rs @@ -0,0 +1,19 @@ +use super::Key; +use cranelift_bforest::{Set, SetForest}; +use wasmtime::Result; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn bforest_set() -> Result<()> { + OomTest::new().test(|| { + let mut forest = SetForest::new(); + let mut set = Set::new(); + for i in 0..100 { + set.try_insert(Key(i), &mut forest, &())?; + } + for i in 0..100 { + assert!(set.contains(Key(i), &forest, &())); + } + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/bit_set.rs b/crates/fuzzing/tests/oom/bit_set.rs new file mode 100644 index 000000000000..1fdc493a6454 --- /dev/null +++ b/crates/fuzzing/tests/oom/bit_set.rs @@ -0,0 +1,20 @@ +use cranelift_bitset::CompoundBitSet; +use wasmtime::Result; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn compound_bit_set_try_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _bitset = CompoundBitSet::::try_with_capacity(32)?; + Ok(()) + }) +} + +#[test] +fn compound_bit_set_try_ensure_capacity() -> Result<()> { + OomTest::new().test(|| { + let mut bitset = CompoundBitSet::new(); + bitset.try_ensure_capacity(100)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/boxed.rs b/crates/fuzzing/tests/oom/boxed.rs new file mode 100644 index 000000000000..4b0a2fdfd19f --- /dev/null +++ b/crates/fuzzing/tests/oom/boxed.rs @@ -0,0 +1,11 @@ +use wasmtime::Result; +use wasmtime_environ::collections::try_new; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_new_box() -> Result<()> { + OomTest::new().test(|| { + let _box = try_new::>(36)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/btree_map.rs b/crates/fuzzing/tests/oom/btree_map.rs new file mode 100644 index 000000000000..c03fa113e691 --- /dev/null +++ b/crates/fuzzing/tests/oom/btree_map.rs @@ -0,0 +1,49 @@ +use wasmtime::Result; +use wasmtime_environ::collections::{BTreeMap, btree_map::Entry}; +use wasmtime_fuzzing::oom::OomTest; + +type M = BTreeMap; + +#[test] +fn btree_map() -> Result<()> { + OomTest::new().test(|| { + let mut m = M::new(); + + m.insert(100, 100.0)?; + + m.entry(0).or_insert(99.0)?; + m.entry(0).or_default()?; + + match m.entry(1) { + Entry::Occupied(_) => unreachable!(), + Entry::Vacant(e) => { + let e = e.insert_entry(42.0)?; + e.insert(43.0); + } + }; + + match m.entry(1) { + Entry::Occupied(e) => { + e.remove_entry(); + } + Entry::Vacant(_) => unreachable!(), + } + + match m.entry(2) { + Entry::Occupied(_) => unreachable!(), + Entry::Vacant(e) => { + e.insert(99.0)?; + } + }; + + let _ = m.iter().count(); + let _ = m.iter_mut().count(); + let _ = m.keys().count(); + let _ = m.values().count(); + let _ = m.values_mut().count(); + let _ = m.range(..3).count(); + let _ = m.range_mut(..3).count(); + + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/config.rs b/crates/fuzzing/tests/oom/config.rs new file mode 100644 index 000000000000..dd54e9e4723b --- /dev/null +++ b/crates/fuzzing/tests/oom/config.rs @@ -0,0 +1,11 @@ +use wasmtime::{Config, Result}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn config_new() -> Result<()> { + OomTest::new().test(|| { + let mut config = Config::new(); + config.enable_compiler(false); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/engine.rs b/crates/fuzzing/tests/oom/engine.rs new file mode 100644 index 000000000000..2f666edf5a30 --- /dev/null +++ b/crates/fuzzing/tests/oom/engine.rs @@ -0,0 +1,14 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Result}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn engine_new() -> Result<()> { + OomTest::new().test(|| { + let mut config = Config::new(); + config.enable_compiler(false); + let _ = Engine::new(&config)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/entity_set.rs b/crates/fuzzing/tests/oom/entity_set.rs new file mode 100644 index 000000000000..ddb6228ab5ee --- /dev/null +++ b/crates/fuzzing/tests/oom/entity_set.rs @@ -0,0 +1,22 @@ +use super::Key; +use wasmtime::Result; +use wasmtime_environ::collections::TryEntitySet; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_entity_set_ensure_capacity() -> Result<()> { + OomTest::new().test(|| { + let mut set = TryEntitySet::::new(); + set.ensure_capacity(100)?; + Ok(()) + }) +} + +#[test] +fn try_entity_set_insert() -> Result<()> { + OomTest::new().test(|| { + let mut set = TryEntitySet::::new(); + set.insert(Key::from_u32(256))?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/error.rs b/crates/fuzzing/tests/oom/error.rs new file mode 100644 index 000000000000..97e4a3332627 --- /dev/null +++ b/crates/fuzzing/tests/oom/error.rs @@ -0,0 +1,109 @@ +use std::{ + fmt::{self, Write}, + sync::atomic::{AtomicU32, Ordering::SeqCst}, +}; +use wasmtime::{Error, Result, error::OutOfMemory, format_err}; +use wasmtime_fuzzing::oom::OomTest; + +fn ok_if_not_oom(error: Error) -> Result<()> { + if error.is::() { + Err(error) + } else { + Ok(()) + } +} + +#[test] +fn error_new() -> Result<()> { + OomTest::new().test(|| { + let error = Error::new(u8::try_from(u32::MAX).unwrap_err()); + ok_if_not_oom(error) + }) +} + +#[test] +fn error_msg() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("ouch"); + ok_if_not_oom(error) + }) +} + +static X: AtomicU32 = AtomicU32::new(42); + +#[test] +fn error_fmt() -> Result<()> { + OomTest::new().test(|| { + let x = X.load(SeqCst); + let error = format_err!("ouch: {x}"); + ok_if_not_oom(error) + }) +} + +#[test] +fn error_context() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + ok_if_not_oom(error) + }) +} + +#[test] +fn error_chain() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + for _ in error.chain() { + // Nothing to do here, just exercising the iteration. + } + ok_if_not_oom(error) + }) +} + +struct Null; +impl Write for Null { + fn write_str(&mut self, _s: &str) -> fmt::Result { + Ok(()) + } +} + +#[test] +fn display_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error}").unwrap(); + ok_if_not_oom(error) + }) +} + +#[test] +fn alternate_display_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error:?}").unwrap(); + ok_if_not_oom(error) + }) +} + +#[test] +fn debug_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error:?}").unwrap(); + ok_if_not_oom(error) + }) +} + +#[test] +fn alternate_debug_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error:#?}").unwrap(); + ok_if_not_oom(error) + }) +} diff --git a/crates/fuzzing/tests/oom/func_type.rs b/crates/fuzzing/tests/oom/func_type.rs new file mode 100644 index 000000000000..960f3047a577 --- /dev/null +++ b/crates/fuzzing/tests/oom/func_type.rs @@ -0,0 +1,47 @@ +#![cfg(arc_try_new)] + +use std::iter; +use wasmtime::{Config, Engine, FuncType, Result, ValType}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn func_type_try_new() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + let engine = Engine::new(&config)?; + + // Run this OOM test a few times to make sure that we leave the engine's + // type registry in a good state when failing to register new types. + for i in 1..6 { + OomTest::new().test(|| { + let ty1 = FuncType::try_new( + &engine, + iter::repeat(ValType::ANYREF).take(i), + iter::repeat(ValType::ANYREF).take(i), + )?; + assert_eq!(ty1.params().len(), i); + assert_eq!(ty1.results().len(), i); + + let ty2 = FuncType::try_new( + &engine, + iter::repeat(ValType::ANYREF).take(i), + iter::repeat(ValType::ANYREF).take(i), + )?; + assert_eq!(ty2.params().len(), i); + assert_eq!(ty2.results().len(), i); + + let ty3 = FuncType::try_new(&engine, [], [])?; + assert_eq!(ty3.params().len(), 0); + assert_eq!(ty3.results().len(), 0); + + assert!( + !FuncType::eq(&ty2, &ty3), + "{ty2:?} should not be equal to {ty3:?}" + ); + + Ok(()) + })?; + } + + Ok(()) +} diff --git a/crates/fuzzing/tests/oom/hash_map.rs b/crates/fuzzing/tests/oom/hash_map.rs new file mode 100644 index 000000000000..0e15e856c292 --- /dev/null +++ b/crates/fuzzing/tests/oom/hash_map.rs @@ -0,0 +1,47 @@ +use wasmtime::Result; +use wasmtime_environ::collections::{TryClone, TryHashMap}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_hash_map_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _s = TryHashMap::::with_capacity(100)?; + Ok(()) + }) +} + +#[test] +fn try_hash_map_reserve() -> Result<()> { + OomTest::new().test(|| { + let mut map = TryHashMap::::new(); + map.reserve(100)?; + Ok(()) + }) +} + +#[test] +fn try_hash_map_insert() -> Result<()> { + OomTest::new().test(|| { + let mut map = TryHashMap::::new(); + for i in 0..1024 { + map.insert(i, i * 2)?; + } + for i in 0..1024 { + map.insert(i, i * 2)?; + } + Ok(()) + }) +} + +#[test] +fn try_hash_map_try_clone() -> Result<()> { + OomTest::new().test(|| { + let mut map = TryHashMap::new(); + for i in 0..10 { + map.insert(i, i * 2)?; + } + let map2 = map.try_clone()?; + assert_eq!(map, map2); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/hash_set.rs b/crates/fuzzing/tests/oom/hash_set.rs new file mode 100644 index 000000000000..18d25084f877 --- /dev/null +++ b/crates/fuzzing/tests/oom/hash_set.rs @@ -0,0 +1,34 @@ +use wasmtime::Result; +use wasmtime_environ::collections::TryHashSet; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_hash_set_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _s = TryHashSet::::with_capacity(100)?; + Ok(()) + }) +} + +#[test] +fn try_hash_set_reserve() -> Result<()> { + OomTest::new().test(|| { + let mut set = TryHashSet::::new(); + set.reserve(100)?; + Ok(()) + }) +} + +#[test] +fn try_hash_set_insert() -> Result<()> { + OomTest::new().test(|| { + let mut set = TryHashSet::::new(); + for i in 0..1024 { + set.insert(i)?; + } + for i in 0..1024 { + set.insert(i)?; + } + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/index_map.rs b/crates/fuzzing/tests/oom/index_map.rs new file mode 100644 index 000000000000..00e30177eeb7 --- /dev/null +++ b/crates/fuzzing/tests/oom/index_map.rs @@ -0,0 +1,178 @@ +use wasmtime::Result; +use wasmtime_environ::collections::{TryClone, TryIndexMap, try_new}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_index_map_try_clone() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map1 = TryIndexMap::new(); + map1.insert("a", try_new::>(42)?)?; + map1.insert("b", try_new::>(36)?)?; + let map2 = map1.try_clone()?; + assert_eq!(map1, map2); + Ok(()) + }) +} + +#[test] +fn try_index_map_with_capacity() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let _map = TryIndexMap::<&str, usize>::with_capacity(100)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_split_off() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map1 = TryIndexMap::new(); + map1.insert("a", 42)?; + map1.insert("b", 36)?; + + let map2 = map1.split_off(1)?; + + assert_eq!(map1.len(), 1); + assert_eq!(map2.len(), 1); + assert_eq!(map1[&"a"], 42); + assert_eq!(map1[0], 42); + assert_eq!(map2[&"b"], 36); + assert_eq!(map2[0], 36); + + Ok(()) + }) +} + +#[test] +fn try_index_map_reserve() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::::new(); + map.reserve(100)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_reserve_exact() -> Result<()> { + OomTest::new().test(|| { + let mut map = TryIndexMap::::new(); + map.reserve_exact(100)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_insert() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert(10, 20)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_insert_full() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert_full(10, 20)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_insert_sorted() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert_sorted(10, 20)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_insert_sorted_by() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert_sorted_by(10, 20, |_k, _v, _k2, _v2| core::cmp::Ordering::Less)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_insert_sorted_by_key() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert_sorted_by_key(10, 20, |_k, v| *v)?; + Ok(()) + }) +} + +#[test] +fn try_index_map_insert_before() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert("a", 20)?; + map.insert("b", 30)?; + map.insert_before(1, "c", 40)?; + assert_eq!(map[0], 20); + assert_eq!(map[1], 40); + assert_eq!(map[2], 30); + Ok(()) + }) +} + +#[test] +fn try_index_map_shift_insert() -> Result<()> { + OomTest::new() + // `indexmap` will first try to double its capacity, and, if that fails, + // will then try to allocate only as much as it absolutely must. + .allow_alloc_after_oom(true) + .test(|| { + let mut map = TryIndexMap::new(); + map.insert("a", 20)?; + map.insert("b", 30)?; + map.shift_insert(1, "c", 40)?; + assert_eq!(map[0], 20); + assert_eq!(map[1], 40); + assert_eq!(map[2], 30); + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/linker.rs b/crates/fuzzing/tests/oom/linker.rs new file mode 100644 index 000000000000..b767a86677aa --- /dev/null +++ b/crates/fuzzing/tests/oom/linker.rs @@ -0,0 +1,71 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Linker, Module, Result}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn linker_new() -> Result<()> { + OomTest::new().test(|| { + let mut config = Config::new(); + config.enable_compiler(false); + let engine = Engine::new(&config)?; + let _linker = Linker::<()>::new(&engine); + Ok(()) + }) +} + +#[test] +fn linker_func_wrap() -> Result<()> { + OomTest::new().test(|| { + let mut config = Config::new(); + config.enable_compiler(false); + let engine = Engine::new(&config)?; + let mut linker = Linker::<()>::new(&engine); + linker.func_wrap("module", "func", |x: i32| x * 2)?; + Ok(()) + }) +} + +#[test] +fn linker_instantiate_pre() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = Module::new( + &engine, + r#" + (module + (import "module" "func" (func (param i32) (result i32))) + + (memory (export "memory") 1) + (data (i32.const 0) "a") + + (table (export "table") 1 funcref) + (elem (i32.const 0) func 1) + + (func (export "func") (param i32) (result i32) + (call 0 (local.get 0)) + ) + ) + "#, + )?; + module.serialize()? + }; + + 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("module", "func", |x: i32| x * 2)?; + + let module = unsafe { Module::deserialize(&engine, &module_bytes)? }; + + OomTest::new().test(|| { + let _ = linker.instantiate_pre(&module)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/main.rs b/crates/fuzzing/tests/oom/main.rs new file mode 100644 index 000000000000..2337f6d9a1c0 --- /dev/null +++ b/crates/fuzzing/tests/oom/main.rs @@ -0,0 +1,32 @@ +mod arc; +mod bforest_map; +mod bforest_set; +mod bit_set; +mod boxed; +mod btree_map; +mod config; +mod engine; +mod entity_set; +mod error; +mod func_type; +mod hash_map; +mod hash_set; +mod index_map; +mod linker; +mod module; +mod primary_map; +mod secondary_map; +mod smoke; +mod store; +mod string; +mod vec; + +use wasmtime_fuzzing::oom::OomTestAllocator; + +#[global_allocator] +static GLOBAL_ALLOCATOR: OomTestAllocator = OomTestAllocator::new(); + +/// Entity key for testing fallible `PrimaryMap`s and such. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Key(u32); +wasmtime_environ::entity_impl!(Key); diff --git a/crates/fuzzing/tests/oom/module.rs b/crates/fuzzing/tests/oom/module.rs new file mode 100644 index 000000000000..e63186e30b53 --- /dev/null +++ b/crates/fuzzing/tests/oom/module.rs @@ -0,0 +1,50 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Module, Result}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn module_deserialize() -> Result<()> { + let module_bytes = { + let mut config = Config::new(); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + let module = Module::new( + &engine, + r#" + (module + (import "module" "func" (func (param i32) (result i32))) + + (memory (export "memory") 1) + (data (i32.const 0) "a") + + (table (export "table") 1 funcref) + (elem (i32.const 0) func 1) + + (func (export "func") (param i32) (result i32) + (call 0 (local.get 0)) + ) + ) + "#, + )?; + module.serialize()? + }; + + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + + OomTest::new() + // NB: We use `postcard` to deserialize module metadata, and it will + // return a `postcard::Error::SerdeDeCustom` when we generate an + // `OutOfMemory` error during deserialization. That is then converted + // into a `wasmtime::Error`, and in the process we will attempt to box + // that into an `Error` trait object. There is no good way to avoid all + // this, so just allow allocation attempts after OOM here. + .allow_alloc_after_oom(true) + .test(|| unsafe { + let _ = Module::deserialize(&engine, &module_bytes)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/primary_map.rs b/crates/fuzzing/tests/oom/primary_map.rs new file mode 100644 index 000000000000..3d9cfc1f79c6 --- /dev/null +++ b/crates/fuzzing/tests/oom/primary_map.rs @@ -0,0 +1,30 @@ +use super::Key; +use wasmtime::Result; +use wasmtime_environ::PrimaryMap; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn primary_map_try_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _map = PrimaryMap::::try_with_capacity(32)?; + Ok(()) + }) +} + +#[test] +fn primary_map_try_reserve() -> Result<()> { + OomTest::new().test(|| { + let mut map = PrimaryMap::::new(); + map.try_reserve(100)?; + Ok(()) + }) +} + +#[test] +fn primary_map_try_reserve_exact() -> Result<()> { + OomTest::new().test(|| { + let mut map = PrimaryMap::::new(); + map.try_reserve_exact(13)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/secondary_map.rs b/crates/fuzzing/tests/oom/secondary_map.rs new file mode 100644 index 000000000000..4413ec0bd29c --- /dev/null +++ b/crates/fuzzing/tests/oom/secondary_map.rs @@ -0,0 +1,30 @@ +use super::Key; +use wasmtime::Result; +use wasmtime_environ::SecondaryMap; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn secondary_map_try_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _map = SecondaryMap::::try_with_capacity(32)?; + Ok(()) + }) +} + +#[test] +fn secondary_map_try_resize() -> Result<()> { + OomTest::new().test(|| { + let mut map = SecondaryMap::::new(); + map.try_resize(100)?; + Ok(()) + }) +} + +#[test] +fn secondary_map_try_insert() -> Result<()> { + OomTest::new().test(|| { + let mut map = SecondaryMap::::new(); + map.try_insert(Key::from_u32(42), 100)?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/smoke.rs b/crates/fuzzing/tests/oom/smoke.rs new file mode 100644 index 000000000000..aef769908b99 --- /dev/null +++ b/crates/fuzzing/tests/oom/smoke.rs @@ -0,0 +1,91 @@ +use std::{ + alloc::{Layout, alloc, dealloc}, + ops::Deref, +}; +use wasmtime::{Result, error::OutOfMemory}; +use wasmtime_fuzzing::oom::OomTest; + +/// RAII wrapper around a raw allocation to deallocate it on drop. +struct Alloc { + layout: Layout, + ptr: *mut u8, +} + +impl Drop for Alloc { + fn drop(&mut self) { + if !self.ptr.is_null() { + unsafe { + dealloc(self.ptr, self.layout); + } + } + } +} + +impl Deref for Alloc { + type Target = *mut u8; + + fn deref(&self) -> &Self::Target { + &self.ptr + } +} + +impl Alloc { + /// Safety: same as `std::alloc::alloc`. + unsafe fn new(layout: Layout) -> Self { + let ptr = unsafe { alloc(layout) }; + Alloc { layout, ptr } + } +} + +#[test] +pub(crate) fn smoke_test_ok() -> Result<()> { + OomTest::new().test(|| Ok(())) +} + +#[test] +pub(crate) fn smoke_test_missed_oom() -> Result<()> { + let err = OomTest::new() + .test(|| unsafe { + let _ = Alloc::new(Layout::new::()); + Ok(()) + }) + .unwrap_err(); + let err = format!("{err:?}"); + assert!( + err.contains("OOM test function missed an OOM"), + "should have missed an OOM, got: {err}" + ); + Ok(()) +} + +#[test] +pub(crate) fn smoke_test_disallow_alloc_after_oom() -> Result<()> { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _ = OomTest::new().test(|| unsafe { + let layout = Layout::new::(); + let p = Alloc::new(layout); + let _q = Alloc::new(layout); + if p.is_null() { + Err(OutOfMemory::new(layout.size()).into()) + } else { + Ok(()) + } + }); + })); + assert!(result.is_err()); + Ok(()) +} + +#[test] +pub(crate) fn smoke_test_allow_alloc_after_oom() -> Result<()> { + OomTest::new().allow_alloc_after_oom(true).test(|| unsafe { + let layout = Layout::new::(); + let p = Alloc::new(layout); + let q = Alloc::new(layout); + if p.is_null() || q.is_null() { + Err(OutOfMemory::new(layout.size()).into()) + } else { + Ok(()) + } + }) +} diff --git a/crates/fuzzing/tests/oom/store.rs b/crates/fuzzing/tests/oom/store.rs new file mode 100644 index 000000000000..2f94401cd298 --- /dev/null +++ b/crates/fuzzing/tests/oom/store.rs @@ -0,0 +1,16 @@ +#![cfg(arc_try_new)] + +use wasmtime::{Config, Engine, Result, Store}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn store_try_new() -> Result<()> { + let mut config = Config::new(); + config.enable_compiler(false); + config.concurrency_support(false); + let engine = Engine::new(&config)?; + OomTest::new().test(|| { + let _ = Store::try_new(&engine, ())?; + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/string.rs b/crates/fuzzing/tests/oom/string.rs new file mode 100644 index 000000000000..8eb6293c2666 --- /dev/null +++ b/crates/fuzzing/tests/oom/string.rs @@ -0,0 +1,101 @@ +use wasmtime::Result; +use wasmtime_environ::collections::TryString; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_string_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _s = TryString::with_capacity(100)?; + Ok(()) + }) +} + +#[test] +fn try_string_reserve() -> Result<()> { + OomTest::new().test(|| { + let mut s = TryString::new(); + s.reserve(10)?; + Ok(()) + }) +} + +#[test] +fn try_string_reserve_exact() -> Result<()> { + OomTest::new().test(|| { + let mut s = TryString::new(); + s.reserve_exact(3)?; + Ok(()) + }) +} + +#[test] +fn try_string_push() -> Result<()> { + OomTest::new().test(|| { + let mut s = TryString::new(); + s.push('c')?; + Ok(()) + }) +} + +#[test] +fn try_string_push_str() -> Result<()> { + OomTest::new().test(|| { + let mut s = TryString::new(); + s.push_str("hello")?; + Ok(()) + }) +} + +#[test] +fn try_string_shrink_to_fit() -> Result<()> { + OomTest::new().test(|| { + // len == cap == 0 + let mut s = TryString::new(); + s.shrink_to_fit()?; + + // len == 0 < cap + let mut s = TryString::with_capacity(4)?; + s.shrink_to_fit()?; + + // 0 < len < cap + let mut s = TryString::with_capacity(4)?; + s.push('a')?; + s.shrink_to_fit()?; + + // 0 < len == cap + let mut s = TryString::new(); + s.reserve_exact(2)?; + s.push('a')?; + s.push('a')?; + s.shrink_to_fit()?; + + Ok(()) + }) +} + +#[test] +fn try_string_into_boxed_str() -> Result<()> { + OomTest::new().test(|| { + // len == cap == 0 + let s = TryString::new(); + let _ = s.into_boxed_str()?; + + // len == 0 < cap + let s = TryString::with_capacity(4)?; + let _ = s.into_boxed_str()?; + + // 0 < len < cap + let mut s = TryString::with_capacity(4)?; + s.push('a')?; + let _ = s.into_boxed_str()?; + + // 0 < len == cap + let mut s = TryString::new(); + s.reserve_exact(2)?; + s.push('a')?; + s.push('a')?; + let _ = s.into_boxed_str()?; + + Ok(()) + }) +} diff --git a/crates/fuzzing/tests/oom/vec.rs b/crates/fuzzing/tests/oom/vec.rs new file mode 100644 index 000000000000..a80893a0bde2 --- /dev/null +++ b/crates/fuzzing/tests/oom/vec.rs @@ -0,0 +1,185 @@ +use std::iter; +use wasmtime::Result; +use wasmtime_environ::collections::{TryCollect, TryVec, try_vec}; +use wasmtime_fuzzing::oom::OomTest; + +#[test] +fn try_vec_with_capacity() -> Result<()> { + OomTest::new().test(|| { + let _v = wasmtime_environ::collections::TryVec::::with_capacity(100)?; + Ok(()) + }) +} + +#[test] +fn try_vec_reserve() -> Result<()> { + OomTest::new().test(|| { + let mut v = wasmtime_environ::collections::TryVec::::new(); + v.reserve(10)?; + Ok(()) + }) +} + +#[test] +fn try_vec_reserve_exact() -> Result<()> { + OomTest::new().test(|| { + let mut v = wasmtime_environ::collections::TryVec::::new(); + v.reserve_exact(3)?; + Ok(()) + }) +} + +#[test] +fn try_vec_push() -> Result<()> { + OomTest::new().test(|| { + let mut v = wasmtime_environ::collections::TryVec::new(); + v.push(42)?; + Ok(()) + }) +} + +#[test] +fn try_vec_and_boxed_slice() -> Result<()> { + use wasmtime_core::alloc::TryVec; + + OomTest::new().test(|| { + // Nonzero-sized type. + let mut vec = TryVec::new(); + vec.push(1)?; + let slice = vec.into_boxed_slice()?; // len > 0, cap > 0 + + let mut vec = TryVec::from(slice); + vec.pop(); + let slice = vec.into_boxed_slice()?; // len = 0, cap > 0 + + let vec = TryVec::from(slice); + let _slice = vec.into_boxed_slice()?; // len = 0, cap = 0 + + let mut vec = TryVec::new(); + vec.reserve_exact(3)?; + vec.push(2)?; + vec.push(2)?; + vec.push(2)?; + let _slice = vec.into_boxed_slice()?; // len = cap, len > 0 + + for i in 0..12 { + let mut vec = TryVec::new(); + for j in 0..i { + vec.push(j)?; + } + let _slice = vec.into_boxed_slice()?; // len ?= cap + } + + // Zero-sized type. + let mut vec = TryVec::new(); + vec.push(())?; + let slice = vec.into_boxed_slice()?; // len > 0, cap > 0 + let mut vec = TryVec::from(slice); + vec.pop(); + let slice = vec.into_boxed_slice()?; // len = 0, cap > 0 + let vec = TryVec::from(slice); + let _ = vec.into_boxed_slice()?; // len = 0, cap = 0 + + Ok(()) + }) +} + +#[test] +fn try_vec_shrink_to_fit() -> Result<()> { + use wasmtime_core::alloc::TryVec; + + #[derive(Default)] + struct ZeroSized; + + #[derive(Default)] + struct NonZeroSized { + _unused: usize, + } + + fn do_test() -> Result<()> { + // len == cap == 0 + let mut v = TryVec::::new(); + v.shrink_to_fit()?; + + // len == 0 < cap + let mut v = TryVec::::with_capacity(4)?; + v.shrink_to_fit()?; + + // 0 < len < cap + let mut v = TryVec::with_capacity(4)?; + v.push(T::default())?; + v.shrink_to_fit()?; + + // 0 < len == cap + let mut v = TryVec::new(); + v.reserve_exact(2)?; + v.push(T::default())?; + v.push(T::default())?; + v.shrink_to_fit()?; + + Ok(()) + } + + OomTest::new().test(|| do_test::())?; + OomTest::new().test(|| do_test::())?; + Ok(()) +} + +#[test] +fn try_vec_resize() -> Result<()> { + OomTest::new().test(|| { + let mut v = TryVec::new(); + v.resize(10, 'a')?; // Grow. + v.resize(1, 'b')?; // Truncate. + v.resize(1, 'c')?; // Same length. + v.resize(3, 'd')?; // Grow again. + assert_eq!(&*v, &['a', 'd', 'd']); + Ok(()) + }) +} + +#[test] +fn try_vec_try_collect() -> Result<()> { + OomTest::new().test(|| { + iter::repeat(1).take(0).try_collect::, _>()?; + iter::repeat(1).take(1).try_collect::, _>()?; + iter::repeat(1).take(100).try_collect::, _>()?; + iter::repeat(()).take(100).try_collect::, _>()?; + Ok(()) + }) +} + +#[test] +fn try_vec_extend() -> Result<()> { + use wasmtime_core::alloc::{TryExtend, TryVec}; + OomTest::new().test(|| { + let mut vec = TryVec::new(); + vec.try_extend([])?; + vec.try_extend([1])?; + vec.try_extend([1, 2, 3, 4])?; + + let mut vec = TryVec::new(); + vec.try_extend([])?; + vec.try_extend([()])?; + vec.try_extend([(), (), ()])?; + Ok(()) + }) +} + +#[test] +fn try_vec_macro_elems() -> Result<()> { + OomTest::new().test(|| { + let v = try_vec![100, 200, 300, 400]?; + assert_eq!(&*v, &[100, 200, 300, 400]); + Ok(()) + }) +} + +#[test] +fn try_vec_macro_elem_len() -> Result<()> { + OomTest::new().test(|| { + let v = try_vec![100; 3]?; + assert_eq!(&*v, &[100, 100, 100]); + Ok(()) + }) +}