1 use super::{ErrorExt, skip_pooling_allocator_tests}; 2 use wasmtime::*; 3 4 #[test] 5 fn successful_instantiation() -> Result<()> { 6 let pool = crate::small_pool_config(); 7 let mut config = Config::new(); 8 config.allocation_strategy(pool); 9 config.memory_guard_size(0); 10 config.memory_reservation(1 << 16); 11 12 let engine = Engine::new(&config)?; 13 let module = Module::new(&engine, r#"(module (memory 1) (table 10 funcref))"#)?; 14 15 // Module should instantiate 16 let mut store = Store::new(&engine, ()); 17 Instance::new(&mut store, &module, &[])?; 18 19 Ok(()) 20 } 21 22 #[test] 23 #[cfg_attr(miri, ignore)] 24 fn memory_limit() -> Result<()> { 25 let mut pool = crate::small_pool_config(); 26 pool.max_memory_size(3 << 16); 27 let mut config = Config::new(); 28 config.allocation_strategy(pool); 29 config.memory_guard_size(1 << 16); 30 config.memory_reservation(3 << 16); 31 config.wasm_multi_memory(true); 32 33 let engine = Engine::new(&config)?; 34 35 // Module should fail to instantiate because it has too many memories 36 match Module::new(&engine, r#"(module (memory 1) (memory 1))"#) { 37 Ok(_) => panic!("module instantiation should fail"), 38 Err(e) => { 39 e.assert_contains("defined memories count of 2 exceeds the per-instance limit of 1") 40 } 41 } 42 43 // Module should fail to instantiate because the minimum is greater than 44 // the configured limit 45 match Module::new(&engine, r#"(module (memory 4))"#) { 46 Ok(_) => panic!("module instantiation should fail"), 47 Err(e) => { 48 e.assert_contains( 49 "memory index 0 is unsupported in this pooling allocator \ 50 configuration", 51 ); 52 e.assert_contains( 53 "memory has a minimum byte size of 262144 which exceeds \ 54 the limit of 0x30000 bytes", 55 ); 56 } 57 } 58 59 let module = Module::new( 60 &engine, 61 r#"(module (memory (export "m") 0) (func (export "f") (result i32) (memory.grow (i32.const 1))))"#, 62 )?; 63 64 // Instantiate the module and grow the memory via the `f` function 65 { 66 let mut store = Store::new(&engine, ()); 67 let instance = Instance::new(&mut store, &module, &[])?; 68 let f = instance.get_typed_func::<(), i32>(&mut store, "f")?; 69 70 assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 0); 71 assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 1); 72 assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 2); 73 assert_eq!( 74 f.call(&mut store, ()).expect("function should not trap"), 75 -1 76 ); 77 assert_eq!( 78 f.call(&mut store, ()).expect("function should not trap"), 79 -1 80 ); 81 } 82 83 // Instantiate the module and grow the memory via the Wasmtime API 84 let mut store = Store::new(&engine, ()); 85 let instance = Instance::new(&mut store, &module, &[])?; 86 87 let memory = instance.get_memory(&mut store, "m").unwrap(); 88 assert_eq!(memory.size(&store), 0); 89 assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 0); 90 assert_eq!(memory.size(&store), 1); 91 assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 1); 92 assert_eq!(memory.size(&store), 2); 93 assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 2); 94 assert_eq!(memory.size(&store), 3); 95 assert!(memory.grow(&mut store, 1).is_err()); 96 97 Ok(()) 98 } 99 100 #[test] 101 fn memory_init() -> Result<()> { 102 let mut pool = crate::small_pool_config(); 103 pool.max_memory_size(2 << 16).table_elements(0); 104 let mut config = Config::new(); 105 config.allocation_strategy(pool); 106 107 let engine = Engine::new(&config)?; 108 109 let module = Module::new( 110 &engine, 111 r#" 112 (module 113 (memory (export "m") 2) 114 (data (i32.const 65530) "this data spans multiple pages") 115 (data (i32.const 10) "hello world") 116 ) 117 "#, 118 )?; 119 120 let mut store = Store::new(&engine, ()); 121 let instance = Instance::new(&mut store, &module, &[])?; 122 let memory = instance.get_memory(&mut store, "m").unwrap(); 123 124 assert_eq!( 125 &memory.data(&store)[65530..65560], 126 b"this data spans multiple pages" 127 ); 128 assert_eq!(&memory.data(&store)[10..21], b"hello world"); 129 130 Ok(()) 131 } 132 133 #[test] 134 #[cfg_attr(miri, ignore)] 135 fn memory_guard_page_trap() -> Result<()> { 136 let mut pool = crate::small_pool_config(); 137 pool.max_memory_size(2 << 16).table_elements(0); 138 let mut config = Config::new(); 139 config.allocation_strategy(pool); 140 141 let engine = Engine::new(&config)?; 142 143 let module = Module::new( 144 &engine, 145 r#" 146 (module 147 (memory (export "m") 0) 148 (func (export "f") (param i32) local.get 0 i32.load drop) 149 ) 150 "#, 151 )?; 152 153 // Instantiate the module and check for out of bounds trap 154 for _ in 0..10 { 155 let mut store = Store::new(&engine, ()); 156 let instance = Instance::new(&mut store, &module, &[])?; 157 let m = instance.get_memory(&mut store, "m").unwrap(); 158 let f = instance.get_typed_func::<i32, ()>(&mut store, "f")?; 159 160 let trap = f 161 .call(&mut store, 0) 162 .expect_err("function should trap") 163 .downcast::<Trap>()?; 164 assert_eq!(trap, Trap::MemoryOutOfBounds); 165 166 let trap = f 167 .call(&mut store, 1) 168 .expect_err("function should trap") 169 .downcast::<Trap>()?; 170 assert_eq!(trap, Trap::MemoryOutOfBounds); 171 172 m.grow(&mut store, 1).expect("memory should grow"); 173 f.call(&mut store, 0).expect("function should not trap"); 174 175 let trap = f 176 .call(&mut store, 65536) 177 .expect_err("function should trap") 178 .downcast::<Trap>()?; 179 assert_eq!(trap, Trap::MemoryOutOfBounds); 180 181 let trap = f 182 .call(&mut store, 65537) 183 .expect_err("function should trap") 184 .downcast::<Trap>()?; 185 assert_eq!(trap, Trap::MemoryOutOfBounds); 186 187 m.grow(&mut store, 1).expect("memory should grow"); 188 f.call(&mut store, 65536).expect("function should not trap"); 189 190 m.grow(&mut store, 1) 191 .expect_err("memory should be at the limit"); 192 } 193 194 Ok(()) 195 } 196 197 #[test] 198 fn memory_zeroed() -> Result<()> { 199 if skip_pooling_allocator_tests() { 200 return Ok(()); 201 } 202 203 let mut pool = crate::small_pool_config(); 204 pool.max_memory_size(1 << 16).table_elements(0); 205 let mut config = Config::new(); 206 config.allocation_strategy(pool); 207 config.memory_guard_size(0); 208 config.memory_reservation(1 << 16); 209 210 let engine = Engine::new(&config)?; 211 212 let module = Module::new(&engine, r#"(module (memory (export "m") 1))"#)?; 213 214 // Instantiate the module repeatedly after writing data to the entire memory 215 for _ in 0..10 { 216 let mut store = Store::new(&engine, ()); 217 let instance = Instance::new(&mut store, &module, &[])?; 218 let memory = instance.get_memory(&mut store, "m").unwrap(); 219 220 assert_eq!(memory.size(&store,), 1); 221 assert_eq!(memory.data_size(&store), 65536); 222 223 let ptr = memory.data_mut(&mut store).as_mut_ptr(); 224 225 unsafe { 226 for i in 0..8192 { 227 assert_eq!(*ptr.cast::<u64>().offset(i), 0); 228 } 229 std::ptr::write_bytes(ptr, 0xFE, memory.data_size(&store)); 230 } 231 } 232 233 Ok(()) 234 } 235 236 #[test] 237 #[cfg_attr(miri, ignore)] 238 fn table_limit() -> Result<()> { 239 const TABLE_ELEMENTS: usize = 10; 240 let mut pool = crate::small_pool_config(); 241 pool.table_elements(TABLE_ELEMENTS); 242 let mut config = Config::new(); 243 config.allocation_strategy(pool); 244 config.memory_guard_size(0); 245 config.memory_reservation(1 << 16); 246 247 let engine = Engine::new(&config)?; 248 249 // Module should fail to instantiate because it has too many tables 250 match Module::new(&engine, r#"(module (table 1 funcref) (table 1 funcref))"#) { 251 Ok(_) => panic!("module compilation should fail"), 252 Err(e) => { 253 e.assert_contains("defined tables count of 2 exceeds the per-instance limit of 1") 254 } 255 } 256 257 // Module should fail to instantiate because the minimum is greater than 258 // the configured limit 259 match Module::new(&engine, r#"(module (table 31 funcref))"#) { 260 Ok(_) => panic!("module compilation should fail"), 261 Err(e) => e.assert_contains( 262 "table index 0 has a minimum element size of 31 which exceeds the limit of 10", 263 ), 264 } 265 266 let module = Module::new( 267 &engine, 268 r#"(module (table (export "t") 0 funcref) (func (export "f") (result i32) (table.grow (ref.null func) (i32.const 1))))"#, 269 )?; 270 271 // Instantiate the module and grow the table via the `f` function 272 { 273 let mut store = Store::new(&engine, ()); 274 let instance = Instance::new(&mut store, &module, &[])?; 275 let f = instance.get_typed_func::<(), i32>(&mut store, "f")?; 276 277 for i in 0..TABLE_ELEMENTS { 278 assert_eq!( 279 f.call(&mut store, ()).expect("function should not trap"), 280 i as i32 281 ); 282 } 283 284 assert_eq!( 285 f.call(&mut store, ()).expect("function should not trap"), 286 -1 287 ); 288 assert_eq!( 289 f.call(&mut store, ()).expect("function should not trap"), 290 -1 291 ); 292 } 293 294 // Instantiate the module and grow the table via the Wasmtime API 295 let mut store = Store::new(&engine, ()); 296 let instance = Instance::new(&mut store, &module, &[])?; 297 298 let table = instance.get_table(&mut store, "t").unwrap(); 299 300 for i in 0..TABLE_ELEMENTS { 301 assert_eq!(table.size(&store), i as u64); 302 assert_eq!( 303 table 304 .grow(&mut store, 1, Ref::Func(None)) 305 .expect("table should grow"), 306 i as u64 307 ); 308 } 309 310 assert_eq!(table.size(&store), TABLE_ELEMENTS as u64); 311 assert!(table.grow(&mut store, 1, Ref::Func(None)).is_err()); 312 313 Ok(()) 314 } 315 316 #[test] 317 #[cfg_attr(miri, ignore)] 318 fn table_init() -> Result<()> { 319 let mut pool = crate::small_pool_config(); 320 pool.max_memory_size(0).table_elements(6); 321 let mut config = Config::new(); 322 config.allocation_strategy(pool); 323 324 let engine = Engine::new(&config)?; 325 326 let module = Module::new( 327 &engine, 328 r#" 329 (module 330 (table (export "t") 6 funcref) 331 (elem (i32.const 1) 1 2 3 4) 332 (elem (i32.const 0) 0) 333 (func) 334 (func (param i32)) 335 (func (param i32 i32)) 336 (func (param i32 i32 i32)) 337 (func (param i32 i32 i32 i32)) 338 ) 339 "#, 340 )?; 341 342 let mut store = Store::new(&engine, ()); 343 let instance = Instance::new(&mut store, &module, &[])?; 344 let table = instance.get_table(&mut store, "t").unwrap(); 345 346 for i in 0..5 { 347 let v = table.get(&mut store, i).expect("table should have entry"); 348 let f = v 349 .as_func() 350 .expect("expected funcref") 351 .expect("expected non-null value"); 352 assert_eq!(f.ty(&store).params().len(), i as usize); 353 } 354 355 assert!( 356 table 357 .get(&mut store, 5) 358 .expect("table should have entry") 359 .as_func() 360 .expect("expected funcref") 361 .is_none(), 362 "funcref should be null" 363 ); 364 365 Ok(()) 366 } 367 368 #[test] 369 fn table_zeroed() -> Result<()> { 370 if skip_pooling_allocator_tests() { 371 return Ok(()); 372 } 373 374 let pool = crate::small_pool_config(); 375 let mut config = Config::new(); 376 config.allocation_strategy(pool); 377 config.memory_guard_size(0); 378 config.memory_reservation(1 << 16); 379 380 let engine = Engine::new(&config)?; 381 382 let module = Module::new(&engine, r#"(module (table (export "t") 10 funcref))"#)?; 383 384 // Instantiate the module repeatedly after filling table elements 385 for _ in 0..10 { 386 let mut store = Store::new(&engine, ()); 387 let instance = Instance::new(&mut store, &module, &[])?; 388 let table = instance.get_table(&mut store, "t").unwrap(); 389 let f = Func::wrap(&mut store, || {}); 390 391 assert_eq!(table.size(&store), 10); 392 393 for i in 0..10 { 394 match table.get(&mut store, i).unwrap() { 395 Ref::Func(r) => assert!(r.is_none()), 396 _ => panic!("expected a funcref"), 397 } 398 table.set(&mut store, i, Ref::Func(Some(f))).unwrap(); 399 } 400 } 401 402 Ok(()) 403 } 404 405 #[test] 406 fn total_core_instances_limit() -> Result<()> { 407 const INSTANCE_LIMIT: u32 = 10; 408 let mut pool = crate::small_pool_config(); 409 pool.total_core_instances(INSTANCE_LIMIT); 410 let mut config = Config::new(); 411 config.allocation_strategy(pool); 412 config.memory_guard_size(0); 413 config.memory_reservation(1 << 16); 414 415 let engine = Engine::new(&config)?; 416 let module = Module::new(&engine, r#"(module)"#)?; 417 418 // Instantiate to the limit 419 { 420 let mut store = Store::new(&engine, ()); 421 422 for _ in 0..INSTANCE_LIMIT { 423 Instance::new(&mut store, &module, &[])?; 424 } 425 426 match Instance::new(&mut store, &module, &[]) { 427 Ok(_) => panic!("instantiation should fail"), 428 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 429 } 430 } 431 432 // With the above store dropped, ensure instantiations can be made 433 434 let mut store = Store::new(&engine, ()); 435 436 for _ in 0..INSTANCE_LIMIT { 437 Instance::new(&mut store, &module, &[])?; 438 } 439 440 Ok(()) 441 } 442 443 #[test] 444 fn preserve_data_segments() -> Result<()> { 445 let mut pool = crate::small_pool_config(); 446 pool.total_memories(2); 447 let mut config = Config::new(); 448 config.allocation_strategy(pool); 449 let engine = Engine::new(&config)?; 450 let m = Module::new( 451 &engine, 452 r#" 453 (module 454 (memory (export "mem") 1 1) 455 (data (i32.const 0) "foo")) 456 "#, 457 )?; 458 let mut store = Store::new(&engine, ()); 459 let i = Instance::new(&mut store, &m, &[])?; 460 461 // Drop the module. This should *not* drop the actual data referenced by the 462 // module. 463 drop(m); 464 465 // Spray some stuff on the heap. If wasm data lived on the heap this should 466 // paper over things and help us catch use-after-free here if it would 467 // otherwise happen. 468 if !cfg!(miri) { 469 let mut strings = Vec::new(); 470 for _ in 0..1000 { 471 let mut string = String::new(); 472 for _ in 0..1000 { 473 string.push('g'); 474 } 475 strings.push(string); 476 } 477 drop(strings); 478 } 479 480 let mem = i.get_memory(&mut store, "mem").unwrap(); 481 482 // Hopefully it's still `foo`! 483 assert!(mem.data(&store).starts_with(b"foo")); 484 485 Ok(()) 486 } 487 488 #[test] 489 fn multi_memory_with_imported_memories() -> Result<()> { 490 // This test checks that the base address for the defined memory is correct for the instance 491 // despite the presence of an imported memory. 492 493 let mut pool = crate::small_pool_config(); 494 pool.total_memories(2).max_memories_per_module(2); 495 let mut config = Config::new(); 496 config.allocation_strategy(pool); 497 config.wasm_multi_memory(true); 498 499 let engine = Engine::new(&config)?; 500 let module = Module::new( 501 &engine, 502 r#"(module (import "" "m1" (memory 0)) (memory (export "m2") 1))"#, 503 )?; 504 505 let mut store = Store::new(&engine, ()); 506 507 let m1 = Memory::new(&mut store, MemoryType::new(0, None))?; 508 let instance = Instance::new(&mut store, &module, &[m1.into()])?; 509 510 let m2 = instance.get_memory(&mut store, "m2").unwrap(); 511 512 m2.data_mut(&mut store)[0] = 0x42; 513 assert_eq!(m2.data(&store)[0], 0x42); 514 515 Ok(()) 516 } 517 518 #[test] 519 fn drop_externref_global_during_module_init() -> Result<()> { 520 struct Limiter; 521 522 impl ResourceLimiter for Limiter { 523 fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 524 Ok(false) 525 } 526 527 fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 528 Ok(false) 529 } 530 } 531 532 let pool = crate::small_pool_config(); 533 let mut config = Config::new(); 534 config.wasm_reference_types(true); 535 config.allocation_strategy(pool); 536 537 let engine = Engine::new(&config)?; 538 539 let module = Module::new( 540 &engine, 541 r#" 542 (module 543 (global i32 (i32.const 1)) 544 (global i32 (i32.const 2)) 545 (global i32 (i32.const 3)) 546 (global i32 (i32.const 4)) 547 (global i32 (i32.const 5)) 548 ) 549 "#, 550 )?; 551 552 let mut store = Store::new(&engine, Limiter); 553 Instance::new(&mut store, &module, &[])?; 554 drop(store); 555 556 let module = Module::new( 557 &engine, 558 r#" 559 (module 560 (memory 1) 561 (global (mut externref) (ref.null extern)) 562 ) 563 "#, 564 )?; 565 566 let mut store = Store::new(&engine, Limiter); 567 store.limiter(|s| s); 568 assert!(Instance::new(&mut store, &module, &[]).is_err()); 569 570 Ok(()) 571 } 572 573 #[test] 574 #[cfg_attr(miri, ignore)] 575 fn switch_image_and_non_image() -> Result<()> { 576 let pool = crate::small_pool_config(); 577 let mut c = Config::new(); 578 c.allocation_strategy(pool); 579 let engine = Engine::new(&c)?; 580 let module1 = Module::new( 581 &engine, 582 r#" 583 (module 584 (memory 1) 585 (func (export "load") (param i32) (result i32) 586 local.get 0 587 i32.load 588 ) 589 ) 590 "#, 591 )?; 592 let module2 = Module::new( 593 &engine, 594 r#" 595 (module 596 (memory (export "memory") 1) 597 (data (i32.const 0) "1234") 598 ) 599 "#, 600 )?; 601 602 let assert_zero = || -> Result<()> { 603 let mut store = Store::new(&engine, ()); 604 let instance = Instance::new(&mut store, &module1, &[])?; 605 let func = instance.get_typed_func::<i32, i32>(&mut store, "load")?; 606 assert_eq!(func.call(&mut store, 0)?, 0); 607 Ok(()) 608 }; 609 610 // Initialize with a heap image and make sure the next instance, without an 611 // image, is zeroed 612 Instance::new(&mut Store::new(&engine, ()), &module2, &[])?; 613 assert_zero()?; 614 615 // ... transition back to heap image and do this again 616 Instance::new(&mut Store::new(&engine, ()), &module2, &[])?; 617 assert_zero()?; 618 619 // And go back to an image and make sure it's read/write on the host. 620 let mut store = Store::new(&engine, ()); 621 let instance = Instance::new(&mut store, &module2, &[])?; 622 let memory = instance.get_memory(&mut store, "memory").unwrap(); 623 let mem = memory.data_mut(&mut store); 624 assert!(mem.starts_with(b"1234")); 625 mem[..6].copy_from_slice(b"567890"); 626 627 Ok(()) 628 } 629 630 #[test] 631 #[cfg(target_pointer_width = "64")] 632 #[cfg_attr(miri, ignore)] 633 fn instance_too_large() -> Result<()> { 634 let mut pool = crate::small_pool_config(); 635 pool.max_core_instance_size(16); 636 let mut config = Config::new(); 637 config.allocation_strategy(pool); 638 639 let engine = Engine::new(&config)?; 640 match Module::new(&engine, "(module)") { 641 Ok(_) => panic!("should have failed to compile"), 642 Err(e) => { 643 e.assert_contains("exceeds the configured maximum of 16 bytes"); 644 e.assert_contains("breakdown of allocation requirement"); 645 e.assert_contains("instance state management"); 646 e.assert_contains("static vmctx data"); 647 } 648 } 649 650 let mut lots_of_globals = format!("(module"); 651 for _ in 0..100 { 652 lots_of_globals.push_str("(global i32 i32.const 0)\n"); 653 } 654 lots_of_globals.push_str(")"); 655 656 match Module::new(&engine, &lots_of_globals) { 657 Ok(_) => panic!("should have failed to compile"), 658 Err(e) => { 659 e.assert_contains("exceeds the configured maximum of 16 bytes"); 660 e.assert_contains("breakdown of allocation requirement"); 661 e.assert_contains("defined globals"); 662 e.assert_contains("instance state management"); 663 } 664 } 665 666 Ok(()) 667 } 668 669 #[test] 670 #[cfg_attr(miri, ignore)] 671 fn dynamic_memory_pooling_allocator() -> Result<()> { 672 for guard_size in [0, 1 << 16] { 673 let max_size = 128 << 20; 674 let mut pool = crate::small_pool_config(); 675 pool.max_memory_size(max_size as usize); 676 let mut config = Config::new(); 677 config.memory_reservation(max_size); 678 config.memory_guard_size(guard_size); 679 config.allocation_strategy(pool); 680 681 let engine = Engine::new(&config)?; 682 683 let module = Module::new( 684 &engine, 685 r#" 686 (module 687 (memory (export "memory") 1) 688 689 (func (export "grow") (param i32) (result i32) 690 local.get 0 691 memory.grow) 692 693 (func (export "size") (result i32) 694 memory.size) 695 696 (func (export "i32.load") (param i32) (result i32) 697 local.get 0 698 i32.load) 699 700 (func (export "i32.store") (param i32 i32) 701 local.get 0 702 local.get 1 703 i32.store) 704 705 (data (i32.const 100) "x") 706 ) 707 "#, 708 )?; 709 710 let mut store = Store::new(&engine, ()); 711 let instance = Instance::new(&mut store, &module, &[])?; 712 713 let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?; 714 let size = instance.get_typed_func::<(), u32>(&mut store, "size")?; 715 let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?; 716 let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?; 717 let memory = instance.get_memory(&mut store, "memory").unwrap(); 718 719 // basic length 1 tests 720 // assert_eq!(memory.grow(&mut store, 1)?, 0); 721 assert_eq!(memory.size(&store), 1); 722 assert_eq!(size.call(&mut store, ())?, 1); 723 assert_eq!(i32_load.call(&mut store, 0)?, 0); 724 assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'x')); 725 i32_store.call(&mut store, (0, 0))?; 726 i32_store.call(&mut store, (100, i32::from(b'y')))?; 727 assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'y')); 728 729 // basic length 2 tests 730 let page = 64 * 1024; 731 assert_eq!(grow.call(&mut store, 1)?, 1); 732 assert_eq!(memory.size(&store), 2); 733 assert_eq!(size.call(&mut store, ())?, 2); 734 i32_store.call(&mut store, (page, 200))?; 735 assert_eq!(i32_load.call(&mut store, page)?, 200); 736 737 // test writes are visible 738 i32_store.call(&mut store, (2, 100))?; 739 assert_eq!(i32_load.call(&mut store, 2)?, 100); 740 741 // test growth can't exceed maximum 742 let too_many = max_size / (64 * 1024); 743 assert_eq!(grow.call(&mut store, too_many as u32)?, -1); 744 assert!(memory.grow(&mut store, too_many).is_err()); 745 746 assert_eq!(memory.data(&store)[page as usize], 200); 747 748 // Re-instantiate in another store. 749 store = Store::new(&engine, ()); 750 let instance = Instance::new(&mut store, &module, &[])?; 751 let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?; 752 let memory = instance.get_memory(&mut store, "memory").unwrap(); 753 754 // This is out of bounds... 755 assert!(i32_load.call(&mut store, page).is_err()); 756 assert_eq!(memory.data_size(&store), page as usize); 757 758 // ... but implementation-wise it should still be mapped memory from 759 // before if we don't have any guard pages. 760 // 761 // Note though that prior writes should all appear as zeros and we can't see 762 // data from the prior instance. 763 // 764 // Note that this part is only implemented on Linux which has 765 // `MADV_DONTNEED`. 766 if cfg!(target_os = "linux") && guard_size == 0 { 767 unsafe { 768 let ptr = memory.data_ptr(&store); 769 assert_eq!(*ptr.offset(page as isize), 0); 770 } 771 } 772 } 773 774 Ok(()) 775 } 776 777 #[test] 778 #[cfg_attr(miri, ignore)] 779 fn zero_memory_pages_disallows_oob() -> Result<()> { 780 let mut pool = crate::small_pool_config(); 781 pool.max_memory_size(0); 782 let mut config = Config::new(); 783 config.allocation_strategy(pool); 784 785 let engine = Engine::new(&config)?; 786 let module = Module::new( 787 &engine, 788 r#" 789 (module 790 (memory 0) 791 792 (func (export "load") (param i32) (result i32) 793 local.get 0 794 i32.load) 795 796 (func (export "store") (param i32 ) 797 local.get 0 798 local.get 0 799 i32.store) 800 ) 801 "#, 802 )?; 803 let mut store = Store::new(&engine, ()); 804 let instance = Instance::new(&mut store, &module, &[])?; 805 let load32 = instance.get_typed_func::<i32, i32>(&mut store, "load")?; 806 let store32 = instance.get_typed_func::<i32, ()>(&mut store, "store")?; 807 for i in 0..31 { 808 assert!(load32.call(&mut store, 1 << i).is_err()); 809 assert!(store32.call(&mut store, 1 << i).is_err()); 810 } 811 Ok(()) 812 } 813 814 #[test] 815 #[cfg(feature = "component-model")] 816 fn total_component_instances_limit() -> Result<()> { 817 const TOTAL_COMPONENT_INSTANCES: u32 = 5; 818 819 let mut pool = crate::small_pool_config(); 820 pool.total_component_instances(TOTAL_COMPONENT_INSTANCES); 821 let mut config = Config::new(); 822 config.wasm_component_model(true); 823 config.allocation_strategy(pool); 824 825 let engine = Engine::new(&config)?; 826 let linker = wasmtime::component::Linker::new(&engine); 827 let component = wasmtime::component::Component::new(&engine, "(component)")?; 828 829 let mut store = Store::new(&engine, ()); 830 for _ in 0..TOTAL_COMPONENT_INSTANCES { 831 linker.instantiate(&mut store, &component)?; 832 } 833 834 match linker.instantiate(&mut store, &component) { 835 Ok(_) => panic!("should have hit component instance limit"), 836 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 837 } 838 839 drop(store); 840 let mut store = Store::new(&engine, ()); 841 for _ in 0..TOTAL_COMPONENT_INSTANCES { 842 linker.instantiate(&mut store, &component)?; 843 } 844 845 Ok(()) 846 } 847 848 #[test] 849 #[cfg(feature = "component-model")] 850 #[cfg(target_pointer_width = "64")] // error message tailored for 64-bit 851 fn component_instance_size_limit() -> Result<()> { 852 let mut pool = crate::small_pool_config(); 853 pool.max_component_instance_size(1); 854 let mut config = Config::new(); 855 config.wasm_component_model(true); 856 config.allocation_strategy(pool); 857 let engine = Engine::new(&config)?; 858 859 match wasmtime::component::Component::new(&engine, "(component)") { 860 Ok(_) => panic!("should have hit limit"), 861 Err(e) => e.assert_contains( 862 "instance allocation for this component requires 64 bytes of \ 863 `VMComponentContext` space which exceeds the configured maximum of 1 bytes", 864 ), 865 } 866 867 Ok(()) 868 } 869 870 #[test] 871 #[cfg_attr(miri, ignore)] 872 fn total_tables_limit() -> Result<()> { 873 const TOTAL_TABLES: u32 = 5; 874 875 let mut pool = crate::small_pool_config(); 876 pool.total_tables(TOTAL_TABLES) 877 .total_core_instances(TOTAL_TABLES + 1); 878 let mut config = Config::new(); 879 config.allocation_strategy(pool); 880 881 let engine = Engine::new(&config)?; 882 let linker = Linker::new(&engine); 883 let module = Module::new(&engine, "(module (table 0 1 funcref))")?; 884 885 let mut store = Store::new(&engine, ()); 886 for _ in 0..TOTAL_TABLES { 887 linker.instantiate(&mut store, &module)?; 888 } 889 890 match linker.instantiate(&mut store, &module) { 891 Ok(_) => panic!("should have hit table limit"), 892 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 893 } 894 895 drop(store); 896 let mut store = Store::new(&engine, ()); 897 for _ in 0..TOTAL_TABLES { 898 linker.instantiate(&mut store, &module)?; 899 } 900 901 Ok(()) 902 } 903 904 #[tokio::test] 905 #[cfg(not(miri))] 906 async fn total_stacks_limit() -> Result<()> { 907 use super::async_functions::PollOnce; 908 909 const TOTAL_STACKS: u32 = 2; 910 911 let mut pool = crate::small_pool_config(); 912 pool.total_stacks(TOTAL_STACKS) 913 .total_core_instances(TOTAL_STACKS + 1); 914 let mut config = Config::new(); 915 config.allocation_strategy(pool); 916 917 let engine = Engine::new(&config)?; 918 919 let mut linker = Linker::new(&engine); 920 linker.func_new_async( 921 "async", 922 "yield", 923 FuncType::new(&engine, [], []), 924 |_caller, _params, _results| { 925 Box::new(async { 926 tokio::task::yield_now().await; 927 Ok(()) 928 }) 929 }, 930 )?; 931 932 let module = Module::new( 933 &engine, 934 r#" 935 (module 936 (import "async" "yield" (func $yield)) 937 (func (export "run") 938 call $yield 939 ) 940 941 (func $empty) 942 (start $empty) 943 ) 944 "#, 945 )?; 946 947 // Allocate stacks up to the limit. (Poll the futures once to make sure we 948 // actually enter Wasm and force a stack allocation.) 949 950 let mut store1 = Store::new(&engine, ()); 951 let instance1 = linker.instantiate_async(&mut store1, &module).await?; 952 let run1 = instance1.get_func(&mut store1, "run").unwrap(); 953 let future1 = PollOnce::new(Box::pin(run1.call_async(store1, &[], &mut []))) 954 .await 955 .unwrap_err(); 956 957 let mut store2 = Store::new(&engine, ()); 958 let instance2 = linker.instantiate_async(&mut store2, &module).await?; 959 let run2 = instance2.get_func(&mut store2, "run").unwrap(); 960 let future2 = PollOnce::new(Box::pin(run2.call_async(store2, &[], &mut []))) 961 .await 962 .unwrap_err(); 963 964 // Allocating more should fail. 965 let mut store3 = Store::new(&engine, ()); 966 match linker.instantiate_async(&mut store3, &module).await { 967 Ok(_) => panic!("should have hit stack limit"), 968 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 969 } 970 971 // Finish the futures and return their Wasm stacks to the pool. 972 future1.await?; 973 future2.await?; 974 975 // Should be able to allocate new stacks again. 976 let mut store1 = Store::new(&engine, ()); 977 let instance1 = linker.instantiate_async(&mut store1, &module).await?; 978 let run1 = instance1.get_func(&mut store1, "run").unwrap(); 979 let future1 = run1.call_async(&mut store1, &[], &mut []); 980 981 let mut store2 = Store::new(&engine, ()); 982 let instance2 = linker.instantiate_async(&mut store2, &module).await?; 983 let run2 = instance2.get_func(&mut store2, "run").unwrap(); 984 let future2 = run2.call_async(&mut store2, &[], &mut []); 985 986 future1.await?; 987 future2.await?; 988 989 // Dispose one store via `Drop`, the other via `into_data`, and ensure that 990 // any lingering stacks make their way back to the pool. 991 drop(store1); 992 store2.into_data(); 993 994 Ok(()) 995 } 996 997 #[test] 998 #[cfg(feature = "component-model")] 999 fn component_core_instances_limit() -> Result<()> { 1000 let mut pool = crate::small_pool_config(); 1001 pool.max_core_instances_per_component(1); 1002 let mut config = Config::new(); 1003 config.wasm_component_model(true); 1004 config.allocation_strategy(pool); 1005 let engine = Engine::new(&config)?; 1006 1007 // One core instance works. 1008 wasmtime::component::Component::new( 1009 &engine, 1010 r#" 1011 (component 1012 (core module $m) 1013 (core instance $a (instantiate $m)) 1014 ) 1015 "#, 1016 )?; 1017 1018 // Two core instances doesn't. 1019 match wasmtime::component::Component::new( 1020 &engine, 1021 r#" 1022 (component 1023 (core module $m) 1024 (core instance $a (instantiate $m)) 1025 (core instance $b (instantiate $m)) 1026 ) 1027 "#, 1028 ) { 1029 Ok(_) => panic!("should have hit limit"), 1030 Err(e) => e.assert_contains( 1031 "The component transitively contains 2 core module instances, which exceeds the \ 1032 configured maximum of 1", 1033 ), 1034 } 1035 1036 Ok(()) 1037 } 1038 1039 #[test] 1040 #[cfg(feature = "component-model")] 1041 fn component_memories_limit() -> Result<()> { 1042 let mut pool = crate::small_pool_config(); 1043 pool.max_memories_per_component(1).total_memories(2); 1044 let mut config = Config::new(); 1045 config.wasm_component_model(true); 1046 config.allocation_strategy(pool); 1047 let engine = Engine::new(&config)?; 1048 1049 // One memory works. 1050 wasmtime::component::Component::new( 1051 &engine, 1052 r#" 1053 (component 1054 (core module $m (memory 1 1)) 1055 (core instance $a (instantiate $m)) 1056 ) 1057 "#, 1058 )?; 1059 1060 // Two memories doesn't. 1061 match wasmtime::component::Component::new( 1062 &engine, 1063 r#" 1064 (component 1065 (core module $m (memory 1 1)) 1066 (core instance $a (instantiate $m)) 1067 (core instance $b (instantiate $m)) 1068 ) 1069 "#, 1070 ) { 1071 Ok(_) => panic!("should have hit limit"), 1072 Err(e) => e.assert_contains( 1073 "The component transitively contains 2 Wasm linear memories, which exceeds the \ 1074 configured maximum of 1", 1075 ), 1076 } 1077 1078 Ok(()) 1079 } 1080 1081 #[test] 1082 #[cfg(feature = "component-model")] 1083 fn component_tables_limit() -> Result<()> { 1084 let mut pool = crate::small_pool_config(); 1085 pool.max_tables_per_component(1).total_tables(2); 1086 let mut config = Config::new(); 1087 config.wasm_component_model(true); 1088 config.allocation_strategy(pool); 1089 let engine = Engine::new(&config)?; 1090 1091 // One table works. 1092 wasmtime::component::Component::new( 1093 &engine, 1094 r#" 1095 (component 1096 (core module $m (table 1 1 funcref)) 1097 (core instance $a (instantiate $m)) 1098 ) 1099 "#, 1100 )?; 1101 1102 // Two tables doesn't. 1103 match wasmtime::component::Component::new( 1104 &engine, 1105 r#" 1106 (component 1107 (core module $m (table 1 1 funcref)) 1108 (core instance $a (instantiate $m)) 1109 (core instance $b (instantiate $m)) 1110 ) 1111 "#, 1112 ) { 1113 Ok(_) => panic!("should have hit limit"), 1114 Err(e) => e.assert_contains( 1115 "The component transitively contains 2 tables, which exceeds the \ 1116 configured maximum of 1", 1117 ), 1118 } 1119 1120 Ok(()) 1121 } 1122 1123 #[test] 1124 #[cfg_attr(miri, ignore)] 1125 fn total_memories_limit() -> Result<()> { 1126 const TOTAL_MEMORIES: u32 = 5; 1127 1128 let mut pool = crate::small_pool_config(); 1129 pool.total_memories(TOTAL_MEMORIES) 1130 .total_core_instances(TOTAL_MEMORIES + 1) 1131 .memory_protection_keys(Enabled::No); 1132 let mut config = Config::new(); 1133 config.allocation_strategy(pool); 1134 1135 let engine = Engine::new(&config)?; 1136 let linker = Linker::new(&engine); 1137 let module = Module::new(&engine, "(module (memory 1 1))")?; 1138 1139 let mut store = Store::new(&engine, ()); 1140 for _ in 0..TOTAL_MEMORIES { 1141 linker.instantiate(&mut store, &module)?; 1142 } 1143 1144 match linker.instantiate(&mut store, &module) { 1145 Ok(_) => panic!("should have hit memory limit"), 1146 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 1147 } 1148 1149 drop(store); 1150 let mut store = Store::new(&engine, ()); 1151 for _ in 0..TOTAL_MEMORIES { 1152 linker.instantiate(&mut store, &module)?; 1153 } 1154 1155 Ok(()) 1156 } 1157 1158 #[test] 1159 #[cfg_attr(miri, ignore)] 1160 fn decommit_batching() -> Result<()> { 1161 for (capacity, batch_size) in [ 1162 // A reasonable batch size. 1163 (10, 5), 1164 // Batch sizes of zero and one should effectively disable batching. 1165 (10, 1), 1166 (10, 0), 1167 // A bigger batch size than capacity, which forces the allocation path 1168 // to flush the decommit queue. 1169 (10, 99), 1170 ] { 1171 let mut pool = crate::small_pool_config(); 1172 pool.total_memories(capacity) 1173 .total_core_instances(capacity) 1174 .decommit_batch_size(batch_size) 1175 .memory_protection_keys(Enabled::No); 1176 let mut config = Config::new(); 1177 config.allocation_strategy(pool); 1178 1179 let engine = Engine::new(&config)?; 1180 let linker = Linker::new(&engine); 1181 let module = Module::new(&engine, "(module (memory 1 1))")?; 1182 1183 // Just make sure that we can instantiate all slots a few times and the 1184 // pooling allocator must be flushing the decommit queue as necessary. 1185 for _ in 0..3 { 1186 let mut store = Store::new(&engine, ()); 1187 for _ in 0..capacity { 1188 linker.instantiate(&mut store, &module)?; 1189 } 1190 } 1191 } 1192 1193 Ok(()) 1194 } 1195 1196 #[test] 1197 fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> { 1198 // Configure the pooling allocator to have no access to virtual memory, e.g. 1199 // no table elements but a single table. This should technically support a 1200 // single empty table being allocated into it but virtual memory isn't 1201 // actually allocated here. 1202 let mut cfg = PoolingAllocationConfig::default(); 1203 cfg.table_elements(0); 1204 cfg.total_memories(0); 1205 cfg.total_tables(1); 1206 cfg.total_stacks(0); 1207 cfg.total_core_instances(1); 1208 cfg.max_memory_size(0); 1209 1210 let mut c = Config::new(); 1211 c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1212 1213 // Disable lazy init to actually try to get this to do something interesting 1214 // at runtime. 1215 c.table_lazy_init(false); 1216 1217 let engine = Engine::new(&c)?; 1218 1219 // This module has a single empty table, with a single empty element 1220 // segment. Nothing actually goes wrong here, it should instantiate 1221 // successfully. Along the way though the empty mmap above will get viewed 1222 // as an array-of-pointers, so everything internally should all line up to 1223 // work ok. 1224 let module = Module::new( 1225 &engine, 1226 r#" 1227 (module 1228 (table 0 funcref) 1229 (elem (i32.const 0) func) 1230 ) 1231 "#, 1232 )?; 1233 let mut store = Store::new(&engine, ()); 1234 Instance::new(&mut store, &module, &[])?; 1235 Ok(()) 1236 } 1237 1238 #[test] 1239 #[cfg_attr(miri, ignore)] 1240 fn shared_memory_unsupported() -> Result<()> { 1241 // Skip this test on platforms that don't support threads. 1242 if crate::threads::engine().is_none() { 1243 return Ok(()); 1244 } 1245 let mut config = Config::new(); 1246 let mut cfg = PoolingAllocationConfig::default(); 1247 // shrink the size of this allocator 1248 cfg.total_memories(1); 1249 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1250 let engine = Engine::new(&config)?; 1251 1252 let err = Module::new( 1253 &engine, 1254 r#" 1255 (module 1256 (memory 5 5 shared) 1257 ) 1258 "#, 1259 ) 1260 .unwrap_err(); 1261 err.assert_contains( 1262 "memory is shared which is not supported \ 1263 in the pooling allocator", 1264 ); 1265 err.assert_contains("memory index 0"); 1266 Ok(()) 1267 } 1268 1269 #[test] 1270 #[cfg_attr(miri, ignore)] 1271 fn custom_page_sizes_reusing_same_slot() -> Result<()> { 1272 let mut config = Config::new(); 1273 config.wasm_custom_page_sizes(true); 1274 let mut cfg = crate::small_pool_config(); 1275 // force the memories below to collide in the same memory slot 1276 cfg.total_memories(1); 1277 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1278 let engine = Engine::new(&config)?; 1279 1280 // Instantiate one module, leaving the slot 5 bytes big (but one page 1281 // accessible) 1282 { 1283 let m1 = Module::new( 1284 &engine, 1285 r#" 1286 (module 1287 (memory 5 (pagesize 1)) 1288 1289 (data (i32.const 0) "a") 1290 ) 1291 "#, 1292 )?; 1293 let mut store = Store::new(&engine, ()); 1294 Instance::new(&mut store, &m1, &[])?; 1295 } 1296 1297 // Instantiate a second module, which should work 1298 { 1299 let m2 = Module::new( 1300 &engine, 1301 r#" 1302 (module 1303 (memory 6 (pagesize 1)) 1304 1305 (data (i32.const 0) "a") 1306 ) 1307 "#, 1308 )?; 1309 let mut store = Store::new(&engine, ()); 1310 Instance::new(&mut store, &m2, &[])?; 1311 } 1312 Ok(()) 1313 } 1314 1315 #[test] 1316 #[cfg_attr(miri, ignore)] 1317 fn instantiate_non_page_aligned_sizes() -> Result<()> { 1318 let mut config = Config::new(); 1319 config.wasm_custom_page_sizes(true); 1320 let mut cfg = crate::small_pool_config(); 1321 cfg.total_memories(1); 1322 cfg.max_memory_size(761927); 1323 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1324 let engine = Engine::new(&config)?; 1325 1326 let module = Module::new( 1327 &engine, 1328 r#" 1329 (module 1330 (memory 761927 761927 (pagesize 0x1)) 1331 ) 1332 "#, 1333 )?; 1334 let mut store = Store::new(&engine, ()); 1335 Instance::new(&mut store, &module, &[])?; 1336 Ok(()) 1337 } 1338 1339 #[test] 1340 #[cfg_attr(miri, ignore)] 1341 fn pagemap_scan_enabled_or_disabled() -> Result<()> { 1342 let mut config = Config::new(); 1343 let mut cfg = crate::small_pool_config(); 1344 cfg.total_memories(1); 1345 cfg.pagemap_scan(Enabled::Yes); 1346 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1347 let result = Engine::new(&config); 1348 1349 if PoolingAllocationConfig::is_pagemap_scan_available() { 1350 assert!(result.is_ok()); 1351 } else { 1352 assert!(result.is_err()); 1353 } 1354 Ok(()) 1355 } 1356 1357 // This test instantiates a memory with an image into a slot in the pooling 1358 // allocator in a way that maps the image into the allocator but fails 1359 // instantiation. Failure here is injected with `ResourceLimiter`. Afterwards 1360 // instantiation is allowed to succeed with a memory that has no image, and this 1361 // asserts that the previous image is indeed not available any more as that 1362 // would otherwise mean data was leaked between modules. 1363 #[test] 1364 fn memory_reset_if_instantiation_fails() -> Result<()> { 1365 struct Limiter; 1366 1367 impl ResourceLimiter for Limiter { 1368 fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 1369 Ok(false) 1370 } 1371 1372 fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 1373 Ok(false) 1374 } 1375 } 1376 1377 let pool = crate::small_pool_config(); 1378 let mut config = Config::new(); 1379 config.allocation_strategy(pool); 1380 let engine = Engine::new(&config)?; 1381 1382 let module_with_image = Module::new( 1383 &engine, 1384 r#" 1385 (module 1386 (memory 1) 1387 (data (i32.const 0) "\aa") 1388 ) 1389 "#, 1390 )?; 1391 let module_without_image = Module::new( 1392 &engine, 1393 r#" 1394 (module 1395 (memory (export "m") 1) 1396 ) 1397 "#, 1398 )?; 1399 1400 let mut store = Store::new(&engine, Limiter); 1401 store.limiter(|s| s); 1402 assert!(Instance::new(&mut store, &module_with_image, &[]).is_err()); 1403 drop(store); 1404 1405 let mut store = Store::new(&engine, Limiter); 1406 let instance = Instance::new(&mut store, &module_without_image, &[])?; 1407 let mem = instance.get_memory(&mut store, "m").unwrap(); 1408 let data = mem.data(&store); 1409 assert_eq!(data[0], 0); 1410 1411 Ok(()) 1412 } 1413