From 1a782cbb5c3575a8519b0a1aa9fcb83ed189fb73 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 20 Feb 2026 17:30:14 +0000 Subject: [PATCH] pr9.3 --- crates/console/prometeu-vm/src/verifier.rs | 125 +++++++ .../prometeu-vm/tests/verifier_closures.rs | 132 -------- .../prometeu-vm/tests/verifier_coroutines.rs | 56 ---- .../prometeu-vm/tests/verifier_golden.rs | 317 ------------------ files/TODOs.md | 42 --- 5 files changed, 125 insertions(+), 547 deletions(-) delete mode 100644 crates/console/prometeu-vm/tests/verifier_closures.rs delete mode 100644 crates/console/prometeu-vm/tests/verifier_coroutines.rs delete mode 100644 crates/console/prometeu-vm/tests/verifier_golden.rs diff --git a/crates/console/prometeu-vm/src/verifier.rs b/crates/console/prometeu-vm/src/verifier.rs index 73480dbc..22ab16c4 100644 --- a/crates/console/prometeu-vm/src/verifier.rs +++ b/crates/console/prometeu-vm/src/verifier.rs @@ -598,6 +598,131 @@ mod golden_ext { let res = Verifier::verify(&code, &functions); assert!(matches!(res, Err(VerifierError::NotAClosureOnCallClosure { .. }))); } + + #[test] + fn closure_call_wrong_argc_fails() { + // Same as valid case but argc = 0 while callee expects 1 user arg + let mut code = Vec::new(); + // F0 @ 0 + code.push(OpCode::PushI32 as u8); code.push(0x00); + code.extend_from_slice(&7u32.to_le_bytes()); + code.push(OpCode::MakeClosure as u8); code.push(0x00); + code.extend_from_slice(&1u32.to_le_bytes()); // fn id + code.extend_from_slice(&0u32.to_le_bytes()); // cap count + code.push(OpCode::CallClosure as u8); code.push(0x00); + code.extend_from_slice(&0u32.to_le_bytes()); // argc = 0 (mismatch) + code.push(OpCode::Ret as u8); code.push(0x00); + + let f0_len = code.len() as u32; + + // F1 @ f0_len + code.push(OpCode::PushI32 as u8); code.push(0x00); + code.extend_from_slice(&1u32.to_le_bytes()); + code.push(OpCode::Ret as u8); code.push(0x00); + let f1_len = (code.len() as u32) - f0_len; + + let functions = vec![ + FunctionMeta { code_offset: 0, code_len: f0_len, return_slots: 0, ..Default::default() }, + FunctionMeta { code_offset: f0_len, code_len: f1_len, param_slots: 2, return_slots: 1, ..Default::default() }, + ]; + + let res = Verifier::verify(&code, &functions); + assert!(matches!(res, Err(VerifierError::BadClosureArgCount { .. }))); + } + + #[test] + fn nested_closure_calls_verify() { + // F0: MakeClosure(fn=1,0); CallClosure argc=0; PopN 1; Ret + // F1: MakeClosure(fn=2,0); CallClosure argc=0; Ret (param=1 hidden, ret=1) + // F2: PushI32 5; Ret (param=1 hidden, ret=1) + let mut code = Vec::new(); + // F0 @ 0 + code.push(OpCode::MakeClosure as u8); code.push(0x00); + code.extend_from_slice(&1u32.to_le_bytes()); // F1 + code.extend_from_slice(&0u32.to_le_bytes()); // cap=0 + code.push(OpCode::CallClosure as u8); code.push(0x00); + code.extend_from_slice(&0u32.to_le_bytes()); // argc=0 + code.push(OpCode::PopN as u8); code.push(0x00); + code.extend_from_slice(&1u32.to_le_bytes()); + code.push(OpCode::Ret as u8); code.push(0x00); + let f0_len = code.len() as u32; + + // F1 @ f0_len + code.push(OpCode::MakeClosure as u8); code.push(0x00); + code.extend_from_slice(&2u32.to_le_bytes()); // F2 + code.extend_from_slice(&0u32.to_le_bytes()); // cap=0 + code.push(OpCode::CallClosure as u8); code.push(0x00); + code.extend_from_slice(&0u32.to_le_bytes()); // argc=0 + code.push(OpCode::Ret as u8); code.push(0x00); + let f1_len = (code.len() as u32) - f0_len; + + // F2 @ f0_len + f1_len + code.push(OpCode::PushI32 as u8); code.push(0x00); + code.extend_from_slice(&5u32.to_le_bytes()); + code.push(OpCode::Ret as u8); code.push(0x00); + let f2_len = (code.len() as u32) - f0_len - f1_len; + + let functions = vec![ + FunctionMeta { code_offset: 0, code_len: f0_len, return_slots: 0, ..Default::default() }, + FunctionMeta { code_offset: f0_len, code_len: f1_len, param_slots: 1, return_slots: 1, ..Default::default() }, + FunctionMeta { code_offset: f0_len + f1_len, code_len: f2_len, param_slots: 1, return_slots: 1, ..Default::default() }, + ]; + + let res = Verifier::verify(&code, &functions).unwrap(); + assert!(res[0] >= 1); + } + + // --- Coroutines subset ---------------------------------------------------------------- + + #[test] + fn err_invalid_yield_with_non_empty_stack() { + // Program: + // 0..2: PUSH_I32 1 (imm 4 bytes) + // 6..8: YIELD + // 8..10: RET + let mut code = Vec::new(); + code.extend_from_slice(&enc_op(OpCode::PushI32)); + code.extend_from_slice(&1u32.to_le_bytes()); + code.extend_from_slice(&enc_op(OpCode::Yield)); + code.extend_from_slice(&enc_op(OpCode::Ret)); + + let functions = vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 1, ..Default::default() }]; + let res = Verifier::verify(&code, &functions); + assert_eq!(res, Err(VerifierError::InvalidYieldContext { pc: 6, height: 1 })); + } + + #[test] + fn err_spawn_arg_mismatch() { + // Caller at func 0: SPAWN fn_id=1, arg_count=1; RET + // Callee at func 1: expects 2 param_slots + let mut code = Vec::new(); + code.extend_from_slice(&enc_op(OpCode::Spawn)); + code.extend_from_slice(&1u32.to_le_bytes()); // fn_id + code.extend_from_slice(&1u32.to_le_bytes()); // arg_count (mismatch: callee expects 2) + code.extend_from_slice(&enc_op(OpCode::Ret)); + + let caller = FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 0, ..Default::default() }; + // Callee has no code here; only signature matters + let callee = FunctionMeta { code_offset: code.len() as u32, code_len: 0, param_slots: 2, return_slots: 0, ..Default::default() }; + let functions = vec![caller, callee]; + + let res = Verifier::verify(&code, &functions); + assert_eq!(res, Err(VerifierError::BadSpawnArgCount { pc: 0, expected: 2, got: 1 })); + } + + #[test] + fn err_sleep_truncated_immediate() { + // Encode SLEEP but provide only 1 of 4 immediate bytes + let mut code = Vec::new(); + code.extend_from_slice(&enc_op(OpCode::Sleep)); + code.push(0xAB); + let functions = vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }]; + let res = Verifier::verify(&code, &functions); + assert_eq!( + res, + Err(VerifierError::TruncatedImmediate { pc: 0, opcode: OpCode::Sleep, need: 4, have: 1 }) + ); + } } #[cfg(test)] diff --git a/crates/console/prometeu-vm/tests/verifier_closures.rs b/crates/console/prometeu-vm/tests/verifier_closures.rs deleted file mode 100644 index 2297978d..00000000 --- a/crates/console/prometeu-vm/tests/verifier_closures.rs +++ /dev/null @@ -1,132 +0,0 @@ -#[cfg(any())] -mod moved { -use prometeu_bytecode::FunctionMeta; -use prometeu_bytecode::isa::core::CoreOpCode as OpCode; -use crate::prometeu_vm::verifier::{Verifier, VerifierError}; - -// Re-export path shim for tests since this file is in integration tests crate scope -mod prometeu_vm { pub use prometeu_vm::*; } - -#[test] -fn closure_call_valid_passes() { - // F0: PushI32 7; MakeClosure(fn=1, cap=0); CallClosure argc=1; PopN 1; Ret - // F1: PushI32 1; Ret (param_slots=2: hidden arg0 + 1 user arg; returns 1) - let mut code = Vec::new(); - // F0 @ 0 - code.push(OpCode::PushI32 as u8); code.push(0x00); - code.extend_from_slice(&7u32.to_le_bytes()); - code.push(OpCode::MakeClosure as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); // fn id - code.extend_from_slice(&0u32.to_le_bytes()); // cap count - code.push(OpCode::CallClosure as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); // argc = 1 (excludes hidden) - code.push(OpCode::PopN as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); - code.push(OpCode::Ret as u8); code.push(0x00); - - let f0_len = code.len() as u32; - - // F1 @ f0_len - code.push(OpCode::PushI32 as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); - code.push(OpCode::Ret as u8); code.push(0x00); - let f1_len = (code.len() as u32) - f0_len; - - let functions = vec![ - FunctionMeta { code_offset: 0, code_len: f0_len, return_slots: 0, ..Default::default() }, - FunctionMeta { code_offset: f0_len, code_len: f1_len, param_slots: 2, return_slots: 1, ..Default::default() }, - ]; - - let res = Verifier::verify(&code, &functions).unwrap(); - // Max stack in F0: PushI32 (1), MakeClosure (2), CallClosure pops 2 pushes 1 => (1), PopN to 0 - assert!(res[0] >= 2); -} - -#[test] -fn closure_call_wrong_argc_fails() { - // Same as previous but argc = 0 while callee expects 1 user arg - let mut code = Vec::new(); - // F0 @ 0 - code.push(OpCode::PushI32 as u8); code.push(0x00); - code.extend_from_slice(&7u32.to_le_bytes()); - code.push(OpCode::MakeClosure as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); // fn id - code.extend_from_slice(&0u32.to_le_bytes()); // cap count - code.push(OpCode::CallClosure as u8); code.push(0x00); - code.extend_from_slice(&0u32.to_le_bytes()); // argc = 0 (mismatch) - code.push(OpCode::Ret as u8); code.push(0x00); - - let f0_len = code.len() as u32; - - // F1 @ f0_len - code.push(OpCode::PushI32 as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); - code.push(OpCode::Ret as u8); code.push(0x00); - let f1_len = (code.len() as u32) - f0_len; - - let functions = vec![ - FunctionMeta { code_offset: 0, code_len: f0_len, return_slots: 0, ..Default::default() }, - FunctionMeta { code_offset: f0_len, code_len: f1_len, param_slots: 2, return_slots: 1, ..Default::default() }, - ]; - - let res = Verifier::verify(&code, &functions); - assert!(matches!(res, Err(VerifierError::BadClosureArgCount { .. }))); -} - -#[test] -fn call_closure_on_non_closure_fails() { - // F0: PushI32 7; CallClosure argc=0; Ret - let mut code = Vec::new(); - code.push(OpCode::PushI32 as u8); code.push(0x00); - code.extend_from_slice(&7u32.to_le_bytes()); - code.push(OpCode::CallClosure as u8); code.push(0x00); - code.extend_from_slice(&0u32.to_le_bytes()); - code.push(OpCode::Ret as u8); code.push(0x00); - - let functions = vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 0, ..Default::default() }]; - let res = Verifier::verify(&code, &functions); - assert!(matches!(res, Err(VerifierError::NotAClosureOnCallClosure { .. }))); -} - -#[test] -fn nested_closure_calls_verify() { - // F0: MakeClosure(fn=1,0); CallClosure argc=0; PopN 1; Ret - // F1: MakeClosure(fn=2,0); CallClosure argc=0; Ret (param=1 hidden, ret=1) - // F2: PushI32 5; Ret (param=1 hidden, ret=1) - let mut code = Vec::new(); - // F0 @ 0 - code.push(OpCode::MakeClosure as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); // F1 - code.extend_from_slice(&0u32.to_le_bytes()); // cap=0 - code.push(OpCode::CallClosure as u8); code.push(0x00); - code.extend_from_slice(&0u32.to_le_bytes()); // argc=0 - code.push(OpCode::PopN as u8); code.push(0x00); - code.extend_from_slice(&1u32.to_le_bytes()); - code.push(OpCode::Ret as u8); code.push(0x00); - let f0_len = code.len() as u32; - - // F1 @ f0_len - code.push(OpCode::MakeClosure as u8); code.push(0x00); - code.extend_from_slice(&2u32.to_le_bytes()); // F2 - code.extend_from_slice(&0u32.to_le_bytes()); // cap=0 - code.push(OpCode::CallClosure as u8); code.push(0x00); - code.extend_from_slice(&0u32.to_le_bytes()); // argc=0 - code.push(OpCode::Ret as u8); code.push(0x00); - let f1_len = (code.len() as u32) - f0_len; - - // F2 @ f0_len + f1_len - code.push(OpCode::PushI32 as u8); code.push(0x00); - code.extend_from_slice(&5u32.to_le_bytes()); - code.push(OpCode::Ret as u8); code.push(0x00); - let f2_len = (code.len() as u32) - f0_len - f1_len; - - let functions = vec![ - FunctionMeta { code_offset: 0, code_len: f0_len, return_slots: 0, ..Default::default() }, - FunctionMeta { code_offset: f0_len, code_len: f1_len, param_slots: 1, return_slots: 1, ..Default::default() }, - FunctionMeta { code_offset: f0_len + f1_len, code_len: f2_len, param_slots: 1, return_slots: 1, ..Default::default() }, - ]; - - let res = Verifier::verify(&code, &functions).unwrap(); - assert!(res[0] >= 1); -} -} diff --git a/crates/console/prometeu-vm/tests/verifier_coroutines.rs b/crates/console/prometeu-vm/tests/verifier_coroutines.rs deleted file mode 100644 index b8852652..00000000 --- a/crates/console/prometeu-vm/tests/verifier_coroutines.rs +++ /dev/null @@ -1,56 +0,0 @@ -#![cfg(FALSE)] -use prometeu_bytecode::FunctionMeta; -use prometeu_bytecode::isa::core::CoreOpCode as OpCode; -use prometeu_vm::verifier::{Verifier, VerifierError}; - -fn enc_op(op: OpCode) -> [u8; 2] { [(op as u8), 0x00] } - -#[test] -fn err_invalid_yield_with_non_empty_stack() { - // Program: - // 0..2: PUSH_I32 1 (imm 4 bytes) - // 6..8: YIELD - // 8..10: RET - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&1u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Yield)); - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 1, ..Default::default() }]; - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::InvalidYieldContext { pc: 6, height: 1 })); -} - -#[test] -fn err_spawn_arg_mismatch() { - // Caller at func 0: SPAWN fn_id=1, arg_count=1; RET - // Callee at func 1: expects 2 param_slots - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Spawn)); - code.extend_from_slice(&1u32.to_le_bytes()); // fn_id - code.extend_from_slice(&1u32.to_le_bytes()); // arg_count (mismatch: callee expects 2) - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let caller = FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 0, ..Default::default() }; - // Callee has no code here; only signature matters - let callee = FunctionMeta { code_offset: code.len() as u32, code_len: 0, param_slots: 2, return_slots: 0, ..Default::default() }; - let functions = vec![caller, callee]; - - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::BadSpawnArgCount { pc: 0, expected: 2, got: 1 })); -} - -#[test] -fn err_sleep_truncated_immediate() { - // Encode SLEEP but provide only 1 of 4 immediate bytes - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Sleep)); - code.push(0xAB); - let functions = vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }]; - let res = Verifier::verify(&code, &functions); - assert_eq!( - res, - Err(VerifierError::TruncatedImmediate { pc: 0, opcode: OpCode::Sleep, need: 4, have: 1 }) - ); -} diff --git a/crates/console/prometeu-vm/tests/verifier_golden.rs b/crates/console/prometeu-vm/tests/verifier_golden.rs deleted file mode 100644 index dfece92c..00000000 --- a/crates/console/prometeu-vm/tests/verifier_golden.rs +++ /dev/null @@ -1,317 +0,0 @@ -#[cfg(any())] -mod moved { -//! Verifier Golden Test Suite -//! -//! This suite exercises a stable set of valid and invalid bytecode samples -//! and asserts either successful verification or specific `VerifierError` -//! kinds. All tests are deterministic and self-contained. - -use prometeu_bytecode::FunctionMeta; -use prometeu_bytecode::isa::core::CoreOpCode as OpCode; -use prometeu_vm::verifier::{Verifier, VerifierError}; - -// --- Small helpers --------------------------------------------------------------------------- - -fn enc_op(op: OpCode) -> [u8; 2] { - // Opcodes are encoded as u16 LE; CoreOpCode fits in u8 so we write [lo, hi=0x00] - [(op as u8), 0x00] -} - -fn func(meta: FunctionMeta) -> Vec { - vec![meta] -} - -// --- Valid Programs -------------------------------------------------------------------------- - -#[test] -fn golden_valid_empty_function() { - // Empty function is allowed by the verifier. - let code = vec![]; - let functions = func(FunctionMeta { code_offset: 0, code_len: 0, ..Default::default() }); - let res = Verifier::verify(&code, &functions).unwrap(); - assert_eq!(res[0], 0); -} - -#[test] -fn golden_valid_simple_arith_and_ret() { - // push 1; push 2; add; ret - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&1u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&2u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Add)); - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 1, ..Default::default() }); - let res = Verifier::verify(&code, &functions).unwrap(); - assert_eq!(res[0], 2); // max stack reaches 2 after two pushes -} - -#[test] -fn golden_valid_jump_to_end() { - // Single-instr function that jumps to exactly end (allowed terminator path) - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Jmp)); - code.extend_from_slice(&6u32.to_le_bytes()); - - let functions = func(FunctionMeta { code_offset: 0, code_len: 6, ..Default::default() }); - let res = Verifier::verify(&code, &functions).unwrap(); - assert_eq!(res[0], 0); -} - -// --- Invalid Programs (one per error category) ----------------------------------------------- - -#[test] -fn golden_err_unknown_opcode() { - // Write u16 opcode that does not map to any CoreOpCode - let code = vec![0xFF, 0xFF]; - let functions = func(FunctionMeta { code_offset: 0, code_len: 2, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::UnknownOpcode { pc: 0, opcode: 0xFFFF })); -} - -#[test] -fn golden_err_truncated_opcode() { - let code = vec![OpCode::PushI32 as u8]; // only 1 byte of a u16 opcode - let functions = func(FunctionMeta { code_offset: 0, code_len: 1, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::TruncatedOpcode { pc: 0 })); -} - -#[test] -fn golden_err_truncated_immediate() { - // PushI32 requires 4-byte immediate, provide only 1 - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.push(0xAA); - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!( - res, - Err(VerifierError::TruncatedImmediate { pc: 0, opcode: OpCode::PushI32, need: 4, have: 1 }) - ); -} - -#[test] -fn golden_err_invalid_jump_target() { - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Jmp)); - code.extend_from_slice(&100u32.to_le_bytes()); - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::InvalidJumpTarget { pc: 0, target: 100 })); -} - -#[test] -fn golden_err_jump_to_mid_instruction() { - // [PushI32 4 bytes] then jmp to offset 1 (middle of first instruction) - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&42u32.to_le_bytes()); // at pc 0..6 - code.extend_from_slice(&enc_op(OpCode::Jmp)); // at pc 6..8 - code.extend_from_slice(&1u32.to_le_bytes()); // target 1 - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - // The verifier reports error at the jmp site pc (func_start + pc==6) with target=1 - assert_eq!(res, Err(VerifierError::JumpToMidInstruction { pc: 6, target: 1 })); -} - -#[test] -fn golden_err_stack_underflow() { - // Add requires 2 inputs; stack is empty - let code = vec![OpCode::Add as u8, 0x00]; - let functions = func(FunctionMeta { code_offset: 0, code_len: 2, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::StackUnderflow { pc: 0, opcode: OpCode::Add })); -} - -#[test] -fn golden_err_stack_overflow() { - // Two pushes would raise max stack to 2, but limit is set to 1 - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&1u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&2u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 1, max_stack_slots: 1, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - // Overflow happens when second push increases height from 1 -> 2 over limit=1; pc is start of that push - // First push consumes 6 bytes; second push opcode begins at pc=6 - assert_eq!(res, Err(VerifierError::StackOverflow { pc: 6, height: 2, limit: 1 })); -} - -#[test] -fn golden_err_stack_mismatch_join() { - // Branch that merges with different heights at join point - // 0: PushBool true (3) - // 3: JmpIfTrue 15 (6) - // 9: Jmp 27 (6) - // 15: PushI32 1 (6) - // 21: Jmp 27 (6) - // 27: Nop (2) - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushBool)); - code.push(1); - code.extend_from_slice(&enc_op(OpCode::JmpIfTrue)); - code.extend_from_slice(&15u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Jmp)); - code.extend_from_slice(&27u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&1u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Jmp)); - code.extend_from_slice(&27u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Nop)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!( - res, - Err(VerifierError::StackMismatchJoin { pc: 21, target: 27, height_in: 1, height_target: 0 }) - ); -} - -#[test] -fn golden_err_bad_ret_stack_height() { - // Push one value but function declares 0 returns - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::PushI32)); - code.extend_from_slice(&1u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 0, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::BadRetStackHeight { pc: 6, height: 1, expected: 0 })); -} - -#[test] -fn golden_err_function_out_of_bounds() { - // Function start is beyond the available code length - let code = vec![enc_op(OpCode::Nop)[0], enc_op(OpCode::Nop)[1]]; // len = 2 - let functions = func(FunctionMeta { code_offset: 10, code_len: 0, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert!(matches!(res, Err(VerifierError::FunctionOutOfBounds { func_idx: 0, start: 10, end, code_len: 2 }))); -} - -#[test] -fn golden_err_trailing_bytes() { - // With current decoder loop, a stray byte at the end is reported as TruncatedOpcode - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Nop)); // 2 bytes - code.push(0xCC); // stray byte - let functions = func(FunctionMeta { code_offset: 0, code_len: 3, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::TruncatedOpcode { pc: 2 })); -} - -#[test] -fn golden_err_unterminated_path() { - // Code that falls off end without terminator (no ret/jmp to end/trap/halt) - let code = vec![enc_op(OpCode::Nop)[0], enc_op(OpCode::Nop)[1]]; // single NOP, then fallthrough - let functions = func(FunctionMeta { code_offset: 0, code_len: 2, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::UnterminatedPath { func_idx: 0, at_pc: 0 })); -} - -#[test] -fn golden_err_invalid_syscall_id() { - // Syscall expects valid `prometeu_hal::syscalls::Syscall` id; use an unknown value - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Syscall)); - code.extend_from_slice(&0xDEADBEEFu32.to_le_bytes()); - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::InvalidSyscallId { pc: 0, id: 0xDEADBEEF })); -} - -#[test] -fn golden_err_syscall_too_few_args() { - // Choose a syscall that requires 2 arg slots and returns 0: LogWrite (0x5001) - // Program: SYSCALL 0x5001; RET - // No arguments are pushed before the syscall, so verifier must catch underflow at the syscall. - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Syscall)); - code.extend_from_slice(&0x5001u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 0, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::StackUnderflow { pc: 0, opcode: OpCode::Syscall })); -} - -#[test] -fn golden_ok_syscall_args_and_returns() { - // Case A: Syscall with 2 args, 0 returns (LogWrite): push 2; syscall; ret (ret_slots = 0) - let mut code_a = Vec::new(); - code_a.extend_from_slice(&enc_op(OpCode::PushI32)); - code_a.extend_from_slice(&1i32.to_le_bytes()); - code_a.extend_from_slice(&enc_op(OpCode::PushI32)); - code_a.extend_from_slice(&2i32.to_le_bytes()); - code_a.extend_from_slice(&enc_op(OpCode::Syscall)); - code_a.extend_from_slice(&0x5001u32.to_le_bytes()); // LogWrite: 2 args, 0 returns - code_a.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions_a = func(FunctionMeta { code_offset: 0, code_len: code_a.len() as u32, return_slots: 0, ..Default::default() }); - let res_a = Verifier::verify(&code_a, &functions_a).unwrap(); - // Max stack height reaches 2 after two pushes - assert!(res_a[0] >= 2); - - // Case B: Syscall with 0 args, 1 return (TouchGetX: 0x2101): syscall; ret (ret_slots = 1) - let mut code_b = Vec::new(); - code_b.extend_from_slice(&enc_op(OpCode::Syscall)); - code_b.extend_from_slice(&0x2101u32.to_le_bytes()); // TouchGetX: 0 args, 1 return - code_b.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions_b = func(FunctionMeta { code_offset: 0, code_len: code_b.len() as u32, return_slots: 1, ..Default::default() }); - let res_b = Verifier::verify(&code_b, &functions_b).unwrap(); - // Max stack height should be at least 1 due to the return value - assert!(res_b[0] >= 1); -} - -#[test] -fn golden_ok_syscall_multi_returns() { - // Syscall with 0 args and 4 returns: PadGetUp (0x2200) - // Program: SYSCALL 0x2200; RET (ret_slots = 4) - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Syscall)); - code.extend_from_slice(&0x2200u32.to_le_bytes()); // PadGetUp: 0 args, 4 returns - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 4, ..Default::default() }); - let res = Verifier::verify(&code, &functions).unwrap(); - // Max stack height should reach at least 4 due to the syscall's return values - assert!(res[0] >= 4); -} - -#[test] -fn golden_err_syscall_multi_returns_mismatch() { - // Same syscall (PadGetUp: returns 4), but function declares only 3 return slots. - // Program: SYSCALL 0x2200; RET (ret_slots = 3) → verifier must reject due to BadRetStackHeight. - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Syscall)); - code.extend_from_slice(&0x2200u32.to_le_bytes()); - code.extend_from_slice(&enc_op(OpCode::Ret)); - - let functions = func(FunctionMeta { code_offset: 0, code_len: code.len() as u32, return_slots: 3, ..Default::default() }); - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::BadRetStackHeight { pc: 6, height: 4, expected: 3 })); -} - -#[test] -fn golden_err_invalid_func_id() { - // Encode a Call with invalid function id (beyond functions.len()) - // We use two functions meta entries; first is caller, second is callee placeholder. - // The call uses id=5 which is out-of-range → InvalidFuncId. - let mut code = Vec::new(); - code.extend_from_slice(&enc_op(OpCode::Call)); - code.extend_from_slice(&5u32.to_le_bytes()); - - // Caller function only. No actual callee at index 5. - let functions = vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }]; - let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::InvalidFuncId { pc: 0, id: 5 })); -} -} diff --git a/files/TODOs.md b/files/TODOs.md index f6bb0a30..dcc95527 100644 --- a/files/TODOs.md +++ b/files/TODOs.md @@ -1,45 +1,3 @@ -# PR-9.3 — Remove Temporary Feature Flags - -## Briefing - -During refactor phases, temporary feature flags or conditional compilation may have been introduced. - -These must not remain in the final baseline. - -## Target - -1. Identify all feature flags related to transitional behavior. -2. Remove obsolete `cfg` gates. -3. Remove commented legacy branches. -4. Ensure single authoritative execution path. - -## Acceptance Checklist - -* [ ] No transitional feature flags remain. -* [ ] No commented-out legacy logic. -* [ ] Single execution model. -* [ ] All tests pass. - -## Tests - -* Full test suite passes without feature toggles. - -## Junie Instructions - -You MAY: - -* Remove obsolete flags. -* Simplify code paths. - -You MUST NOT: - -* Remove legitimate platform flags. -* Leave partial dead branches. - -If unsure whether a flag is temporary or architectural, STOP and ask. - ---- - # PR-9.4 — Final Cleanup & Quality Sweep ## Briefing