Skip to main content

wasmtime_internal_core/error/
oom.rs

1use crate::error::{Error, OomOrDynError};
2use core::{fmt, mem, ptr::NonNull};
3
4/// An out-of-memory (OOM) error.
5///
6/// This error is the sentinel for allocation failure due to memory exhaustion.
7///
8/// Constructing an [`Error`] from an `OutOfMemory` does not allocate.
9///
10/// Allocation failure inside any `Error` method that must allocate
11/// (e.g. [`Error::context`]) will propagate an `OutOfMemory` error.
12///
13/// # Out-of-Memory Handling in Wasmtime
14///
15/// Wasmtime performs out-of-memory (OOM) error handling on a **best-effort
16/// basis**. OOM handling does not have [tier 1
17/// support](https://docs.wasmtime.dev/stability-tiers.html) at this time and,
18/// therefore, while failure to handle OOM at some allocation site may be
19/// considered a bug, and might be a potential denial-of-service vector, it
20/// would not be considered a security vulnerability.[^limits]
21///
22/// [^limits]: Note that unconstrained guest-controlled resource usage is still
23/// considered a vulnerability. Wasmtime has tier 1 support for limiting guest
24/// resources, but not for handling OOMs within those limits.
25///
26/// ## Where Wasmtime Attempts to Handle OOM
27///
28/// It is important to note that **not all portions of Wasmtime attempt to
29/// handle out-of-memory errors**. Notably, Wasmtime only ever attempts to
30/// handle OOM in the core *runtime* and never in the *compiler*. No attempt is
31/// made to handle allocation failure in the middle of compiling new `Module`s
32/// or `Component`s from Wasm to machine code (or Pulley bytecode). However,
33/// Wasmtime will attempt to handle OOM when running *pre-compiled* Wasm code
34/// (loaded via `Module::deserialize` or `Component::deserialize`).
35///
36/// Wasmtime's interfaces allow *you* to handle OOM in your own embedding's WASI
37/// implementations and host APIs, but Wasmtime's provided WASI implementations
38/// (e.g. `wasmtime_wasi_http`) will generally not attempt to handle OOM (as
39/// they often depend on third-party crates that do not attempt to handle OOM).
40///
41/// The API documentation for individual functions and methods that handle OOM
42/// should generally document this fact by listing `OutOfMemory` as one of the
43/// potential errors returned.
44///
45/// | **Where**                                       | **Handles OOM?**                |
46/// |-------------------------------------------------|---------------------------------|
47/// | **Compiler**                                    | **No**                          |
48/// |  `wasmtime::Module::new`                   | No                              |
49/// |  `wasmtime::Component::new`                | No                              |
50/// |  `wasmtime::CodeBuilder`                   | No                              |
51/// |  Other compilation APIs...                 | No                              |
52/// | **Runtime**                                     | **Yes**                         |
53/// |  `wasmtime::Store`                         | Yes                             |
54/// |  `wasmtime::Linker`                        | Yes                             |
55/// |  `wasmtime::Module::deserialize`           | Yes                             |
56/// |  `wasmtime::Instance`                      | Yes                             |
57/// |  `wasmtime::Func::call`                    | Yes                             |
58/// |  Component Model concurrency/async APIs    | Not yet                         |
59/// |  Other instantiation and execution APIs... | Yes                             |
60/// | **WASI Implementations and Host APIs**          | **Depends**                     |
61/// |  `wasmtime_wasi`                           | No                              |
62/// |  `wasmtime_wasi_http`                      | No                              |
63/// |  `wasmtime_wasi_*`                         | No                              |
64/// |  Your embedding's APIs                     | If *you* implement OOM handling |
65///
66/// If you encounter an unhandled OOM inside Wasmtime, and it is within a
67/// portion of code where it should be handled, then please [file an
68/// issue](https://github.com/bytecodealliance/wasmtime/issues/new/choose).
69///
70/// ## Handling More OOMs with Rust Nightly APIs
71///
72/// Rust's standard library provides fallible allocation APIs, or the necessary
73/// building blocks for making our own fallible allocation APIs, for some of its
74/// types and collections. For example, it provides `Vec::try_reserve` which can
75/// be used to build a fallible version of `Vec::push` and fallible `Box`
76/// allocation can be built upon raw allocations from the global allocator and
77/// `Box::from_raw`.
78///
79/// However, the standard library does not provide these things for all the
80/// types and collections that Wasmtime uses. Some of these APIs are completely
81/// missing (such as a fallible version of
82/// `std::collections::hash_map::VacantEntry::insert`) and some APIs exist but
83/// are feature-gated on unstable, nightly-only Rust features. The most relevant
84/// API from this latter category is
85/// [`Arc::try_new`](https://doc.rust-lang.org/nightly/std/sync/struct.Arc.html#method.try_new),
86/// as Wasmtime's runtime uses a number of `Arc`s under the covers.
87///
88/// If handling OOMs is important for your Wasmtime embedding, then you should
89/// compile Wasmtime from source using a Nightly Rust toolchain and with the
90/// `RUSTFLAGS="--cfg arc_try_new"` environment variable set. This unlocks
91/// Wasmtime's internal usage of `Arc::try_new`, making more OOM handling at
92/// more allocation sites possible.
93#[derive(Clone, Copy)]
94// NB: `OutOfMemory`'s representation must be the same as `OomOrDynError`
95// (and therefore also `Error`).
96#[repr(transparent)]
97pub struct OutOfMemory {
98    inner: NonNull<u8>,
99}
100
101// Safety: The `inner` pointer is not a real pointer, it is just bitpacked size
102// data.
103unsafe impl Send for OutOfMemory {}
104
105// Safety: The `inner` pointer is not a real pointer, it is just bitpacked size
106// data.
107unsafe impl Sync for OutOfMemory {}
108
109impl fmt::Debug for OutOfMemory {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.debug_struct("OutOfMemory")
112            .field(
113                "requested_allocation_size",
114                &self.requested_allocation_size(),
115            )
116            .finish()
117    }
118}
119
120impl fmt::Display for OutOfMemory {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(
123            f,
124            "out of memory (failed to allocate {} bytes)",
125            self.requested_allocation_size()
126        )
127    }
128}
129
130impl core::error::Error for OutOfMemory {
131    #[inline]
132    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
133        None
134    }
135}
136
137impl OutOfMemory {
138    // NB: `OutOfMemory`'s representation must be the same as `OomOrDynError`
139    // (and therefore also `Error`).
140    const _SAME_SIZE_AS_OOM_OR_DYN_ERROR: () =
141        assert!(mem::size_of::<OutOfMemory>() == mem::size_of::<OomOrDynError>());
142    const _SAME_ALIGN_AS_OOM_OR_DYN_ERROR: () =
143        assert!(mem::align_of::<OutOfMemory>() == mem::align_of::<OomOrDynError>());
144    const _SAME_SIZE_AS_ERROR: () =
145        assert!(mem::size_of::<OutOfMemory>() == mem::size_of::<Error>());
146    const _SAME_ALIGN_AS_ERROR: () =
147        assert!(mem::align_of::<OutOfMemory>() == mem::align_of::<Error>());
148
149    /// Construct a new `OutOfMemory` error.
150    ///
151    /// The `requested_allocation_size` argument should be the size (in bytes)
152    /// of the associated allocation that was attempted and failed.
153    ///
154    /// This operation does not allocate.
155    ///
156    /// # Example
157    ///
158    /// ```rust
159    /// # use wasmtime_internal_core::error::OutOfMemory;
160    /// # extern crate alloc;
161    /// use alloc::alloc::{Layout, alloc};
162    /// use core::ptr::NonNull;
163    ///
164    /// /// Attempt to allocate a block of memory from the global allocator,
165    /// /// returning an `OutOfMemory` error on failure.
166    /// fn try_global_alloc(layout: Layout) -> Result<NonNull<u8>, OutOfMemory> {
167    ///     if layout.size() == 0 {
168    ///         return Ok(NonNull::dangling());
169    ///     }
170    ///
171    ///     // Safety: the layout's size is non-zero.
172    ///     let ptr = unsafe { alloc(layout) };
173    ///
174    ///     if let Some(ptr) = NonNull::new(ptr) {
175    ///         Ok(ptr)
176    ///     } else {
177    ///         // The allocation failed, so return an `OutOfMemory` error,
178    ///         // passing the attempted allocation's size into the `OutOfMemory`
179    ///         // constructor.
180    ///         Err(OutOfMemory::new(layout.size()))
181    ///     }
182    /// }
183    /// ```
184    #[inline]
185    pub const fn new(requested_allocation_size: usize) -> Self {
186        Self {
187            inner: OomOrDynError::new_oom_ptr(requested_allocation_size),
188        }
189    }
190
191    /// Get the size (in bytes) of the associated allocation that was attempted
192    /// and which failed.
193    ///
194    /// Very large allocation sizes (near `isize::MAX` and larger) may be capped
195    /// to a maximum value.
196    ///
197    /// # Example
198    ///
199    /// ```rust
200    /// # use wasmtime_internal_core::error::OutOfMemory;
201    /// let oom = OutOfMemory::new(8192);
202    /// assert_eq!(oom.requested_allocation_size(), 8192);
203    /// ```
204    #[inline]
205    pub fn requested_allocation_size(&self) -> usize {
206        OomOrDynError::oom_size(self.inner)
207    }
208}
209
210impl From<OutOfMemory> for OomOrDynError {
211    fn from(oom: OutOfMemory) -> Self {
212        OomOrDynError::new_oom(oom.inner)
213    }
214}