test: Cycle 9 — send overflow/boundary, cross-thread relay, property fuzz all send args
Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
8ae620ebbb
commit
1387bce562
1 changed files with 153 additions and 0 deletions
|
|
@ -1736,3 +1736,156 @@ fn ten_wasm_actors_chain_relay() {
|
||||||
let received = inbox.try_recv().expect("10-actor chain should deliver");
|
let received = inbox.try_recv().expect("10-actor chain should deliver");
|
||||||
assert_eq!(received.0, final_payload);
|
assert_eq!(received.0, final_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── send payload overflow: payload_ptr + payload_len wraps ──────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_with_payload_range_overflow_traps_actor_survives() {
|
||||||
|
// Guest calls send with payload_ptr=1, payload_len=i32::MAX.
|
||||||
|
// checked_add(payload_len) overflows → trap. Actor must survive.
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||||
|
(func (export "handle") (param i32 i32)
|
||||||
|
i32.const 0 ;; dest_ptr (valid)
|
||||||
|
i32.const 1 ;; payload_ptr
|
||||||
|
i32.const 2147483647 ;; payload_len = i32::MAX → overflow
|
||||||
|
call $send
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
let wasm = wat::parse_str(wat).unwrap();
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
rt.send_to(addr, ByteMessage(vec![1])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
// Actor survives — send another
|
||||||
|
rt.send_to(addr, ByteMessage(vec![2])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── send with payload at exact memory end ───────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_payload_at_exact_memory_end_works() {
|
||||||
|
// Guest writes a byte at offset 65535 (last byte of 1-page memory) and
|
||||||
|
// sends it as a 1-byte payload. payload_end = 65535 + 1 = 65536 == mem_len.
|
||||||
|
// This should succeed (not exceed bounds).
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||||
|
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||||
|
;; Write marker at last byte
|
||||||
|
(i32.store8 (i32.const 65535) (i32.const 88))
|
||||||
|
;; Send: dest from message, payload = last byte of memory
|
||||||
|
local.get $ptr ;; dest_ptr
|
||||||
|
i32.const 65535 ;; payload_ptr (last byte)
|
||||||
|
i32.const 1 ;; payload_len
|
||||||
|
call $send
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
let wasm = wat::parse_str(wat).unwrap();
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
let addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), b"x")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let received = inbox.try_recv().expect("exact-end payload should succeed");
|
||||||
|
assert_eq!(received.0, vec![88]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Multi-worker: two WASM actors cross-thread messaging ────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasm_actors_communicate_across_threads() {
|
||||||
|
// Two WASM echo actors on a 2-worker runtime. Actor A echoes to Actor B,
|
||||||
|
// Actor B echoes to inbox. Verifies cross-thread WASM messaging.
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let wasm_bytes = guest_wasm("echo");
|
||||||
|
|
||||||
|
let actor_a = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone()).build().unwrap();
|
||||||
|
let actor_b = WasmActorBuilder::new(engine, wasm_bytes).build().unwrap();
|
||||||
|
|
||||||
|
let config = RuntimeConfig {
|
||||||
|
num_threads: 2,
|
||||||
|
..RuntimeConfig::default()
|
||||||
|
};
|
||||||
|
let rt = Runtime::new(config);
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
|
||||||
|
let addr_a = rt.spawn(actor_a).unwrap();
|
||||||
|
let addr_b = rt.spawn(actor_b).unwrap();
|
||||||
|
|
||||||
|
// A receives [addr_b | [inbox_addr | "cross-thread"]]
|
||||||
|
// A echoes [inbox_addr | "cross-thread"] to B
|
||||||
|
// B echoes "cross-thread" to inbox
|
||||||
|
let final_payload = b"cross-thread";
|
||||||
|
let msg_for_b = framed_msg(inbox.addr(), final_payload);
|
||||||
|
let msg_for_a = framed_msg(&addr_b, &msg_for_b.0);
|
||||||
|
|
||||||
|
rt.send_to(addr_a, msg_for_a).unwrap();
|
||||||
|
|
||||||
|
let handle = rt.run().unwrap();
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
|
||||||
|
let received = inbox.try_recv().expect("cross-thread relay should deliver");
|
||||||
|
assert_eq!(received.0, final_payload);
|
||||||
|
|
||||||
|
handle.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Property: any send arguments never crash the host ────────────────────────
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_any_send_args_never_crash_host(
|
||||||
|
dest_ptr in -100i32..70000,
|
||||||
|
payload_ptr in -100i32..70000,
|
||||||
|
payload_len in -100i32..70000,
|
||||||
|
) {
|
||||||
|
// Regardless of what arguments the guest passes to swactor.send,
|
||||||
|
// the host import should either succeed or trap — never panic.
|
||||||
|
let wat = format!(r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||||
|
(func (export "handle") (param i32 i32)
|
||||||
|
i32.const {dest_ptr}
|
||||||
|
i32.const {payload_ptr}
|
||||||
|
i32.const {payload_len}
|
||||||
|
call $send
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
let wasm = wat::parse_str(&wat).unwrap();
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
// Should never panic regardless of send args
|
||||||
|
rt.send_to(addr, ByteMessage(vec![0u8; 64])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
// Actor should still accept messages (not poisoned)
|
||||||
|
let result = rt.send_to(addr, ByteMessage(vec![1]));
|
||||||
|
prop_assert!(result.is_ok(), "actor must survive any send args: dest={dest_ptr} payload_ptr={payload_ptr} len={payload_len}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue