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