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:
Claude 2026-02-13 08:59:12 +00:00
parent 8ae620ebbb
commit 1387bce562

View file

@ -1736,3 +1736,156 @@ fn ten_wasm_actors_chain_relay() {
let received = inbox.try_recv().expect("10-actor chain should deliver");
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}");
}
}