test: Cycle 18 — div-by-zero trap, extra exports, zero-addr send, operation sequence fuzz
Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
d92e2a5499
commit
89a22f4b5f
1 changed files with 155 additions and 0 deletions
|
|
@ -3184,3 +3184,158 @@ fn two_watchers_both_notified_when_wasm_actor_dies() {
|
||||||
assert_eq!(count_a.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher A should be notified");
|
assert_eq!(count_a.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher A should be notified");
|
||||||
assert_eq!(count_b.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher B should be notified");
|
assert_eq!(count_b.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher B should be notified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Division by zero: WASM trap, actor survives ─────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn guest_division_by_zero_traps_actor_survives() {
|
||||||
|
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)
|
||||||
|
;; Division by zero is a trap in WASM
|
||||||
|
local.get $len
|
||||||
|
i32.const 0
|
||||||
|
i32.div_u
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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(); // div by zero trap
|
||||||
|
|
||||||
|
// Actor survives
|
||||||
|
rt.send_to(addr, ByteMessage(vec![2])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Module with extra exports: globals and extra functions ──────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_with_extra_exports_builds_and_works() {
|
||||||
|
// Module exports extra globals and functions beyond the required ones.
|
||||||
|
// Builder should ignore them.
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(global (export "version") i32 (i32.const 42))
|
||||||
|
(global (export "magic") i64 (i64.const 12345))
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||||
|
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||||
|
(i32.store8 (i32.const 200) (i32.const 7))
|
||||||
|
local.get $ptr
|
||||||
|
i32.const 200
|
||||||
|
i32.const 1
|
||||||
|
call $send
|
||||||
|
)
|
||||||
|
(func (export "extra_func") (param i32) (result i32)
|
||||||
|
local.get 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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"extras")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let received = inbox.try_recv().expect("module with extras should work");
|
||||||
|
assert_eq!(received.0, vec![7]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Send with dest_ptr=0, all zeroes in memory: valid but unroutable ────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_with_all_zero_dest_from_uninitialized_memory() {
|
||||||
|
// Guest reads dest address from offset 500 (uninitialized, all zeros).
|
||||||
|
// The zero address isn't routable. ctx.send fails silently.
|
||||||
|
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)
|
||||||
|
;; Read dest from uninitialized region (offset 500, all zeros)
|
||||||
|
i32.const 500 ;; dest_ptr
|
||||||
|
i32.const 32 ;; payload_ptr
|
||||||
|
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 addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap();
|
||||||
|
rt.tick(); // send to zero-address fails silently
|
||||||
|
|
||||||
|
// Actor survives
|
||||||
|
rt.send_to(addr, ByteMessage(vec![4])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Property: WASM actor survives any sequence of operations ────────────────
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_actor_survives_any_operation_sequence(
|
||||||
|
ops in proptest::collection::vec(
|
||||||
|
prop_oneof![
|
||||||
|
Just("send"),
|
||||||
|
Just("empty"),
|
||||||
|
Just("large"),
|
||||||
|
],
|
||||||
|
1..20
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let actor = WasmActorBuilder::new(engine, guest_wasm("echo"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
let addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
for op in &ops {
|
||||||
|
match *op {
|
||||||
|
"send" => {
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), b"msg")).unwrap();
|
||||||
|
}
|
||||||
|
"empty" => {
|
||||||
|
rt.send_to(addr, ByteMessage(vec![])).unwrap();
|
||||||
|
}
|
||||||
|
"large" => {
|
||||||
|
rt.send_to(addr, ByteMessage(vec![0u8; 60000])).unwrap();
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
rt.tick();
|
||||||
|
// Drain inbox
|
||||||
|
while inbox.try_recv().is_some() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actor should still be alive
|
||||||
|
let result = rt.send_to(addr, ByteMessage(vec![99]));
|
||||||
|
prop_assert!(result.is_ok(), "actor must survive any operation sequence");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue