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)]
test_limits() -> Result<()>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)]
test_limits_async() -> Result<()>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]
test_limits_memory_only() -> Result<()>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]
test_initial_memory_limits_exceeded() -> Result<()>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]
test_limits_table_only() -> Result<()>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]
test_initial_table_limits_exceeded() -> Result<()>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]
test_pooling_allocator_initial_limits_exceeded() -> Result<()>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 {
memory_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>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 }
table_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>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)]
test_custom_memory_limiter() -> Result<()>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 {
memory_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>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 }
table_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>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)]
test_custom_memory_limiter_async() -> Result<()>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 {
memory_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>631 fn memory_growing(
632 &mut self,
633 _current: usize,
634 _desired: usize,
635 _maximum: Option<usize>,
636 ) -> Result<bool> {
637 Ok(true)
638 }
table_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>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]
test_custom_table_limiter() -> Result<()>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 {
memory_growing( &mut self, current: usize, desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 }
memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()>725 fn memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
726 self.memory_error = Some(err.to_string());
727 Ok(())
728 }
table_growing( &mut self, current: usize, desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 }
table_grow_failed(&mut self, err: wasmtime::Error) -> Result<()>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]
custom_limiter_detect_grow_failure() -> Result<()>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 {
memory_growing( &mut self, current: usize, desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 }
memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()>834 fn memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
835 self.memory_error = Some(err.to_string());
836 Ok(())
837 }
838
table_growing( &mut self, current: usize, desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 }
table_grow_failed(&mut self, err: wasmtime::Error) -> Result<()>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)]
custom_limiter_async_detect_grow_failure() -> Result<()>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 {
memory_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 }
table_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 {
memory_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>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 }
table_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>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")]
panic_in_memory_limiter()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)]
panic_in_memory_limiter_wasm_stack()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")]
panic_in_table_limiter()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)]
panic_in_async_memory_limiter()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)]
panic_in_async_memory_limiter_wasm_stack()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)]
panic_in_async_table_limiter()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)]
growth_trap() -> Result<()>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