Skip to content

Commit 6cb6c3c

Browse files
authored
Add implementation limits + error (#1980)
* add AnyHandle type for InstanceEntity * move limits.rs -> limiter.rs * rename Error::Limits -> UserLimits * update test after renaming * add implementation limits + error * remove AnyHandle (oups: from other PR)
1 parent 3dc99f5 commit 6cb6c3c

5 files changed

Lines changed: 353 additions & 210 deletions

File tree

crates/wasmi/src/engine/limits/tests.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn max_globals_err() {
4545
};
4646
assert!(matches!(
4747
parse_with(wasm, limits).unwrap_err().kind(),
48-
ErrorKind::Limits(EnforcedLimitsError::TooManyGlobals { limit: 2 }),
48+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyGlobals { limit: 2 }),
4949
))
5050
}
5151

@@ -84,7 +84,7 @@ fn max_functions_err() {
8484
};
8585
assert!(matches!(
8686
parse_with(wasm, limits).unwrap_err().kind(),
87-
ErrorKind::Limits(EnforcedLimitsError::TooManyFunctions { limit: 2 }),
87+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyFunctions { limit: 2 }),
8888
))
8989
}
9090

@@ -123,7 +123,7 @@ fn max_tables_err() {
123123
};
124124
assert!(matches!(
125125
parse_with(wasm, limits).unwrap_err().kind(),
126-
ErrorKind::Limits(EnforcedLimitsError::TooManyTables { limit: 2 }),
126+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyTables { limit: 2 }),
127127
))
128128
}
129129

@@ -162,7 +162,7 @@ fn max_memories_err() {
162162
};
163163
assert!(matches!(
164164
parse_with(wasm, limits).unwrap_err().kind(),
165-
ErrorKind::Limits(EnforcedLimitsError::TooManyMemories { limit: 2 }),
165+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyMemories { limit: 2 }),
166166
))
167167
}
168168

@@ -205,7 +205,7 @@ fn max_element_segments_err() {
205205
};
206206
assert!(matches!(
207207
parse_with(wasm, limits).unwrap_err().kind(),
208-
ErrorKind::Limits(EnforcedLimitsError::TooManyElementSegments { limit: 2 }),
208+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyElementSegments { limit: 2 }),
209209
))
210210
}
211211

@@ -246,7 +246,7 @@ fn max_data_segments_err() {
246246
};
247247
assert!(matches!(
248248
parse_with(wasm, limits).unwrap_err().kind(),
249-
ErrorKind::Limits(EnforcedLimitsError::TooManyDataSegments { limit: 2 }),
249+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyDataSegments { limit: 2 }),
250250
))
251251
}
252252

@@ -282,7 +282,7 @@ fn max_params_func_err() {
282282
};
283283
assert!(matches!(
284284
parse_with(wasm, limits).unwrap_err().kind(),
285-
ErrorKind::Limits(EnforcedLimitsError::TooManyParameters { limit: 2 }),
285+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyParameters { limit: 2 }),
286286
))
287287
}
288288

@@ -333,7 +333,7 @@ fn max_params_control_err() {
333333
};
334334
assert!(matches!(
335335
parse_with(wasm, limits).unwrap_err().kind(),
336-
ErrorKind::Limits(EnforcedLimitsError::TooManyParameters { limit: 2 }),
336+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyParameters { limit: 2 }),
337337
))
338338
}
339339

@@ -376,7 +376,7 @@ fn max_results_func_err() {
376376
};
377377
assert!(matches!(
378378
parse_with(wasm, limits).unwrap_err().kind(),
379-
ErrorKind::Limits(EnforcedLimitsError::TooManyResults { limit: 2 }),
379+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyResults { limit: 2 }),
380380
))
381381
}
382382

@@ -428,7 +428,7 @@ fn max_results_control_err() {
428428
};
429429
assert!(matches!(
430430
parse_with(wasm, limits).unwrap_err().kind(),
431-
ErrorKind::Limits(EnforcedLimitsError::TooManyResults { limit: 2 }),
431+
ErrorKind::UserLimits(EnforcedLimitsError::TooManyResults { limit: 2 }),
432432
))
433433
}
434434

@@ -487,7 +487,7 @@ fn min_avg_code_bytes_err() {
487487
std::println!("{:?}", parse_with(wasm, limits).unwrap_err());
488488
assert!(matches!(
489489
parse_with(wasm, limits).unwrap_err().kind(),
490-
ErrorKind::Limits(EnforcedLimitsError::MinAvgBytesPerFunction { limit: 6, avg: 5 }),
490+
ErrorKind::UserLimits(EnforcedLimitsError::MinAvgBytesPerFunction { limit: 6, avg: 5 }),
491491
))
492492
}
493493

crates/wasmi/src/error.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use super::errors::{
99
use crate::{
1010
TrapCode,
1111
engine::{ResumableHostTrapError, ResumableOutOfFuelError, TranslationError},
12+
limits::LimitsError,
1213
module::ReadError,
1314
};
1415
use alloc::{boxed::Box, string::String};
@@ -201,7 +202,9 @@ pub enum ErrorKind {
201202
/// Encountered when there is a Wasm to Wasmi translation error.
202203
Translation(TranslationError),
203204
/// Encountered when an enforced limit is exceeded.
204-
Limits(EnforcedLimitsError),
205+
UserLimits(EnforcedLimitsError),
206+
/// Encountered when a Wasmi implementation limit is exceeded.
207+
ImplementationLimits(LimitsError),
205208
/// Encountered for Wasmi bytecode related errors.
206209
Ir(IrError),
207210
/// Encountered an error from the `wat` crate.
@@ -282,7 +285,8 @@ impl Display for ErrorKind {
282285
Self::Read(error) => Display::fmt(error, f),
283286
Self::Wasm(error) => Display::fmt(error, f),
284287
Self::Translation(error) => Display::fmt(error, f),
285-
Self::Limits(error) => Display::fmt(error, f),
288+
Self::UserLimits(error) => Display::fmt(error, f),
289+
Self::ImplementationLimits(error) => Display::fmt(error, f),
286290
Self::ResumableHostTrap(error) => Display::fmt(error, f),
287291
Self::ResumableOutOfFuel(error) => Display::fmt(error, f),
288292
Self::Ir(error) => Display::fmt(error, f),
@@ -317,7 +321,8 @@ impl_from! {
317321
impl From<ReadError> for Error::Read;
318322
impl From<FuelError> for Error::Fuel;
319323
impl From<FuncError> for Error::Func;
320-
impl From<EnforcedLimitsError> for Error::Limits;
324+
impl From<EnforcedLimitsError> for Error::UserLimits;
325+
impl From<LimitsError> for Error::ImplementationLimits;
321326
impl From<ResumableHostTrapError> for Error::ResumableHostTrap;
322327
impl From<ResumableOutOfFuelError> for Error::ResumableOutOfFuel;
323328
impl From<IrError> for Error::Ir;

crates/wasmi/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ mod error;
117117
mod func;
118118
mod global;
119119
mod instance;
120+
mod limiter;
120121
mod limits;
121122
mod linker;
122123
mod memory;
@@ -208,7 +209,7 @@ pub use self::{
208209
},
209210
global::Global,
210211
instance::{Export, ExportsIter, Extern, ExternType, Instance},
211-
limits::{StoreLimits, StoreLimitsBuilder},
212+
limiter::{StoreLimits, StoreLimitsBuilder},
212213
linker::Linker,
213214
memory::{Memory, MemoryType, MemoryTypeBuilder},
214215
module::{

crates/wasmi/src/limiter.rs

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
use crate::{
2+
ResourceLimiter,
3+
core::LimiterError,
4+
errors::{MemoryError, TableError},
5+
};
6+
7+
/// Value returned by [`ResourceLimiter::instances`] default method
8+
pub const DEFAULT_INSTANCE_LIMIT: usize = 10_000;
9+
10+
/// Value returned by [`ResourceLimiter::tables`] default method
11+
pub const DEFAULT_TABLE_LIMIT: usize = 10_000;
12+
13+
/// Value returned by [`ResourceLimiter::memories`] default method
14+
pub const DEFAULT_MEMORY_LIMIT: usize = 10_000;
15+
16+
/// Used to build [`StoreLimits`].
17+
pub struct StoreLimitsBuilder(StoreLimits);
18+
19+
impl StoreLimitsBuilder {
20+
/// Creates a new [`StoreLimitsBuilder`].
21+
///
22+
/// See the documentation on each builder method for the default for each
23+
/// value.
24+
pub fn new() -> Self {
25+
Self(StoreLimits::default())
26+
}
27+
28+
/// The maximum number of bytes a linear memory can grow to.
29+
///
30+
/// Growing a linear memory beyond this limit will fail. This limit is
31+
/// applied to each linear memory individually, so if a wasm module has
32+
/// multiple linear memories then they're all allowed to reach up to the
33+
/// `limit` specified.
34+
///
35+
/// By default, linear memory will not be limited.
36+
pub fn memory_size(mut self, limit: usize) -> Self {
37+
self.0.memory_size = Some(limit);
38+
self
39+
}
40+
41+
/// The maximum number of elements in a table.
42+
///
43+
/// Growing a table beyond this limit will fail. This limit is applied to
44+
/// each table individually, so if a wasm module has multiple tables then
45+
/// they're all allowed to reach up to the `limit` specified.
46+
///
47+
/// By default, table elements will not be limited.
48+
pub fn table_elements(mut self, limit: usize) -> Self {
49+
self.0.table_elements = Some(limit);
50+
self
51+
}
52+
53+
/// The maximum number of instances that can be created for a [`Store`](crate::Store).
54+
///
55+
/// Module instantiation will fail if this limit is exceeded.
56+
///
57+
/// This value defaults to 10,000.
58+
pub fn instances(mut self, limit: usize) -> Self {
59+
self.0.instances = limit;
60+
self
61+
}
62+
63+
/// The maximum number of tables that can be created for a [`Store`](crate::Store).
64+
///
65+
/// Module instantiation will fail if this limit is exceeded.
66+
///
67+
/// This value defaults to 10,000.
68+
pub fn tables(mut self, tables: usize) -> Self {
69+
self.0.tables = tables;
70+
self
71+
}
72+
73+
/// The maximum number of linear memories that can be created for a [`Store`](crate::Store).
74+
///
75+
/// Instantiation will fail with an error if this limit is exceeded.
76+
///
77+
/// This value defaults to 10,000.
78+
pub fn memories(mut self, memories: usize) -> Self {
79+
self.0.memories = memories;
80+
self
81+
}
82+
83+
/// Indicates that a trap should be raised whenever a growth operation
84+
/// would fail.
85+
///
86+
/// This operation will force `memory.grow` and `table.grow` instructions
87+
/// to raise a trap on failure instead of returning -1. This is not
88+
/// necessarily spec-compliant, but it can be quite handy when debugging a
89+
/// module that fails to allocate memory and might behave oddly as a result.
90+
///
91+
/// This value defaults to `false`.
92+
pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
93+
self.0.trap_on_grow_failure = trap;
94+
self
95+
}
96+
97+
/// Consumes this builder and returns the [`StoreLimits`].
98+
pub fn build(self) -> StoreLimits {
99+
self.0
100+
}
101+
}
102+
103+
impl Default for StoreLimitsBuilder {
104+
fn default() -> Self {
105+
Self::new()
106+
}
107+
}
108+
109+
/// Provides limits for a [`Store`](crate::Store).
110+
///
111+
/// This type is created with a [`StoreLimitsBuilder`] and is typically used in
112+
/// conjunction with [`Store::limiter`](crate::Store::limiter).
113+
///
114+
/// This is a convenience type included to avoid needing to implement the
115+
/// [`ResourceLimiter`] trait if your use case fits in the static configuration
116+
/// that this [`StoreLimits`] provides.
117+
#[derive(Clone, Debug)]
118+
pub struct StoreLimits {
119+
memory_size: Option<usize>,
120+
table_elements: Option<usize>,
121+
instances: usize,
122+
tables: usize,
123+
memories: usize,
124+
trap_on_grow_failure: bool,
125+
}
126+
127+
impl Default for StoreLimits {
128+
fn default() -> Self {
129+
Self {
130+
memory_size: None,
131+
table_elements: None,
132+
instances: DEFAULT_INSTANCE_LIMIT,
133+
tables: DEFAULT_TABLE_LIMIT,
134+
memories: DEFAULT_MEMORY_LIMIT,
135+
trap_on_grow_failure: false,
136+
}
137+
}
138+
}
139+
140+
impl ResourceLimiter for StoreLimits {
141+
fn memory_growing(
142+
&mut self,
143+
_current: usize,
144+
desired: usize,
145+
maximum: Option<usize>,
146+
) -> Result<bool, LimiterError> {
147+
let allow = match self.memory_size {
148+
Some(limit) if desired > limit => false,
149+
_ => match maximum {
150+
Some(max) if desired > max => false,
151+
Some(_) | None => true,
152+
},
153+
};
154+
if !allow && self.trap_on_grow_failure {
155+
return Err(LimiterError::ResourceLimiterDeniedAllocation);
156+
}
157+
Ok(allow)
158+
}
159+
160+
fn memory_grow_failed(&mut self, _error: &MemoryError) -> Result<(), LimiterError> {
161+
if self.trap_on_grow_failure {
162+
return Err(LimiterError::ResourceLimiterDeniedAllocation);
163+
}
164+
Ok(())
165+
}
166+
167+
fn table_growing(
168+
&mut self,
169+
_current: usize,
170+
desired: usize,
171+
maximum: Option<usize>,
172+
) -> Result<bool, LimiterError> {
173+
let allow = match self.table_elements {
174+
Some(limit) if desired > limit => false,
175+
_ => match maximum {
176+
Some(max) if desired > max => false,
177+
Some(_) | None => true,
178+
},
179+
};
180+
if !allow && self.trap_on_grow_failure {
181+
return Err(LimiterError::ResourceLimiterDeniedAllocation);
182+
}
183+
Ok(allow)
184+
}
185+
186+
fn table_grow_failed(&mut self, _error: &TableError) -> Result<(), LimiterError> {
187+
if self.trap_on_grow_failure {
188+
return Err(LimiterError::ResourceLimiterDeniedAllocation);
189+
}
190+
Ok(())
191+
}
192+
193+
fn instances(&self) -> usize {
194+
self.instances
195+
}
196+
197+
fn tables(&self) -> usize {
198+
self.tables
199+
}
200+
201+
fn memories(&self) -> usize {
202+
self.memories
203+
}
204+
}

0 commit comments

Comments
 (0)