Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/limits.rs
3063 views
1
use wasmtime::*;
2
3
const WASM_PAGE_SIZE: usize = wasmtime_environ::Memory::DEFAULT_PAGE_SIZE as usize;
4
5
#[test]
6
#[cfg_attr(miri, ignore)]
7
fn test_limits() -> Result<()> {
8
let engine = Engine::default();
9
let module = Module::new(
10
&engine,
11
r#"(module
12
(memory $m (export "m") 0)
13
(table (export "t") 0 funcref)
14
(func (export "grow") (param i32) (result i32)
15
(memory.grow $m (local.get 0)))
16
)"#,
17
)?;
18
19
let mut store = Store::new(
20
&engine,
21
StoreLimitsBuilder::new()
22
.memory_size(10 * WASM_PAGE_SIZE)
23
.table_elements(5)
24
.build(),
25
);
26
store.limiter(|s| s as &mut dyn ResourceLimiter);
27
28
let instance = Instance::new(&mut store, &module, &[])?;
29
30
// Test instance exports and host objects hitting the limit
31
for memory in IntoIterator::into_iter([
32
instance.get_memory(&mut store, "m").unwrap(),
33
Memory::new(&mut store, MemoryType::new(0, None))?,
34
]) {
35
memory.grow(&mut store, 3)?;
36
memory.grow(&mut store, 5)?;
37
memory.grow(&mut store, 2)?;
38
39
assert_eq!(
40
memory
41
.grow(&mut store, 1)
42
.map_err(|e| e.to_string())
43
.unwrap_err(),
44
"failed to grow memory by `1`"
45
);
46
}
47
48
// Test instance exports and host objects hitting the limit
49
for table in IntoIterator::into_iter([
50
instance.get_table(&mut store, "t").unwrap(),
51
Table::new(
52
&mut store,
53
TableType::new(RefType::FUNCREF, 0, None),
54
Ref::Func(None),
55
)?,
56
]) {
57
table.grow(&mut store, 2, Ref::Func(None))?;
58
table.grow(&mut store, 1, Ref::Func(None))?;
59
table.grow(&mut store, 2, Ref::Func(None))?;
60
61
assert_eq!(
62
table
63
.grow(&mut store, 1, Ref::Func(None))
64
.map_err(|e| e.to_string())
65
.unwrap_err(),
66
"failed to grow table by `1`"
67
);
68
}
69
70
// Make a new store and instance to test memory grow through wasm
71
let mut store = Store::new(
72
&engine,
73
StoreLimitsBuilder::new()
74
.memory_size(10 * WASM_PAGE_SIZE)
75
.table_elements(5)
76
.build(),
77
);
78
store.limiter(|s| s as &mut dyn ResourceLimiter);
79
let instance = Instance::new(&mut store, &module, &[])?;
80
let grow = instance.get_func(&mut store, "grow").unwrap();
81
let grow = grow.typed::<i32, i32>(&store).unwrap();
82
83
grow.call(&mut store, 3).unwrap();
84
grow.call(&mut store, 5).unwrap();
85
grow.call(&mut store, 2).unwrap();
86
87
// Wasm grow failure returns -1.
88
assert_eq!(grow.call(&mut store, 1).unwrap(), -1);
89
90
Ok(())
91
}
92
93
#[tokio::test]
94
#[cfg_attr(miri, ignore)]
95
async fn test_limits_async() -> Result<()> {
96
let engine = Engine::default();
97
let module = Module::new(
98
&engine,
99
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
100
)?;
101
102
struct LimitsAsync {
103
memory_size: usize,
104
table_elements: usize,
105
}
106
#[async_trait::async_trait]
107
impl ResourceLimiterAsync for LimitsAsync {
108
async fn memory_growing(
109
&mut self,
110
_current: usize,
111
desired: usize,
112
_maximum: Option<usize>,
113
) -> Result<bool> {
114
Ok(desired <= self.memory_size)
115
}
116
async fn table_growing(
117
&mut self,
118
_current: usize,
119
desired: usize,
120
_maximum: Option<usize>,
121
) -> Result<bool> {
122
Ok(desired <= self.table_elements)
123
}
124
}
125
126
let mut store = Store::new(
127
&engine,
128
LimitsAsync {
129
memory_size: 10 * WASM_PAGE_SIZE,
130
table_elements: 5,
131
},
132
);
133
134
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
135
136
let instance = Instance::new_async(&mut store, &module, &[]).await?;
137
138
// Test instance exports and host objects hitting the limit
139
for memory in IntoIterator::into_iter([
140
instance.get_memory(&mut store, "m").unwrap(),
141
Memory::new_async(&mut store, MemoryType::new(0, None)).await?,
142
]) {
143
memory.grow_async(&mut store, 3).await?;
144
memory.grow_async(&mut store, 5).await?;
145
memory.grow_async(&mut store, 2).await?;
146
147
assert_eq!(
148
memory
149
.grow_async(&mut store, 1)
150
.await
151
.map_err(|e| e.to_string())
152
.unwrap_err(),
153
"failed to grow memory by `1`"
154
);
155
}
156
157
// Test instance exports and host objects hitting the limit
158
for table in IntoIterator::into_iter([
159
instance.get_table(&mut store, "t").unwrap(),
160
Table::new_async(
161
&mut store,
162
TableType::new(RefType::FUNCREF, 0, None),
163
Ref::Func(None),
164
)
165
.await?,
166
]) {
167
table.grow_async(&mut store, 2, Ref::Func(None)).await?;
168
table.grow_async(&mut store, 1, Ref::Func(None)).await?;
169
table.grow_async(&mut store, 2, Ref::Func(None)).await?;
170
171
assert_eq!(
172
table
173
.grow_async(&mut store, 1, Ref::Func(None))
174
.await
175
.map_err(|e| e.to_string())
176
.unwrap_err(),
177
"failed to grow table by `1`"
178
);
179
}
180
181
Ok(())
182
}
183
184
#[test]
185
fn test_limits_memory_only() -> Result<()> {
186
let engine = Engine::default();
187
let module = Module::new(
188
&engine,
189
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
190
)?;
191
192
let mut store = Store::new(
193
&engine,
194
StoreLimitsBuilder::new()
195
.memory_size(10 * WASM_PAGE_SIZE)
196
.build(),
197
);
198
store.limiter(|s| s as &mut dyn ResourceLimiter);
199
200
let instance = Instance::new(&mut store, &module, &[])?;
201
202
// Test instance exports and host objects hitting the limit
203
for memory in IntoIterator::into_iter([
204
instance.get_memory(&mut store, "m").unwrap(),
205
Memory::new(&mut store, MemoryType::new(0, None))?,
206
]) {
207
memory.grow(&mut store, 3)?;
208
memory.grow(&mut store, 5)?;
209
memory.grow(&mut store, 2)?;
210
211
assert_eq!(
212
memory
213
.grow(&mut store, 1)
214
.map_err(|e| e.to_string())
215
.unwrap_err(),
216
"failed to grow memory by `1`"
217
);
218
}
219
220
// Test instance exports and host objects *not* hitting the limit
221
for table in IntoIterator::into_iter([
222
instance.get_table(&mut store, "t").unwrap(),
223
Table::new(
224
&mut store,
225
TableType::new(RefType::FUNCREF, 0, None),
226
Ref::Func(None),
227
)?,
228
]) {
229
table.grow(&mut store, 2, Ref::Func(None))?;
230
table.grow(&mut store, 1, Ref::Func(None))?;
231
table.grow(&mut store, 2, Ref::Func(None))?;
232
table.grow(&mut store, 1, Ref::Func(None))?;
233
}
234
235
Ok(())
236
}
237
238
#[test]
239
fn test_initial_memory_limits_exceeded() -> Result<()> {
240
let engine = Engine::default();
241
let module = Module::new(&engine, r#"(module (memory (export "m") 11))"#)?;
242
243
let mut store = Store::new(
244
&engine,
245
StoreLimitsBuilder::new()
246
.memory_size(10 * WASM_PAGE_SIZE)
247
.build(),
248
);
249
store.limiter(|s| s as &mut dyn ResourceLimiter);
250
251
match Instance::new(&mut store, &module, &[]) {
252
Ok(_) => unreachable!(),
253
Err(e) => assert_eq!(
254
e.to_string(),
255
"memory minimum size of 11 pages exceeds memory limits"
256
),
257
}
258
259
match Memory::new(&mut store, MemoryType::new(25, None)) {
260
Ok(_) => unreachable!(),
261
Err(e) => assert_eq!(
262
e.to_string(),
263
"memory minimum size of 25 pages exceeds memory limits"
264
),
265
}
266
267
Ok(())
268
}
269
270
#[test]
271
fn test_limits_table_only() -> Result<()> {
272
let engine = Engine::default();
273
let module = Module::new(
274
&engine,
275
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
276
)?;
277
278
let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(5).build());
279
store.limiter(|s| s as &mut dyn ResourceLimiter);
280
281
let instance = Instance::new(&mut store, &module, &[])?;
282
283
// Test instance exports and host objects *not* hitting the limit
284
for memory in IntoIterator::into_iter([
285
instance.get_memory(&mut store, "m").unwrap(),
286
Memory::new(&mut store, MemoryType::new(0, None))?,
287
]) {
288
memory.grow(&mut store, 3)?;
289
memory.grow(&mut store, 5)?;
290
memory.grow(&mut store, 2)?;
291
memory.grow(&mut store, 1)?;
292
}
293
294
// Test instance exports and host objects hitting the limit
295
for table in IntoIterator::into_iter([
296
instance.get_table(&mut store, "t").unwrap(),
297
Table::new(
298
&mut store,
299
TableType::new(RefType::FUNCREF, 0, None),
300
Ref::Func(None),
301
)?,
302
]) {
303
table.grow(&mut store, 2, Ref::Func(None))?;
304
table.grow(&mut store, 1, Ref::Func(None))?;
305
table.grow(&mut store, 2, Ref::Func(None))?;
306
307
assert_eq!(
308
table
309
.grow(&mut store, 1, Ref::Func(None))
310
.map_err(|e| e.to_string())
311
.unwrap_err(),
312
"failed to grow table by `1`"
313
);
314
}
315
316
Ok(())
317
}
318
319
#[test]
320
fn test_initial_table_limits_exceeded() -> Result<()> {
321
let engine = Engine::default();
322
let module = Module::new(&engine, r#"(module (table (export "t") 23 funcref))"#)?;
323
324
let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(4).build());
325
store.limiter(|s| s as &mut dyn ResourceLimiter);
326
327
match Instance::new(&mut store, &module, &[]) {
328
Ok(_) => unreachable!(),
329
Err(e) => assert_eq!(
330
e.to_string(),
331
"table minimum size of 23 elements exceeds table limits"
332
),
333
}
334
335
match Table::new(
336
&mut store,
337
TableType::new(RefType::FUNCREF, 99, None),
338
Ref::Func(None),
339
) {
340
Ok(_) => unreachable!(),
341
Err(e) => assert_eq!(
342
e.to_string(),
343
"table minimum size of 99 elements exceeds table limits"
344
),
345
}
346
347
Ok(())
348
}
349
350
#[test]
351
fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> {
352
let mut pool = crate::small_pool_config();
353
pool.total_memories(2)
354
.max_memories_per_module(2)
355
.max_memory_size(5 << 16)
356
.memory_protection_keys(Enabled::No);
357
let mut config = Config::new();
358
config.wasm_multi_memory(true);
359
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
360
361
let engine = Engine::new(&config)?;
362
let module = Module::new(
363
&engine,
364
r#"(module (memory (export "m1") 2) (memory (export "m2") 5))"#,
365
)?;
366
367
let mut store = Store::new(
368
&engine,
369
StoreLimitsBuilder::new()
370
.memory_size(3 * WASM_PAGE_SIZE)
371
.build(),
372
);
373
store.limiter(|s| s as &mut dyn ResourceLimiter);
374
375
match Instance::new(&mut store, &module, &[]) {
376
Ok(_) => unreachable!(),
377
Err(e) => assert_eq!(
378
e.to_string(),
379
"memory minimum size of 5 pages exceeds memory limits"
380
),
381
}
382
383
// An instance should still be able to be created after the failure above
384
let module = Module::new(&engine, r#"(module (memory (export "m") 2))"#)?;
385
386
Instance::new(&mut store, &module, &[])?;
387
388
Ok(())
389
}
390
391
struct MemoryContext {
392
host_memory_used: usize,
393
wasm_memory_used: usize,
394
memory_limit: usize,
395
limit_exceeded: bool,
396
}
397
398
impl ResourceLimiter for MemoryContext {
399
fn memory_growing(
400
&mut self,
401
current: usize,
402
desired: usize,
403
maximum: Option<usize>,
404
) -> Result<bool> {
405
// Check if the desired exceeds a maximum (either from Wasm or from the host)
406
assert!(desired < maximum.unwrap_or(usize::MAX));
407
408
assert_eq!(current, self.wasm_memory_used);
409
410
if desired + self.host_memory_used > self.memory_limit {
411
self.limit_exceeded = true;
412
return Ok(false);
413
}
414
415
self.wasm_memory_used = desired;
416
Ok(true)
417
}
418
fn table_growing(
419
&mut self,
420
_current: usize,
421
_desired: usize,
422
_maximum: Option<usize>,
423
) -> Result<bool> {
424
Ok(true)
425
}
426
}
427
428
#[test]
429
#[cfg_attr(miri, ignore)]
430
fn test_custom_memory_limiter() -> Result<()> {
431
let engine = Engine::default();
432
let mut linker = Linker::new(&engine);
433
434
// This approximates a function that would "allocate" resources that the host tracks.
435
// Here this is a simple function that increments the current host memory "used".
436
linker.func_wrap(
437
"",
438
"alloc",
439
|mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 {
440
let ctx = caller.data_mut();
441
let size = size as usize;
442
443
if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit {
444
ctx.host_memory_used += size;
445
return 1;
446
}
447
448
ctx.limit_exceeded = true;
449
450
0
451
},
452
)?;
453
454
let module = Module::new(
455
&engine,
456
r#"(module (import "" "alloc" (func $alloc (param i32) (result i32))) (memory (export "m") 0) (func (export "f") (param i32) (result i32) local.get 0 call $alloc))"#,
457
)?;
458
459
let context = MemoryContext {
460
host_memory_used: 0,
461
wasm_memory_used: 0,
462
memory_limit: 1 << 20, // 16 wasm pages is the limit for both wasm + host memory
463
limit_exceeded: false,
464
};
465
466
let mut store = Store::new(&engine, context);
467
store.limiter(|s| s as &mut dyn ResourceLimiter);
468
let instance = linker.instantiate(&mut store, &module)?;
469
let memory = instance.get_memory(&mut store, "m").unwrap();
470
471
// Grow the memory by 640 KiB
472
memory.grow(&mut store, 3)?;
473
memory.grow(&mut store, 5)?;
474
memory.grow(&mut store, 2)?;
475
476
assert!(!store.data().limit_exceeded);
477
478
// Grow the host "memory" by 384 KiB
479
let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
480
481
assert_eq!(f.call(&mut store, 1 * 0x10000)?, 1);
482
assert_eq!(f.call(&mut store, 3 * 0x10000)?, 1);
483
assert_eq!(f.call(&mut store, 2 * 0x10000)?, 1);
484
485
// Memory is at the maximum, but the limit hasn't been exceeded
486
assert!(!store.data().limit_exceeded);
487
488
// Try to grow the memory again
489
assert_eq!(
490
memory
491
.grow(&mut store, 1)
492
.map_err(|e| e.to_string())
493
.unwrap_err(),
494
"failed to grow memory by `1`"
495
);
496
497
assert!(store.data().limit_exceeded);
498
499
// Try to grow the host "memory" again
500
assert_eq!(f.call(&mut store, 1)?, 0);
501
502
assert!(store.data().limit_exceeded);
503
504
drop(store);
505
506
Ok(())
507
}
508
509
#[async_trait::async_trait]
510
impl ResourceLimiterAsync for MemoryContext {
511
async fn memory_growing(
512
&mut self,
513
current: usize,
514
desired: usize,
515
maximum: Option<usize>,
516
) -> Result<bool> {
517
// Show we can await in this async context:
518
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
519
// Check if the desired exceeds a maximum (either from Wasm or from the host)
520
assert!(desired < maximum.unwrap_or(usize::MAX));
521
522
assert_eq!(current, self.wasm_memory_used);
523
524
if desired + self.host_memory_used > self.memory_limit {
525
self.limit_exceeded = true;
526
return Ok(false);
527
}
528
529
self.wasm_memory_used = desired;
530
Ok(true)
531
}
532
async fn table_growing(
533
&mut self,
534
_current: usize,
535
_desired: usize,
536
_maximum: Option<usize>,
537
) -> Result<bool> {
538
Ok(true)
539
}
540
}
541
542
#[tokio::test]
543
#[cfg_attr(miri, ignore)]
544
async fn test_custom_memory_limiter_async() -> Result<()> {
545
let engine = Engine::default();
546
let mut linker = Linker::new(&engine);
547
548
// This approximates a function that would "allocate" resources that the host tracks.
549
// Here this is a simple function that increments the current host memory "used".
550
linker.func_wrap(
551
"",
552
"alloc",
553
|mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 {
554
let ctx = caller.data_mut();
555
let size = size as usize;
556
557
if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit {
558
ctx.host_memory_used += size;
559
return 1;
560
}
561
562
ctx.limit_exceeded = true;
563
564
0
565
},
566
)?;
567
568
let module = Module::new(
569
&engine,
570
r#"(module (import "" "alloc" (func $alloc (param i32) (result i32))) (memory (export "m") 0) (func (export "f") (param i32) (result i32) local.get 0 call $alloc))"#,
571
)?;
572
573
let context = MemoryContext {
574
host_memory_used: 0,
575
wasm_memory_used: 0,
576
memory_limit: 1 << 20, // 16 wasm pages is the limit for both wasm + host memory
577
limit_exceeded: false,
578
};
579
580
let mut store = Store::new(&engine, context);
581
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
582
let instance = linker.instantiate_async(&mut store, &module).await?;
583
let memory = instance.get_memory(&mut store, "m").unwrap();
584
585
// Grow the memory by 640 KiB
586
memory.grow_async(&mut store, 3).await?;
587
memory.grow_async(&mut store, 5).await?;
588
memory.grow_async(&mut store, 2).await?;
589
590
assert!(!store.data().limit_exceeded);
591
592
// Grow the host "memory" by 384 KiB
593
let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
594
595
assert_eq!(f.call_async(&mut store, 1 * 0x10000).await?, 1);
596
assert_eq!(f.call_async(&mut store, 3 * 0x10000).await?, 1);
597
assert_eq!(f.call_async(&mut store, 2 * 0x10000).await?, 1);
598
599
// Memory is at the maximum, but the limit hasn't been exceeded
600
assert!(!store.data().limit_exceeded);
601
602
// Try to grow the memory again
603
assert_eq!(
604
memory
605
.grow_async(&mut store, 1)
606
.await
607
.map_err(|e| e.to_string())
608
.unwrap_err(),
609
"failed to grow memory by `1`"
610
);
611
612
assert!(store.data().limit_exceeded);
613
614
// Try to grow the host "memory" again
615
assert_eq!(f.call_async(&mut store, 1).await?, 0);
616
617
assert!(store.data().limit_exceeded);
618
619
drop(store);
620
621
Ok(())
622
}
623
624
struct TableContext {
625
elements_used: usize,
626
element_limit: usize,
627
limit_exceeded: bool,
628
}
629
630
impl ResourceLimiter for TableContext {
631
fn memory_growing(
632
&mut self,
633
_current: usize,
634
_desired: usize,
635
_maximum: Option<usize>,
636
) -> Result<bool> {
637
Ok(true)
638
}
639
fn table_growing(
640
&mut self,
641
current: usize,
642
desired: usize,
643
maximum: Option<usize>,
644
) -> Result<bool> {
645
// Check if the desired exceeds a maximum (either from Wasm or from the host)
646
assert!(desired < maximum.unwrap_or(usize::MAX));
647
assert_eq!(current, self.elements_used);
648
Ok(if desired > self.element_limit {
649
self.limit_exceeded = true;
650
false
651
} else {
652
self.elements_used = desired;
653
true
654
})
655
}
656
}
657
658
#[test]
659
fn test_custom_table_limiter() -> Result<()> {
660
let engine = Engine::default();
661
let linker = Linker::new(&engine);
662
663
let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#)?;
664
665
let context = TableContext {
666
elements_used: 0,
667
element_limit: 10,
668
limit_exceeded: false,
669
};
670
671
let mut store = Store::new(&engine, context);
672
store.limiter(|s| s as &mut dyn ResourceLimiter);
673
let instance = linker.instantiate(&mut store, &module)?;
674
let table = instance.get_table(&mut store, "t").unwrap();
675
676
// Grow the table by 10 elements
677
table.grow(&mut store, 3, Ref::Func(None))?;
678
table.grow(&mut store, 5, Ref::Func(None))?;
679
table.grow(&mut store, 2, Ref::Func(None))?;
680
681
assert!(!store.data().limit_exceeded);
682
683
// Table is at the maximum, but the limit hasn't been exceeded
684
assert!(!store.data().limit_exceeded);
685
686
// Try to grow the memory again
687
assert_eq!(
688
table
689
.grow(&mut store, 1, Ref::Func(None))
690
.map_err(|e| e.to_string())
691
.unwrap_err(),
692
"failed to grow table by `1`"
693
);
694
695
assert!(store.data().limit_exceeded);
696
697
Ok(())
698
}
699
700
#[derive(Default)]
701
struct FailureDetector {
702
/// Arguments of most recent call to memory_growing
703
memory_current: usize,
704
memory_desired: usize,
705
/// Display impl of most recent call to memory_grow_failed
706
memory_error: Option<String>,
707
/// Arguments of most recent call to table_growing
708
table_current: usize,
709
table_desired: usize,
710
/// Display impl of most recent call to table_grow_failed
711
table_error: Option<String>,
712
}
713
714
impl ResourceLimiter for FailureDetector {
715
fn memory_growing(
716
&mut self,
717
current: usize,
718
desired: usize,
719
_maximum: Option<usize>,
720
) -> Result<bool> {
721
self.memory_current = current;
722
self.memory_desired = desired;
723
Ok(true)
724
}
725
fn memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
726
self.memory_error = Some(err.to_string());
727
Ok(())
728
}
729
fn table_growing(
730
&mut self,
731
current: usize,
732
desired: usize,
733
_maximum: Option<usize>,
734
) -> Result<bool> {
735
self.table_current = current;
736
self.table_desired = desired;
737
Ok(true)
738
}
739
fn table_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
740
self.table_error = Some(err.to_string());
741
Ok(())
742
}
743
}
744
745
#[test]
746
fn custom_limiter_detect_grow_failure() -> Result<()> {
747
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
748
return Ok(());
749
}
750
let mut pool = crate::small_pool_config();
751
pool.max_memory_size(10 << 16).table_elements(10);
752
let mut config = Config::new();
753
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
754
let engine = Engine::new(&config).unwrap();
755
let linker = Linker::new(&engine);
756
757
let module = Module::new(
758
&engine,
759
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
760
)?;
761
762
let context = FailureDetector::default();
763
764
let mut store = Store::new(&engine, context);
765
store.limiter(|s| s as &mut dyn ResourceLimiter);
766
let instance = linker.instantiate(&mut store, &module)?;
767
let memory = instance.get_memory(&mut store, "m").unwrap();
768
769
// Grow the memory by 640 KiB (10 pages)
770
memory.grow(&mut store, 10)?;
771
772
assert!(store.data().memory_error.is_none());
773
assert_eq!(store.data().memory_current, 0);
774
assert_eq!(store.data().memory_desired, 10 * 64 * 1024);
775
776
// Grow past the static limit set by ModuleLimits.
777
// The ResourceLimiter will permit this, but the grow will fail.
778
assert_eq!(
779
memory.grow(&mut store, 1).unwrap_err().to_string(),
780
"failed to grow memory by `1`"
781
);
782
783
assert_eq!(store.data().memory_current, 10 * 64 * 1024);
784
assert_eq!(store.data().memory_desired, 11 * 64 * 1024);
785
assert_eq!(
786
store.data().memory_error.as_ref().unwrap(),
787
"Memory maximum size exceeded"
788
);
789
790
let table = instance.get_table(&mut store, "t").unwrap();
791
// Grow the table 10 elements
792
table.grow(&mut store, 10, Ref::Func(None))?;
793
794
assert!(store.data().table_error.is_none());
795
assert_eq!(store.data().table_current, 0);
796
assert_eq!(store.data().table_desired, 10);
797
798
// Grow past the static limit set by ModuleLimits.
799
// The ResourceLimiter will permit this, but the grow will fail.
800
assert_eq!(
801
table
802
.grow(&mut store, 1, Ref::Func(None))
803
.unwrap_err()
804
.to_string(),
805
"failed to grow table by `1`"
806
);
807
808
assert_eq!(store.data().table_current, 10);
809
assert_eq!(store.data().table_desired, 11);
810
assert_eq!(
811
store.data().table_error.as_ref().unwrap(),
812
"Table maximum size exceeded"
813
);
814
815
drop(store);
816
817
Ok(())
818
}
819
820
#[async_trait::async_trait]
821
impl ResourceLimiterAsync for FailureDetector {
822
async fn memory_growing(
823
&mut self,
824
current: usize,
825
desired: usize,
826
_maximum: Option<usize>,
827
) -> Result<bool> {
828
// Show we can await in this async context:
829
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
830
self.memory_current = current;
831
self.memory_desired = desired;
832
Ok(true)
833
}
834
fn memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
835
self.memory_error = Some(err.to_string());
836
Ok(())
837
}
838
839
async fn table_growing(
840
&mut self,
841
current: usize,
842
desired: usize,
843
_maximum: Option<usize>,
844
) -> Result<bool> {
845
self.table_current = current;
846
self.table_desired = desired;
847
Ok(true)
848
}
849
fn table_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
850
self.table_error = Some(err.to_string());
851
Ok(())
852
}
853
}
854
855
#[tokio::test]
856
#[cfg_attr(miri, ignore)]
857
async fn custom_limiter_async_detect_grow_failure() -> Result<()> {
858
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
859
return Ok(());
860
}
861
let mut pool = crate::small_pool_config();
862
pool.max_memory_size(10 << 16).table_elements(10);
863
let mut config = Config::new();
864
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
865
let engine = Engine::new(&config).unwrap();
866
let linker = Linker::<FailureDetector>::new(&engine);
867
868
let module = Module::new(
869
&engine,
870
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
871
)?;
872
873
let context = FailureDetector::default();
874
875
let mut store = Store::new(&engine, context);
876
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
877
let instance = linker.instantiate_async(&mut store, &module).await?;
878
let memory = instance.get_memory(&mut store, "m").unwrap();
879
880
// Grow the memory by 640 KiB (10 pages)
881
memory.grow_async(&mut store, 10).await?;
882
883
assert!(store.data().memory_error.is_none());
884
assert_eq!(store.data().memory_current, 0);
885
assert_eq!(store.data().memory_desired, 10 * 64 * 1024);
886
887
// Grow past the static limit set by ModuleLimits.
888
// The ResourcLimiterAsync will permit this, but the grow will fail.
889
assert_eq!(
890
memory
891
.grow_async(&mut store, 1)
892
.await
893
.unwrap_err()
894
.to_string(),
895
"failed to grow memory by `1`"
896
);
897
898
assert_eq!(store.data().memory_current, 10 * 64 * 1024);
899
assert_eq!(store.data().memory_desired, 11 * 64 * 1024);
900
assert_eq!(
901
store.data().memory_error.as_ref().unwrap(),
902
"Memory maximum size exceeded"
903
);
904
905
let table = instance.get_table(&mut store, "t").unwrap();
906
// Grow the table 10 elements
907
table.grow_async(&mut store, 10, Ref::Func(None)).await?;
908
909
assert!(store.data().table_error.is_none());
910
assert_eq!(store.data().table_current, 0);
911
assert_eq!(store.data().table_desired, 10);
912
913
// Grow past the static limit set by ModuleLimits.
914
// The ResourceLimiter will permit this, but the grow will fail.
915
assert_eq!(
916
table
917
.grow_async(&mut store, 1, Ref::Func(None))
918
.await
919
.unwrap_err()
920
.to_string(),
921
"failed to grow table by `1`"
922
);
923
924
assert_eq!(store.data().table_current, 10);
925
assert_eq!(store.data().table_desired, 11);
926
assert_eq!(
927
store.data().table_error.as_ref().unwrap(),
928
"Table maximum size exceeded"
929
);
930
931
drop(store);
932
933
Ok(())
934
}
935
936
struct Panic;
937
938
impl ResourceLimiter for Panic {
939
fn memory_growing(
940
&mut self,
941
_current: usize,
942
_desired: usize,
943
_maximum: Option<usize>,
944
) -> Result<bool> {
945
panic!("resource limiter memory growing");
946
}
947
fn table_growing(
948
&mut self,
949
_current: usize,
950
_desired: usize,
951
_maximum: Option<usize>,
952
) -> Result<bool> {
953
panic!("resource limiter table growing");
954
}
955
}
956
#[async_trait::async_trait]
957
impl ResourceLimiterAsync for Panic {
958
async fn memory_growing(
959
&mut self,
960
_current: usize,
961
_desired: usize,
962
_maximum: Option<usize>,
963
) -> Result<bool> {
964
panic!("async resource limiter memory growing");
965
}
966
async fn table_growing(
967
&mut self,
968
_current: usize,
969
_desired: usize,
970
_maximum: Option<usize>,
971
) -> Result<bool> {
972
panic!("async resource limiter table growing");
973
}
974
}
975
976
#[test]
977
#[should_panic(expected = "resource limiter memory growing")]
978
fn panic_in_memory_limiter() {
979
let engine = Engine::default();
980
let linker = Linker::new(&engine);
981
982
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
983
984
let mut store = Store::new(&engine, Panic);
985
store.limiter(|s| s as &mut dyn ResourceLimiter);
986
let instance = linker.instantiate(&mut store, &module).unwrap();
987
let memory = instance.get_memory(&mut store, "m").unwrap();
988
989
// Grow the memory, which should panic
990
memory.grow(&mut store, 3).unwrap();
991
}
992
993
#[test]
994
#[should_panic(expected = "resource limiter memory growing")]
995
#[cfg_attr(miri, ignore)]
996
fn panic_in_memory_limiter_wasm_stack() {
997
// Like the test above, except the memory.grow happens in wasm code
998
// instead of a host function call.
999
let engine = Engine::default();
1000
let linker = Linker::new(&engine);
1001
1002
let module = Module::new(
1003
&engine,
1004
r#"
1005
(module
1006
(memory $m (export "m") 0)
1007
(func (export "grow") (param i32) (result i32)
1008
(memory.grow $m (local.get 0)))
1009
)"#,
1010
)
1011
.unwrap();
1012
1013
let mut store = Store::new(&engine, Panic);
1014
store.limiter(|s| s as &mut dyn ResourceLimiter);
1015
let instance = linker.instantiate(&mut store, &module).unwrap();
1016
let grow = instance.get_func(&mut store, "grow").unwrap();
1017
let grow = grow.typed::<i32, i32>(&store).unwrap();
1018
1019
// Grow the memory, which should panic
1020
grow.call(&mut store, 3).unwrap();
1021
}
1022
1023
#[test]
1024
#[should_panic(expected = "resource limiter table growing")]
1025
fn panic_in_table_limiter() {
1026
let engine = Engine::default();
1027
let linker = Linker::new(&engine);
1028
1029
let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#).unwrap();
1030
1031
let mut store = Store::new(&engine, Panic);
1032
store.limiter(|s| s as &mut dyn ResourceLimiter);
1033
let instance = linker.instantiate(&mut store, &module).unwrap();
1034
let table = instance.get_table(&mut store, "t").unwrap();
1035
1036
// Grow the table, which should panic
1037
table.grow(&mut store, 3, Ref::Func(None)).unwrap();
1038
}
1039
1040
#[tokio::test]
1041
#[should_panic(expected = "async resource limiter memory growing")]
1042
#[cfg_attr(miri, ignore)]
1043
async fn panic_in_async_memory_limiter() {
1044
let engine = Engine::default();
1045
let linker = Linker::<Panic>::new(&engine);
1046
1047
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
1048
1049
let mut store = Store::new(&engine, Panic);
1050
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
1051
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
1052
let memory = instance.get_memory(&mut store, "m").unwrap();
1053
1054
// Grow the memory, which should panic
1055
memory.grow_async(&mut store, 3).await.unwrap();
1056
}
1057
1058
#[tokio::test]
1059
#[should_panic(expected = "async resource limiter memory growing")]
1060
#[cfg_attr(miri, ignore)]
1061
async fn panic_in_async_memory_limiter_wasm_stack() {
1062
// Like the test above, except the memory.grow happens in
1063
// wasm code instead of a host function call.
1064
let engine = Engine::default();
1065
let linker = Linker::<Panic>::new(&engine);
1066
1067
let module = Module::new(
1068
&engine,
1069
r#"
1070
(module
1071
(memory $m (export "m") 0)
1072
(func (export "grow") (param i32) (result i32)
1073
(memory.grow $m (local.get 0)))
1074
)"#,
1075
)
1076
.unwrap();
1077
1078
let mut store = Store::new(&engine, Panic);
1079
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
1080
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
1081
let grow = instance.get_func(&mut store, "grow").unwrap();
1082
let grow = grow.typed::<i32, i32>(&store).unwrap();
1083
1084
// Grow the memory, which should panic
1085
grow.call_async(&mut store, 3).await.unwrap();
1086
}
1087
1088
#[tokio::test]
1089
#[should_panic(expected = "async resource limiter table growing")]
1090
#[cfg_attr(miri, ignore)]
1091
async fn panic_in_async_table_limiter() {
1092
let engine = Engine::default();
1093
let linker = Linker::<Panic>::new(&engine);
1094
1095
let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#).unwrap();
1096
1097
let mut store = Store::new(&engine, Panic);
1098
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
1099
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
1100
let table = instance.get_table(&mut store, "t").unwrap();
1101
1102
// Grow the table, which should panic
1103
table
1104
.grow_async(&mut store, 3, Ref::Func(None))
1105
.await
1106
.unwrap();
1107
}
1108
1109
#[test]
1110
#[cfg_attr(miri, ignore)]
1111
fn growth_trap() -> Result<()> {
1112
let engine = Engine::default();
1113
let module = Module::new(
1114
&engine,
1115
r#"(module
1116
(memory $m (export "m") 0)
1117
(table (export "t") 0 funcref)
1118
(func (export "grow") (param i32) (result i32)
1119
(memory.grow $m (local.get 0)))
1120
)"#,
1121
)?;
1122
1123
let mut store = Store::new(
1124
&engine,
1125
StoreLimitsBuilder::new()
1126
.memory_size(WASM_PAGE_SIZE)
1127
.table_elements(1)
1128
.trap_on_grow_failure(true)
1129
.build(),
1130
);
1131
store.limiter(|s| s as &mut dyn ResourceLimiter);
1132
1133
let instance = Instance::new(&mut store, &module, &[])?;
1134
1135
// Test instance exports and host objects hitting the limit
1136
for memory in [
1137
instance.get_memory(&mut store, "m").unwrap(),
1138
Memory::new(&mut store, MemoryType::new(0, None))?,
1139
] {
1140
memory.grow(&mut store, 1)?;
1141
assert!(memory.grow(&mut store, 1).is_err());
1142
}
1143
1144
// Test instance exports and host objects hitting the limit
1145
for table in [
1146
instance.get_table(&mut store, "t").unwrap(),
1147
Table::new(
1148
&mut store,
1149
TableType::new(RefType::FUNCREF, 0, None),
1150
Ref::Func(None),
1151
)?,
1152
] {
1153
table.grow(&mut store, 1, Ref::Func(None))?;
1154
assert!(table.grow(&mut store, 1, Ref::Func(None)).is_err());
1155
}
1156
1157
let mut store = Store::new(&engine, store.data().clone());
1158
store.limiter(|s| s as &mut dyn ResourceLimiter);
1159
let instance = Instance::new(&mut store, &module, &[])?;
1160
let grow = instance.get_func(&mut store, "grow").unwrap();
1161
let grow = grow.typed::<i32, i32>(&store).unwrap();
1162
grow.call(&mut store, 1)?;
1163
assert!(grow.call(&mut store, 1).is_err());
1164
1165
Ok(())
1166
}
1167
1168