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