This commit is contained in:
bQUARKz 2026-02-20 17:30:14 +00:00
parent 6950f2bef0
commit 1a782cbb5c
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 125 additions and 547 deletions

View File

@ -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)]

View File

@ -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);
}
}

View File

@ -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 })
);
}

View File

@ -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<FunctionMeta> {
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 }));
}
}

View File

@ -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