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) => { 862 e.assert_contains("instance allocation for this component requires 64 bytes"); 863 e.assert_contains("which exceeds the configured maximum of 1 bytes"); 864 e.assert_contains("`VMComponentContext` used 64 bytes"); 865 } 866 } 867 868 Ok(()) 869 } 870 871 #[test] 872 #[cfg_attr(miri, ignore)] 873 fn total_tables_limit() -> Result<()> { 874 const TOTAL_TABLES: u32 = 5; 875 876 let mut pool = crate::small_pool_config(); 877 pool.total_tables(TOTAL_TABLES) 878 .total_core_instances(TOTAL_TABLES + 1); 879 let mut config = Config::new(); 880 config.allocation_strategy(pool); 881 882 let engine = Engine::new(&config)?; 883 let linker = Linker::new(&engine); 884 let module = Module::new(&engine, "(module (table 0 1 funcref))")?; 885 886 let mut store = Store::new(&engine, ()); 887 for _ in 0..TOTAL_TABLES { 888 linker.instantiate(&mut store, &module)?; 889 } 890 891 match linker.instantiate(&mut store, &module) { 892 Ok(_) => panic!("should have hit table limit"), 893 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 894 } 895 896 drop(store); 897 let mut store = Store::new(&engine, ()); 898 for _ in 0..TOTAL_TABLES { 899 linker.instantiate(&mut store, &module)?; 900 } 901 902 Ok(()) 903 } 904 905 #[tokio::test] 906 #[cfg(not(miri))] 907 async fn total_stacks_limit() -> Result<()> { 908 use super::async_functions::PollOnce; 909 910 const TOTAL_STACKS: u32 = 2; 911 912 let mut pool = crate::small_pool_config(); 913 pool.total_stacks(TOTAL_STACKS) 914 .total_core_instances(TOTAL_STACKS + 1); 915 let mut config = Config::new(); 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(feature = "component-model")] 1126 #[cfg_attr(miri, ignore)] 1127 fn component_core_instances_aggregate_size() -> Result<()> { 1128 let mut pool = crate::small_pool_config(); 1129 pool.max_core_instances_per_component(100) 1130 // x86_64 requires 23824 bytes; we exceed this by a fair bit as there will 1131 // be differences by arch. 1132 .max_component_instance_size(1024); 1133 1134 let mut config = Config::new(); 1135 config.wasm_component_model(true); 1136 config.allocation_strategy(pool); 1137 let engine = Engine::new(&config)?; 1138 1139 let core_instances = (1..100) 1140 .map(|i| format!("(core instance $i{i} (instantiate $m))")) 1141 .collect::<Vec<String>>() 1142 .join("\n"); 1143 1144 match wasmtime::component::Component::new( 1145 &engine, 1146 format!( 1147 " 1148 (component 1149 (core module $m 1150 (func (export \"f\") (result i32) 1151 i32.const 42 1152 ) 1153 ) 1154 {core_instances} 1155 ) 1156 " 1157 ), 1158 ) { 1159 Ok(_) => panic!("should have hit aggregate size limit"), 1160 Err(e) => { 1161 e.assert_contains("instance allocation for this component requires"); 1162 e.assert_contains("exceeds the configured maximum"); 1163 } 1164 } 1165 1166 Ok(()) 1167 } 1168 1169 #[test] 1170 #[cfg_attr(miri, ignore)] 1171 fn total_memories_limit() -> Result<()> { 1172 const TOTAL_MEMORIES: u32 = 5; 1173 1174 let mut pool = crate::small_pool_config(); 1175 pool.total_memories(TOTAL_MEMORIES) 1176 .total_core_instances(TOTAL_MEMORIES + 1) 1177 .memory_protection_keys(Enabled::No); 1178 let mut config = Config::new(); 1179 config.allocation_strategy(pool); 1180 1181 let engine = Engine::new(&config)?; 1182 let linker = Linker::new(&engine); 1183 let module = Module::new(&engine, "(module (memory 1 1))")?; 1184 1185 let mut store = Store::new(&engine, ()); 1186 for _ in 0..TOTAL_MEMORIES { 1187 linker.instantiate(&mut store, &module)?; 1188 } 1189 1190 match linker.instantiate(&mut store, &module) { 1191 Ok(_) => panic!("should have hit memory limit"), 1192 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 1193 } 1194 1195 drop(store); 1196 let mut store = Store::new(&engine, ()); 1197 for _ in 0..TOTAL_MEMORIES { 1198 linker.instantiate(&mut store, &module)?; 1199 } 1200 1201 Ok(()) 1202 } 1203 1204 #[test] 1205 #[cfg_attr(miri, ignore)] 1206 fn decommit_batching() -> Result<()> { 1207 for (capacity, batch_size) in [ 1208 // A reasonable batch size. 1209 (10, 5), 1210 // Batch sizes of zero and one should effectively disable batching. 1211 (10, 1), 1212 (10, 0), 1213 // A bigger batch size than capacity, which forces the allocation path 1214 // to flush the decommit queue. 1215 (10, 99), 1216 ] { 1217 let mut pool = crate::small_pool_config(); 1218 pool.total_memories(capacity) 1219 .total_core_instances(capacity) 1220 .decommit_batch_size(batch_size) 1221 .memory_protection_keys(Enabled::No); 1222 let mut config = Config::new(); 1223 config.allocation_strategy(pool); 1224 1225 let engine = Engine::new(&config)?; 1226 let linker = Linker::new(&engine); 1227 let module = Module::new(&engine, "(module (memory 1 1))")?; 1228 1229 // Just make sure that we can instantiate all slots a few times and the 1230 // pooling allocator must be flushing the decommit queue as necessary. 1231 for _ in 0..3 { 1232 let mut store = Store::new(&engine, ()); 1233 for _ in 0..capacity { 1234 linker.instantiate(&mut store, &module)?; 1235 } 1236 } 1237 } 1238 1239 Ok(()) 1240 } 1241 1242 #[test] 1243 fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> { 1244 // Configure the pooling allocator to have no access to virtual memory, e.g. 1245 // no table elements but a single table. This should technically support a 1246 // single empty table being allocated into it but virtual memory isn't 1247 // actually allocated here. 1248 let mut cfg = PoolingAllocationConfig::default(); 1249 cfg.table_elements(0); 1250 cfg.total_memories(0); 1251 cfg.total_tables(1); 1252 cfg.total_stacks(0); 1253 cfg.total_core_instances(1); 1254 cfg.max_memory_size(0); 1255 1256 let mut c = Config::new(); 1257 c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1258 1259 // Disable lazy init to actually try to get this to do something interesting 1260 // at runtime. 1261 c.table_lazy_init(false); 1262 1263 let engine = Engine::new(&c)?; 1264 1265 // This module has a single empty table, with a single empty element 1266 // segment. Nothing actually goes wrong here, it should instantiate 1267 // successfully. Along the way though the empty mmap above will get viewed 1268 // as an array-of-pointers, so everything internally should all line up to 1269 // work ok. 1270 let module = Module::new( 1271 &engine, 1272 r#" 1273 (module 1274 (table 0 funcref) 1275 (elem (i32.const 0) func) 1276 ) 1277 "#, 1278 )?; 1279 let mut store = Store::new(&engine, ()); 1280 Instance::new(&mut store, &module, &[])?; 1281 Ok(()) 1282 } 1283 1284 #[test] 1285 #[cfg_attr(miri, ignore)] 1286 fn shared_memory_unsupported() -> Result<()> { 1287 // Skip this test on platforms that don't support threads. 1288 if crate::threads::engine().is_none() { 1289 return Ok(()); 1290 } 1291 let mut config = Config::new(); 1292 let mut cfg = PoolingAllocationConfig::default(); 1293 // shrink the size of this allocator 1294 cfg.total_memories(1); 1295 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1296 let engine = Engine::new(&config)?; 1297 1298 let err = Module::new( 1299 &engine, 1300 r#" 1301 (module 1302 (memory 5 5 shared) 1303 ) 1304 "#, 1305 ) 1306 .unwrap_err(); 1307 err.assert_contains( 1308 "memory is shared which is not supported \ 1309 in the pooling allocator", 1310 ); 1311 err.assert_contains("memory index 0"); 1312 Ok(()) 1313 } 1314 1315 #[test] 1316 #[cfg_attr(miri, ignore)] 1317 fn custom_page_sizes_reusing_same_slot() -> Result<()> { 1318 let mut config = Config::new(); 1319 config.wasm_custom_page_sizes(true); 1320 let mut cfg = crate::small_pool_config(); 1321 // force the memories below to collide in the same memory slot 1322 cfg.total_memories(1); 1323 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1324 let engine = Engine::new(&config)?; 1325 1326 // Instantiate one module, leaving the slot 5 bytes big (but one page 1327 // accessible) 1328 { 1329 let m1 = Module::new( 1330 &engine, 1331 r#" 1332 (module 1333 (memory 5 (pagesize 1)) 1334 1335 (data (i32.const 0) "a") 1336 ) 1337 "#, 1338 )?; 1339 let mut store = Store::new(&engine, ()); 1340 Instance::new(&mut store, &m1, &[])?; 1341 } 1342 1343 // Instantiate a second module, which should work 1344 { 1345 let m2 = Module::new( 1346 &engine, 1347 r#" 1348 (module 1349 (memory 6 (pagesize 1)) 1350 1351 (data (i32.const 0) "a") 1352 ) 1353 "#, 1354 )?; 1355 let mut store = Store::new(&engine, ()); 1356 Instance::new(&mut store, &m2, &[])?; 1357 } 1358 Ok(()) 1359 } 1360 1361 #[test] 1362 #[cfg_attr(miri, ignore)] 1363 fn instantiate_non_page_aligned_sizes() -> Result<()> { 1364 let mut config = Config::new(); 1365 config.wasm_custom_page_sizes(true); 1366 let mut cfg = crate::small_pool_config(); 1367 cfg.total_memories(1); 1368 cfg.max_memory_size(761927); 1369 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1370 let engine = Engine::new(&config)?; 1371 1372 let module = Module::new( 1373 &engine, 1374 r#" 1375 (module 1376 (memory 761927 761927 (pagesize 0x1)) 1377 ) 1378 "#, 1379 )?; 1380 let mut store = Store::new(&engine, ()); 1381 Instance::new(&mut store, &module, &[])?; 1382 Ok(()) 1383 } 1384 1385 #[test] 1386 #[cfg_attr(miri, ignore)] 1387 fn pagemap_scan_enabled_or_disabled() -> Result<()> { 1388 let mut config = Config::new(); 1389 let mut cfg = crate::small_pool_config(); 1390 cfg.total_memories(1); 1391 cfg.pagemap_scan(Enabled::Yes); 1392 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1393 let result = Engine::new(&config); 1394 1395 if PoolingAllocationConfig::is_pagemap_scan_available() { 1396 assert!(result.is_ok()); 1397 } else { 1398 assert!(result.is_err()); 1399 } 1400 Ok(()) 1401 } 1402 1403 // This test instantiates a memory with an image into a slot in the pooling 1404 // allocator in a way that maps the image into the allocator but fails 1405 // instantiation. Failure here is injected with `ResourceLimiter`. Afterwards 1406 // instantiation is allowed to succeed with a memory that has no image, and this 1407 // asserts that the previous image is indeed not available any more as that 1408 // would otherwise mean data was leaked between modules. 1409 #[test] 1410 fn memory_reset_if_instantiation_fails() -> Result<()> { 1411 struct Limiter; 1412 1413 impl ResourceLimiter for Limiter { 1414 fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 1415 Ok(false) 1416 } 1417 1418 fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> { 1419 Ok(false) 1420 } 1421 } 1422 1423 let pool = crate::small_pool_config(); 1424 let mut config = Config::new(); 1425 config.allocation_strategy(pool); 1426 let engine = Engine::new(&config)?; 1427 1428 let module_with_image = Module::new( 1429 &engine, 1430 r#" 1431 (module 1432 (memory 1) 1433 (data (i32.const 0) "\aa") 1434 ) 1435 "#, 1436 )?; 1437 let module_without_image = Module::new( 1438 &engine, 1439 r#" 1440 (module 1441 (memory (export "m") 1) 1442 ) 1443 "#, 1444 )?; 1445 1446 let mut store = Store::new(&engine, Limiter); 1447 store.limiter(|s| s); 1448 assert!(Instance::new(&mut store, &module_with_image, &[]).is_err()); 1449 drop(store); 1450 1451 let mut store = Store::new(&engine, Limiter); 1452 let instance = Instance::new(&mut store, &module_without_image, &[])?; 1453 let mem = instance.get_memory(&mut store, "m").unwrap(); 1454 let data = mem.data(&store); 1455 assert_eq!(data[0], 0); 1456 1457 Ok(()) 1458 } 1459