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 * 71.43% - 240 bytes - instance state management 643 * 26.19% - 88 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 * 60.00% - 144 bytes - instance state management 651 * 36.67% - 88 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 * 12.40% - 240 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 * 7.83% - 144 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 fn component_instance_size_limit() -> Result<()> { 872 let mut pool = crate::small_pool_config(); 873 pool.max_component_instance_size(1); 874 let mut config = Config::new(); 875 config.wasm_component_model(true); 876 config.allocation_strategy(pool); 877 let engine = Engine::new(&config)?; 878 879 match wasmtime::component::Component::new(&engine, "(component)") { 880 Ok(_) => panic!("should have hit limit"), 881 Err(e) => assert_eq!( 882 e.to_string(), 883 "instance allocation for this component requires 64 bytes of `VMComponentContext` space \ 884 which exceeds the configured maximum of 1 bytes" 885 ), 886 } 887 888 Ok(()) 889 } 890 891 #[test] 892 #[cfg_attr(miri, ignore)] 893 fn total_tables_limit() -> Result<()> { 894 const TOTAL_TABLES: u32 = 5; 895 896 let mut pool = crate::small_pool_config(); 897 pool.total_tables(TOTAL_TABLES) 898 .total_core_instances(TOTAL_TABLES + 1); 899 let mut config = Config::new(); 900 config.allocation_strategy(pool); 901 902 let engine = Engine::new(&config)?; 903 let linker = Linker::new(&engine); 904 let module = Module::new(&engine, "(module (table 0 1 funcref))")?; 905 906 let mut store = Store::new(&engine, ()); 907 for _ in 0..TOTAL_TABLES { 908 linker.instantiate(&mut store, &module)?; 909 } 910 911 match linker.instantiate(&mut store, &module) { 912 Ok(_) => panic!("should have hit table limit"), 913 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 914 } 915 916 drop(store); 917 let mut store = Store::new(&engine, ()); 918 for _ in 0..TOTAL_TABLES { 919 linker.instantiate(&mut store, &module)?; 920 } 921 922 Ok(()) 923 } 924 925 #[tokio::test] 926 #[cfg(not(miri))] 927 async fn total_stacks_limit() -> Result<()> { 928 use super::async_functions::PollOnce; 929 930 const TOTAL_STACKS: u32 = 2; 931 932 let mut pool = crate::small_pool_config(); 933 pool.total_stacks(TOTAL_STACKS) 934 .total_core_instances(TOTAL_STACKS + 1); 935 let mut config = Config::new(); 936 config.async_support(true); 937 config.allocation_strategy(pool); 938 939 let engine = Engine::new(&config)?; 940 941 let mut linker = Linker::new(&engine); 942 linker.func_new_async( 943 "async", 944 "yield", 945 FuncType::new(&engine, [], []), 946 |_caller, _params, _results| { 947 Box::new(async { 948 tokio::task::yield_now().await; 949 Ok(()) 950 }) 951 }, 952 )?; 953 954 let module = Module::new( 955 &engine, 956 r#" 957 (module 958 (import "async" "yield" (func $yield)) 959 (func (export "run") 960 call $yield 961 ) 962 ) 963 "#, 964 )?; 965 966 // Allocate stacks up to the limit. (Poll the futures once to make sure we 967 // actually enter Wasm and force a stack allocation.) 968 969 let mut store1 = Store::new(&engine, ()); 970 let instance1 = linker.instantiate_async(&mut store1, &module).await?; 971 let run1 = instance1.get_func(&mut store1, "run").unwrap(); 972 let future1 = PollOnce::new(Box::pin(run1.call_async(store1, &[], &mut []))) 973 .await 974 .unwrap_err(); 975 976 let mut store2 = Store::new(&engine, ()); 977 let instance2 = linker.instantiate_async(&mut store2, &module).await?; 978 let run2 = instance2.get_func(&mut store2, "run").unwrap(); 979 let future2 = PollOnce::new(Box::pin(run2.call_async(store2, &[], &mut []))) 980 .await 981 .unwrap_err(); 982 983 // Allocating more should fail. 984 let mut store3 = Store::new(&engine, ()); 985 match linker.instantiate_async(&mut store3, &module).await { 986 Ok(_) => panic!("should have hit stack limit"), 987 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 988 } 989 990 // Finish the futures and return their Wasm stacks to the pool. 991 future1.await?; 992 future2.await?; 993 994 // Should be able to allocate new stacks again. 995 let mut store1 = Store::new(&engine, ()); 996 let instance1 = linker.instantiate_async(&mut store1, &module).await?; 997 let run1 = instance1.get_func(&mut store1, "run").unwrap(); 998 let future1 = run1.call_async(store1, &[], &mut []); 999 1000 let mut store2 = Store::new(&engine, ()); 1001 let instance2 = linker.instantiate_async(&mut store2, &module).await?; 1002 let run2 = instance2.get_func(&mut store2, "run").unwrap(); 1003 let future2 = run2.call_async(store2, &[], &mut []); 1004 1005 future1.await?; 1006 future2.await?; 1007 1008 Ok(()) 1009 } 1010 1011 #[test] 1012 #[cfg(feature = "component-model")] 1013 fn component_core_instances_limit() -> Result<()> { 1014 let mut pool = crate::small_pool_config(); 1015 pool.max_core_instances_per_component(1); 1016 let mut config = Config::new(); 1017 config.wasm_component_model(true); 1018 config.allocation_strategy(pool); 1019 let engine = Engine::new(&config)?; 1020 1021 // One core instance works. 1022 wasmtime::component::Component::new( 1023 &engine, 1024 r#" 1025 (component 1026 (core module $m) 1027 (core instance $a (instantiate $m)) 1028 ) 1029 "#, 1030 )?; 1031 1032 // Two core instances doesn't. 1033 match wasmtime::component::Component::new( 1034 &engine, 1035 r#" 1036 (component 1037 (core module $m) 1038 (core instance $a (instantiate $m)) 1039 (core instance $b (instantiate $m)) 1040 ) 1041 "#, 1042 ) { 1043 Ok(_) => panic!("should have hit limit"), 1044 Err(e) => assert_eq!( 1045 e.to_string(), 1046 "The component transitively contains 2 core module instances, which exceeds the \ 1047 configured maximum of 1" 1048 ), 1049 } 1050 1051 Ok(()) 1052 } 1053 1054 #[test] 1055 #[cfg(feature = "component-model")] 1056 fn component_memories_limit() -> Result<()> { 1057 let mut pool = crate::small_pool_config(); 1058 pool.max_memories_per_component(1).total_memories(2); 1059 let mut config = Config::new(); 1060 config.wasm_component_model(true); 1061 config.allocation_strategy(pool); 1062 let engine = Engine::new(&config)?; 1063 1064 // One memory works. 1065 wasmtime::component::Component::new( 1066 &engine, 1067 r#" 1068 (component 1069 (core module $m (memory 1 1)) 1070 (core instance $a (instantiate $m)) 1071 ) 1072 "#, 1073 )?; 1074 1075 // Two memories doesn't. 1076 match wasmtime::component::Component::new( 1077 &engine, 1078 r#" 1079 (component 1080 (core module $m (memory 1 1)) 1081 (core instance $a (instantiate $m)) 1082 (core instance $b (instantiate $m)) 1083 ) 1084 "#, 1085 ) { 1086 Ok(_) => panic!("should have hit limit"), 1087 Err(e) => assert_eq!( 1088 e.to_string(), 1089 "The component transitively contains 2 Wasm linear memories, which exceeds the \ 1090 configured maximum of 1" 1091 ), 1092 } 1093 1094 Ok(()) 1095 } 1096 1097 #[test] 1098 #[cfg(feature = "component-model")] 1099 fn component_tables_limit() -> Result<()> { 1100 let mut pool = crate::small_pool_config(); 1101 pool.max_tables_per_component(1).total_tables(2); 1102 let mut config = Config::new(); 1103 config.wasm_component_model(true); 1104 config.allocation_strategy(pool); 1105 let engine = Engine::new(&config)?; 1106 1107 // One table works. 1108 wasmtime::component::Component::new( 1109 &engine, 1110 r#" 1111 (component 1112 (core module $m (table 1 1 funcref)) 1113 (core instance $a (instantiate $m)) 1114 ) 1115 "#, 1116 )?; 1117 1118 // Two tables doesn't. 1119 match wasmtime::component::Component::new( 1120 &engine, 1121 r#" 1122 (component 1123 (core module $m (table 1 1 funcref)) 1124 (core instance $a (instantiate $m)) 1125 (core instance $b (instantiate $m)) 1126 ) 1127 "#, 1128 ) { 1129 Ok(_) => panic!("should have hit limit"), 1130 Err(e) => assert_eq!( 1131 e.to_string(), 1132 "The component transitively contains 2 tables, which exceeds the \ 1133 configured maximum of 1" 1134 ), 1135 } 1136 1137 Ok(()) 1138 } 1139 1140 #[test] 1141 #[cfg_attr(miri, ignore)] 1142 fn total_memories_limit() -> Result<()> { 1143 const TOTAL_MEMORIES: u32 = 5; 1144 1145 let mut pool = crate::small_pool_config(); 1146 pool.total_memories(TOTAL_MEMORIES) 1147 .total_core_instances(TOTAL_MEMORIES + 1) 1148 .memory_protection_keys(MpkEnabled::Disable); 1149 let mut config = Config::new(); 1150 config.allocation_strategy(pool); 1151 1152 let engine = Engine::new(&config)?; 1153 let linker = Linker::new(&engine); 1154 let module = Module::new(&engine, "(module (memory 1 1))")?; 1155 1156 let mut store = Store::new(&engine, ()); 1157 for _ in 0..TOTAL_MEMORIES { 1158 linker.instantiate(&mut store, &module)?; 1159 } 1160 1161 match linker.instantiate(&mut store, &module) { 1162 Ok(_) => panic!("should have hit memory limit"), 1163 Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()), 1164 } 1165 1166 drop(store); 1167 let mut store = Store::new(&engine, ()); 1168 for _ in 0..TOTAL_MEMORIES { 1169 linker.instantiate(&mut store, &module)?; 1170 } 1171 1172 Ok(()) 1173 } 1174 1175 #[test] 1176 #[cfg_attr(miri, ignore)] 1177 fn decommit_batching() -> Result<()> { 1178 for (capacity, batch_size) in [ 1179 // A reasonable batch size. 1180 (10, 5), 1181 // Batch sizes of zero and one should effectively disable batching. 1182 (10, 1), 1183 (10, 0), 1184 // A bigger batch size than capacity, which forces the allocation path 1185 // to flush the decommit queue. 1186 (10, 99), 1187 ] { 1188 let mut pool = crate::small_pool_config(); 1189 pool.total_memories(capacity) 1190 .total_core_instances(capacity) 1191 .decommit_batch_size(batch_size) 1192 .memory_protection_keys(MpkEnabled::Disable); 1193 let mut config = Config::new(); 1194 config.allocation_strategy(pool); 1195 1196 let engine = Engine::new(&config)?; 1197 let linker = Linker::new(&engine); 1198 let module = Module::new(&engine, "(module (memory 1 1))")?; 1199 1200 // Just make sure that we can instantiate all slots a few times and the 1201 // pooling allocator must be flushing the decommit queue as necessary. 1202 for _ in 0..3 { 1203 let mut store = Store::new(&engine, ()); 1204 for _ in 0..capacity { 1205 linker.instantiate(&mut store, &module)?; 1206 } 1207 } 1208 } 1209 1210 Ok(()) 1211 } 1212 1213 #[test] 1214 fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> { 1215 // Configure the pooling allocator to have no access to virtual memory, e.g. 1216 // no table elements but a single table. This should technically support a 1217 // single empty table being allocated into it but virtual memory isn't 1218 // actually allocated here. 1219 let mut cfg = PoolingAllocationConfig::default(); 1220 cfg.table_elements(0); 1221 cfg.total_memories(0); 1222 cfg.total_tables(1); 1223 cfg.total_stacks(0); 1224 cfg.total_core_instances(1); 1225 cfg.max_memory_size(0); 1226 1227 let mut c = Config::new(); 1228 c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1229 1230 // Disable lazy init to actually try to get this to do something interesting 1231 // at runtime. 1232 c.table_lazy_init(false); 1233 1234 let engine = Engine::new(&c)?; 1235 1236 // This module has a single empty table, with a single empty element 1237 // segment. Nothing actually goes wrong here, it should instantiate 1238 // successfully. Along the way though the empty mmap above will get viewed 1239 // as an array-of-pointers, so everything internally should all line up to 1240 // work ok. 1241 let module = Module::new( 1242 &engine, 1243 r#" 1244 (module 1245 (table 0 funcref) 1246 (elem (i32.const 0) func) 1247 ) 1248 "#, 1249 )?; 1250 let mut store = Store::new(&engine, ()); 1251 Instance::new(&mut store, &module, &[])?; 1252 Ok(()) 1253 } 1254 1255 #[test] 1256 #[cfg_attr(miri, ignore)] 1257 fn shared_memory_unsupported() -> Result<()> { 1258 let mut config = Config::new(); 1259 let mut cfg = PoolingAllocationConfig::default(); 1260 // shrink the size of this allocator 1261 cfg.total_memories(1); 1262 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1263 let engine = Engine::new(&config)?; 1264 1265 let err = Module::new( 1266 &engine, 1267 r#" 1268 (module 1269 (memory 5 5 shared) 1270 ) 1271 "#, 1272 ) 1273 .unwrap_err(); 1274 let err = err.to_string(); 1275 assert!( 1276 err.contains( 1277 "memory index 0 is shared which is not supported \ 1278 in the pooling allocator" 1279 ), 1280 "bad error: {err}" 1281 ); 1282 Ok(()) 1283 } 1284 1285 #[test] 1286 #[cfg_attr(miri, ignore)] 1287 fn custom_page_sizes_reusing_same_slot() -> Result<()> { 1288 let mut config = Config::new(); 1289 config.wasm_custom_page_sizes(true); 1290 let mut cfg = PoolingAllocationConfig::default(); 1291 // force the memories below to collide in the same memory slot 1292 cfg.total_memories(1); 1293 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1294 let engine = Engine::new(&config)?; 1295 1296 // Instantiate one module, leaving the slot 5 bytes big (but one page 1297 // accessible) 1298 { 1299 let m1 = Module::new( 1300 &engine, 1301 r#" 1302 (module 1303 (memory 5 (pagesize 1)) 1304 1305 (data (i32.const 0) "a") 1306 ) 1307 "#, 1308 )?; 1309 let mut store = Store::new(&engine, ()); 1310 Instance::new(&mut store, &m1, &[])?; 1311 } 1312 1313 // Instantiate a second module, which should work 1314 { 1315 let m2 = Module::new( 1316 &engine, 1317 r#" 1318 (module 1319 (memory 6 (pagesize 1)) 1320 1321 (data (i32.const 0) "a") 1322 ) 1323 "#, 1324 )?; 1325 let mut store = Store::new(&engine, ()); 1326 Instance::new(&mut store, &m2, &[])?; 1327 } 1328 Ok(()) 1329 } 1330 1331 #[test] 1332 #[cfg_attr(miri, ignore)] 1333 fn instantiate_non_page_aligned_sizes() -> Result<()> { 1334 let mut config = Config::new(); 1335 config.wasm_custom_page_sizes(true); 1336 let mut cfg = PoolingAllocationConfig::default(); 1337 cfg.total_memories(1); 1338 cfg.max_memory_size(761927); 1339 config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg)); 1340 let engine = Engine::new(&config)?; 1341 1342 let module = Module::new( 1343 &engine, 1344 r#" 1345 (module 1346 (memory 761927 761927 (pagesize 0x1)) 1347 ) 1348 "#, 1349 )?; 1350 let mut store = Store::new(&engine, ()); 1351 Instance::new(&mut store, &module, &[])?; 1352 Ok(()) 1353 } 1354