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 48 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.async_support(true); 916 config.allocation_strategy(pool); 917 918 let engine = Engine::new(&config)?; 919 920 let mut linker = Linker::new(&engine); 921 linker.func_new_async( 922 "async", 923 "yield", 924 FuncType::new(&engine, [], []), 925 |_caller, _params, _results| { 926 Box::new(async { 927 tokio::task::yield_now().await; 928 Ok(()) 929 }) 930 }, 931 )?; 932 933 let module = Module::new( 934 &engine, 935 r#" 936 (module 937 (import "async" "yield" (func $yield)) 938 (func (export "run") 939 call $yield 940 ) 941 942 (func $empty) 943 (start $empty) 944 ) 945 "#, 946 )?; 947 948 // Allocate stacks up to the limit. (Poll the futures once to make sure we 949 // actually enter Wasm and force a stack allocation.) 950 951 let mut store1 = Store::new(&engine, ()); 952 let instance1 = linker.instantiate_async(&mut store1, &module).await?; 953 let run1 = instance1.get_func(&mut store1, "run").unwrap(); 954 let future1 = PollOnce::new(Box::pin(run1.call_async(store1, &[], &mut []))) 955 .await 956 .unwrap_err(); 957 958 let mut store2 = Store::new(&engine, ()); 959 let instance2 = linker.instantiate_async(&mut store2, &module).await?; 960 let run2 = instance2.get_func(&mut store2, "run").unwrap(); 961 let future2 = PollOnce::new(Box::pin(run2.call_async(store2, &[], &mut []))) 962 .await 963 .unwrap_err(); 964 965 // Allocating more should fail. 966 let mut store3 = Store::new(&engine, ()); 967 match linker.instantiate_async(&mut store3, &module).await { 968 Ok(_) => panic!("should have hit stack limit"), 969 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 970 } 971 972 // Finish the futures and return their Wasm stacks to the pool. 973 future1.await?; 974 future2.await?; 975 976 // Should be able to allocate new stacks again. 977 let mut store1 = Store::new(&engine, ()); 978 let instance1 = linker.instantiate_async(&mut store1, &module).await?; 979 let run1 = instance1.get_func(&mut store1, "run").unwrap(); 980 let future1 = run1.call_async(&mut store1, &[], &mut []); 981 982 let mut store2 = Store::new(&engine, ()); 983 let instance2 = linker.instantiate_async(&mut store2, &module).await?; 984 let run2 = instance2.get_func(&mut store2, "run").unwrap(); 985 let future2 = run2.call_async(&mut store2, &[], &mut []); 986 987 future1.await?; 988 future2.await?; 989 990 // Dispose one store via `Drop`, the other via `into_data`, and ensure that 991 // any lingering stacks make their way back to the pool. 992 drop(store1); 993 store2.into_data(); 994 995 Ok(()) 996 } 997 998 #[test] 999 #[cfg(feature = "component-model")] 1000 fn component_core_instances_limit() -> Result<()> { 1001 let mut pool = crate::small_pool_config(); 1002 pool.max_core_instances_per_component(1); 1003 let mut config = Config::new(); 1004 config.wasm_component_model(true); 1005 config.allocation_strategy(pool); 1006 let engine = Engine::new(&config)?; 1007 1008 // One core instance works. 1009 wasmtime::component::Component::new( 1010 &engine, 1011 r#" 1012 (component 1013 (core module $m) 1014 (core instance $a (instantiate $m)) 1015 ) 1016 "#, 1017 )?; 1018 1019 // Two core instances doesn't. 1020 match wasmtime::component::Component::new( 1021 &engine, 1022 r#" 1023 (component 1024 (core module $m) 1025 (core instance $a (instantiate $m)) 1026 (core instance $b (instantiate $m)) 1027 ) 1028 "#, 1029 ) { 1030 Ok(_) => panic!("should have hit limit"), 1031 Err(e) => e.assert_contains( 1032 "The component transitively contains 2 core module instances, which exceeds the \ 1033 configured maximum of 1", 1034 ), 1035 } 1036 1037 Ok(()) 1038 } 1039 1040 #[test] 1041 #[cfg(feature = "component-model")] 1042 fn component_memories_limit() -> Result<()> { 1043 let mut pool = crate::small_pool_config(); 1044 pool.max_memories_per_component(1).total_memories(2); 1045 let mut config = Config::new(); 1046 config.wasm_component_model(true); 1047 config.allocation_strategy(pool); 1048 let engine = Engine::new(&config)?; 1049 1050 // One memory works. 1051 wasmtime::component::Component::new( 1052 &engine, 1053 r#" 1054 (component 1055 (core module $m (memory 1 1)) 1056 (core instance $a (instantiate $m)) 1057 ) 1058 "#, 1059 )?; 1060 1061 // Two memories doesn't. 1062 match wasmtime::component::Component::new( 1063 &engine, 1064 r#" 1065 (component 1066 (core module $m (memory 1 1)) 1067 (core instance $a (instantiate $m)) 1068 (core instance $b (instantiate $m)) 1069 ) 1070 "#, 1071 ) { 1072 Ok(_) => panic!("should have hit limit"), 1073 Err(e) => e.assert_contains( 1074 "The component transitively contains 2 Wasm linear memories, which exceeds the \ 1075 configured maximum of 1", 1076 ), 1077 } 1078 1079 Ok(()) 1080 } 1081 1082 #[test] 1083 #[cfg(feature = "component-model")] 1084 fn component_tables_limit() -> Result<()> { 1085 let mut pool = crate::small_pool_config(); 1086 pool.max_tables_per_component(1).total_tables(2); 1087 let mut config = Config::new(); 1088 config.wasm_component_model(true); 1089 config.allocation_strategy(pool); 1090 let engine = Engine::new(&config)?; 1091 1092 // One table works. 1093 wasmtime::component::Component::new( 1094 &engine, 1095 r#" 1096 (component 1097 (core module $m (table 1 1 funcref)) 1098 (core instance $a (instantiate $m)) 1099 ) 1100 "#, 1101 )?; 1102 1103 // Two tables doesn't. 1104 match wasmtime::component::Component::new( 1105 &engine, 1106 r#" 1107 (component 1108 (core module $m (table 1 1 funcref)) 1109 (core instance $a (instantiate $m)) 1110 (core instance $b (instantiate $m)) 1111 ) 1112 "#, 1113 ) { 1114 Ok(_) => panic!("should have hit limit"), 1115 Err(e) => e.assert_contains( 1116 "The component transitively contains 2 tables, which exceeds the \ 1117 configured maximum of 1", 1118 ), 1119 } 1120 1121 Ok(()) 1122 } 1123 1124 #[test] 1125 #[cfg_attr(miri, ignore)] 1126 fn total_memories_limit() -> Result<()> { 1127 const TOTAL_MEMORIES: u32 = 5; 1128 1129 let mut pool = crate::small_pool_config(); 1130 pool.total_memories(TOTAL_MEMORIES) 1131 .total_core_instances(TOTAL_MEMORIES + 1) 1132 .memory_protection_keys(Enabled::No); 1133 let mut config = Config::new(); 1134 config.allocation_strategy(pool); 1135 1136 let engine = Engine::new(&config)?; 1137 let linker = Linker::new(&engine); 1138 let module = Module::new(&engine, "(module (memory 1 1))")?; 1139 1140 let mut store = Store::new(&engine, ()); 1141 for _ in 0..TOTAL_MEMORIES { 1142 linker.instantiate(&mut store, &module)?; 1143 } 1144 1145 match linker.instantiate(&mut store, &module) { 1146 Ok(_) => panic!("should have hit memory limit"), 1147 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 1148 } 1149 1150 drop(store); 1151 let mut store = Store::new(&engine, ()); 1152 for _ in 0..TOTAL_MEMORIES { 1153 linker.instantiate(&mut store, &module)?; 1154 } 1155 1156 Ok(()) 1157 } 1158 1159 #[test] 1160 #[cfg_attr(miri, ignore)] 1161 fn decommit_batching() -> Result<()> { 1162 for (capacity, batch_size) in [ 1163 // A reasonable batch size. 1164 (10, 5), 1165 // Batch sizes of zero and one should effectively disable batching. 1166 (10, 1), 1167 (10, 0), 1168 // A bigger batch size than capacity, which forces the allocation path 1169 // to flush the decommit queue. 1170 (10, 99), 1171 ] { 1172 let mut pool = crate::small_pool_config(); 1173 pool.total_memories(capacity) 1174 .total_core_instances(capacity) 1175 .decommit_batch_size(batch_size) 1176 .memory_protection_keys(Enabled::No); 1177 let mut config = Config::new(); 1178 config.allocation_strategy(pool); 1179 1180 let engine = Engine::new(&config)?; 1181 let linker = Linker::new(&engine); 1182 let module = Module::new(&engine, "(module (memory 1 1))")?; 1183 1184 // Just make sure that we can instantiate all slots a few times and the 1185 // pooling allocator must be flushing the decommit queue as necessary. 1186 for _ in 0..3 { 1187 let mut store = Store::new(&engine, ()); 1188 for _ in 0..capacity { 1189 linker.instantiate(&mut store, &module)?; 1190 } 1191 } 1192 } 1193 1194 Ok(()) 1195 } 1196 1197 #[test] 1198 fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> { 1199 // Configure the pooling allocator to have no access to virtual memory, e.g. 1200 // no table elements but a single table. This should technically support a 1201 // single empty table being allocated into it but virtual memory isn't 1202 // actually allocated here. 1203 let mut cfg = PoolingAllocationConfig::default(); 1204 cfg.table_elements(0); 1205 cfg.total_memories(0); 1206 cfg.total_tables(1); 1207 cfg.total_stacks(0); 1208 cfg.total_core_instances(1); 1209 cfg.max_memory_size(0); 1210 1211 let mut c = Config::new(); 1212 c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1213 1214 // Disable lazy init to actually try to get this to do something interesting 1215 // at runtime. 1216 c.table_lazy_init(false); 1217 1218 let engine = Engine::new(&c)?; 1219 1220 // This module has a single empty table, with a single empty element 1221 // segment. Nothing actually goes wrong here, it should instantiate 1222 // successfully. Along the way though the empty mmap above will get viewed 1223 // as an array-of-pointers, so everything internally should all line up to 1224 // work ok. 1225 let module = Module::new( 1226 &engine, 1227 r#" 1228 (module 1229 (table 0 funcref) 1230 (elem (i32.const 0) func) 1231 ) 1232 "#, 1233 )?; 1234 let mut store = Store::new(&engine, ()); 1235 Instance::new(&mut store, &module, &[])?; 1236 Ok(()) 1237 } 1238 1239 #[test] 1240 #[cfg_attr(miri, ignore)] 1241 fn shared_memory_unsupported() -> Result<()> { 1242 // Skip this test on platforms that don't support threads. 1243 if crate::threads::engine().is_none() { 1244 return Ok(()); 1245 } 1246 let mut config = Config::new(); 1247 let mut cfg = PoolingAllocationConfig::default(); 1248 // shrink the size of this allocator 1249 cfg.total_memories(1); 1250 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1251 let engine = Engine::new(&config)?; 1252 1253 let err = Module::new( 1254 &engine, 1255 r#" 1256 (module 1257 (memory 5 5 shared) 1258 ) 1259 "#, 1260 ) 1261 .unwrap_err(); 1262 err.assert_contains( 1263 "memory is shared which is not supported \ 1264 in the pooling allocator", 1265 ); 1266 err.assert_contains("memory index 0"); 1267 Ok(()) 1268 } 1269 1270 #[test] 1271 #[cfg_attr(miri, ignore)] 1272 fn custom_page_sizes_reusing_same_slot() -> Result<()> { 1273 let mut config = Config::new(); 1274 config.wasm_custom_page_sizes(true); 1275 let mut cfg = crate::small_pool_config(); 1276 // force the memories below to collide in the same memory slot 1277 cfg.total_memories(1); 1278 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1279 let engine = Engine::new(&config)?; 1280 1281 // Instantiate one module, leaving the slot 5 bytes big (but one page 1282 // accessible) 1283 { 1284 let m1 = Module::new( 1285 &engine, 1286 r#" 1287 (module 1288 (memory 5 (pagesize 1)) 1289 1290 (data (i32.const 0) "a") 1291 ) 1292 "#, 1293 )?; 1294 let mut store = Store::new(&engine, ()); 1295 Instance::new(&mut store, &m1, &[])?; 1296 } 1297 1298 // Instantiate a second module, which should work 1299 { 1300 let m2 = Module::new( 1301 &engine, 1302 r#" 1303 (module 1304 (memory 6 (pagesize 1)) 1305 1306 (data (i32.const 0) "a") 1307 ) 1308 "#, 1309 )?; 1310 let mut store = Store::new(&engine, ()); 1311 Instance::new(&mut store, &m2, &[])?; 1312 } 1313 Ok(()) 1314 } 1315 1316 #[test] 1317 #[cfg_attr(miri, ignore)] 1318 fn instantiate_non_page_aligned_sizes() -> Result<()> { 1319 let mut config = Config::new(); 1320 config.wasm_custom_page_sizes(true); 1321 let mut cfg = crate::small_pool_config(); 1322 cfg.total_memories(1); 1323 cfg.max_memory_size(761927); 1324 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1325 let engine = Engine::new(&config)?; 1326 1327 let module = Module::new( 1328 &engine, 1329 r#" 1330 (module 1331 (memory 761927 761927 (pagesize 0x1)) 1332 ) 1333 "#, 1334 )?; 1335 let mut store = Store::new(&engine, ()); 1336 Instance::new(&mut store, &module, &[])?; 1337 Ok(()) 1338 } 1339 1340 #[test] 1341 #[cfg_attr(miri, ignore)] 1342 fn pagemap_scan_enabled_or_disabled() -> Result<()> { 1343 let mut config = Config::new(); 1344 let mut cfg = crate::small_pool_config(); 1345 cfg.total_memories(1); 1346 cfg.pagemap_scan(Enabled::Yes); 1347 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1348 let result = Engine::new(&config); 1349 1350 if PoolingAllocationConfig::is_pagemap_scan_available() { 1351 assert!(result.is_ok()); 1352 } else { 1353 assert!(result.is_err()); 1354 } 1355 Ok(()) 1356 } 1357 1358 // This test instantiates a memory with an image into a slot in the pooling 1359 // allocator in a way that maps the image into the allocator but fails 1360 // instantiation. Failure here is injected with `ResourceLimiter`. Afterwards 1361 // instantiation is allowed to succeed with a memory that has no image, and this 1362 // asserts that the previous image is indeed not available any more as that 1363 // would otherwise mean data was leaked between modules. 1364 #[test] 1365 fn memory_reset_if_instantiation_fails() -> Result<()> { 1366 struct Limiter; 1367 1368 impl ResourceLimiter for Limiter { 1369 fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 1370 Ok(false) 1371 } 1372 1373 fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 1374 Ok(false) 1375 } 1376 } 1377 1378 let pool = crate::small_pool_config(); 1379 let mut config = Config::new(); 1380 config.allocation_strategy(pool); 1381 let engine = Engine::new(&config)?; 1382 1383 let module_with_image = Module::new( 1384 &engine, 1385 r#" 1386 (module 1387 (memory 1) 1388 (data (i32.const 0) "\aa") 1389 ) 1390 "#, 1391 )?; 1392 let module_without_image = Module::new( 1393 &engine, 1394 r#" 1395 (module 1396 (memory (export "m") 1) 1397 ) 1398 "#, 1399 )?; 1400 1401 let mut store = Store::new(&engine, Limiter); 1402 store.limiter(|s| s); 1403 assert!(Instance::new(&mut store, &module_with_image, &[]).is_err()); 1404 drop(store); 1405 1406 let mut store = Store::new(&engine, Limiter); 1407 let instance = Instance::new(&mut store, &module_without_image, &[])?; 1408 let mem = instance.get_memory(&mut store, "m").unwrap(); 1409 let data = mem.data(&store); 1410 assert_eq!(data[0], 0); 1411 1412 Ok(()) 1413 } 1414