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