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