Skip to main content

submilli_wasm/store/
limits.rs

1//! Resource limiting: the `ResourceLimiter` trait and `StoreLimits[Builder]`.
2
3#[cfg(test)]
4#[path = "limits_tests.rs"]
5mod tests;
6
7use crate::{Error, Result};
8
9const DEFAULT_LIMIT: usize = 10_000;
10
11/// The finite default ceiling on a single memory's size (bytes), applied **only when no
12/// [`ResourceLimiter`] is installed** — with a limiter, the limiter is the sole bound. It equals the
13/// 32-bit architectural maximum (4 GiB), so `memory32` behavior is unchanged while a `memory64`
14/// (whose architectural ceiling is 2^48 pages) is held to a finite multi-tenant default rather than
15/// being effectively unbounded. Tunable policy knob (see `SECURITY.md`); the GC heap's analog
16/// is [`super::gc::ABORT_SAFETY_CAP`]. Clamped to `isize::MAX` on 32-bit hosts because
17/// Rust allocations cannot exceed that size.
18pub(crate) const DEFAULT_MEMORY_CEILING_BYTES: usize = host_ceiling(1u64 << 32);
19
20/// The finite default ceiling on a single table's element count, applied **only when no
21/// [`ResourceLimiter`] is installed**. Equals the 32-bit architectural maximum (`u32::MAX`), so
22/// `table32` is unchanged while a `table64` (architectural ceiling `u64::MAX`) is held finite.
23/// Clamped to `isize::MAX` on 32-bit hosts; the backing allocation imposes a tighter byte limit.
24pub(crate) const DEFAULT_TABLE_CEILING_ELEMS: usize = host_ceiling(u32::MAX as u64);
25
26const fn host_ceiling(limit: u64) -> usize {
27    if limit > isize::MAX as u64 {
28        isize::MAX as usize
29    } else {
30        limit as usize
31    }
32}
33
34/// Controls resource growth (memory/table) and entity counts for a store.
35pub trait ResourceLimiter {
36    fn memory_growing(
37        &mut self,
38        current: usize,
39        desired: usize,
40        maximum: Option<usize>,
41    ) -> Result<bool>;
42
43    fn table_growing(
44        &mut self,
45        current: usize,
46        desired: usize,
47        maximum: Option<usize>,
48    ) -> Result<bool>;
49
50    fn memory_grow_failed(&mut self, error: Error) -> Result<()> {
51        Ok(())
52    }
53
54    fn table_grow_failed(&mut self, error: Error) -> Result<()> {
55        Ok(())
56    }
57
58    fn instances(&self) -> usize {
59        DEFAULT_LIMIT
60    }
61
62    fn tables(&self) -> usize {
63        DEFAULT_LIMIT
64    }
65
66    fn memories(&self) -> usize {
67        DEFAULT_LIMIT
68    }
69}
70
71/// Async sibling of [`ResourceLimiter`]: growth decisions may `.await` (e.g. consult an
72/// async quota service). Used via [`Store::limiter_async`](crate::Store::limiter_async).
73#[cfg(feature = "async")]
74#[async_trait::async_trait]
75pub trait ResourceLimiterAsync: Send {
76    async fn memory_growing(
77        &mut self,
78        current: usize,
79        desired: usize,
80        maximum: Option<usize>,
81    ) -> Result<bool>;
82
83    async fn table_growing(
84        &mut self,
85        current: usize,
86        desired: usize,
87        maximum: Option<usize>,
88    ) -> Result<bool>;
89
90    fn memory_grow_failed(&mut self, error: Error) -> Result<()> {
91        Ok(())
92    }
93
94    fn table_grow_failed(&mut self, error: Error) -> Result<()> {
95        Ok(())
96    }
97
98    fn instances(&self) -> usize {
99        DEFAULT_LIMIT
100    }
101
102    fn tables(&self) -> usize {
103        DEFAULT_LIMIT
104    }
105
106    fn memories(&self) -> usize {
107        DEFAULT_LIMIT
108    }
109}
110
111/// A ready-made [`ResourceLimiter`] configured by [`StoreLimitsBuilder`].
112#[derive(Clone, Debug)]
113pub struct StoreLimits {
114    memory_size: Option<usize>,
115    table_elements: Option<usize>,
116    instances: usize,
117    tables: usize,
118    memories: usize,
119    trap_on_grow_failure: bool,
120}
121
122impl Default for StoreLimits {
123    fn default() -> Self {
124        StoreLimits {
125            memory_size: None,
126            table_elements: None,
127            instances: DEFAULT_LIMIT,
128            tables: DEFAULT_LIMIT,
129            memories: DEFAULT_LIMIT,
130            trap_on_grow_failure: false,
131        }
132    }
133}
134
135impl ResourceLimiter for StoreLimits {
136    fn memory_growing(
137        &mut self,
138        current: usize,
139        desired: usize,
140        maximum: Option<usize>,
141    ) -> Result<bool> {
142        let within_config = self.memory_size.is_none_or(|limit| desired <= limit);
143        let within_max = maximum.is_none_or(|max| desired <= max);
144        Ok(within_config && within_max)
145    }
146
147    fn table_growing(
148        &mut self,
149        current: usize,
150        desired: usize,
151        maximum: Option<usize>,
152    ) -> Result<bool> {
153        let within_config = self.table_elements.is_none_or(|limit| desired <= limit);
154        let within_max = maximum.is_none_or(|max| desired <= max);
155        Ok(within_config && within_max)
156    }
157
158    fn memory_grow_failed(&mut self, error: Error) -> Result<()> {
159        if self.trap_on_grow_failure {
160            Err(error)
161        } else {
162            Ok(())
163        }
164    }
165
166    fn table_grow_failed(&mut self, error: Error) -> Result<()> {
167        if self.trap_on_grow_failure {
168            Err(error)
169        } else {
170            Ok(())
171        }
172    }
173
174    fn instances(&self) -> usize {
175        self.instances
176    }
177
178    fn tables(&self) -> usize {
179        self.tables
180    }
181
182    fn memories(&self) -> usize {
183        self.memories
184    }
185}
186
187/// Builder for [`StoreLimits`].
188#[derive(Debug)]
189pub struct StoreLimitsBuilder(StoreLimits);
190
191impl StoreLimitsBuilder {
192    pub fn new() -> Self {
193        StoreLimitsBuilder(StoreLimits::default())
194    }
195
196    pub fn memory_size(mut self, limit: usize) -> Self {
197        self.0.memory_size = Some(limit);
198        self
199    }
200
201    pub fn table_elements(mut self, limit: usize) -> Self {
202        self.0.table_elements = Some(limit);
203        self
204    }
205
206    pub fn instances(mut self, limit: usize) -> Self {
207        self.0.instances = limit;
208        self
209    }
210
211    pub fn tables(mut self, limit: usize) -> Self {
212        self.0.tables = limit;
213        self
214    }
215
216    pub fn memories(mut self, limit: usize) -> Self {
217        self.0.memories = limit;
218        self
219    }
220
221    pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
222        self.0.trap_on_grow_failure = trap;
223        self
224    }
225
226    pub fn build(self) -> StoreLimits {
227        self.0
228    }
229}
230
231impl Default for StoreLimitsBuilder {
232    fn default() -> Self {
233        StoreLimitsBuilder::new()
234    }
235}