#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;
use crate::Program;
use crate::VerifierError;
use crate::bytecode::InstructionStream;
use crate::dataflow::analysis::Analysis;
use crate::dataflow::bytecode::{BlockId, CfgContext, build_cfg};
use crate::dataflow::direction::Forward;
use crate::dataflow::lattice::Lattice;
use crate::dataflow::solver::solve;
use crate::verifier::reg_type::{RegType, check_reads, write_effect};
#[derive(Clone, PartialEq, Eq)]
pub struct InitValue([u64; 4]);
impl InitValue {
pub(crate) fn is_init(&self, slot: u8) -> bool {
let word = usize::from(slot) / 64;
let bit = u32::from(slot & 63);
self.0.get(word).is_some_and(|&w| (w >> bit) & 1 == 1)
}
pub(crate) fn set_init(&mut self, slot: u8) {
let word = usize::from(slot) / 64;
let bit = u32::from(slot & 63);
if let Some(w) = self.0.get_mut(word) {
*w |= 1u64 << bit;
}
}
pub(crate) fn clear_init(&mut self, slot: u8) {
let word = usize::from(slot) / 64;
let bit = u32::from(slot & 63);
if let Some(w) = self.0.get_mut(word) {
*w &= !(1u64 << bit);
}
}
}
impl Lattice for InitValue {
fn top() -> Self {
Self([u64::MAX; 4])
}
fn meet(&self, other: &Self) -> Self {
let [a0, a1, a2, a3] = self.0;
let [b0, b1, b2, b3] = other.0;
Self([a0 & b0, a1 & b1, a2 & b2, a3 & b3])
}
}
pub struct InitAnalysis {
block_inits: HashMap<BlockId, Vec<(u8, bool)>>,
}
impl Analysis for InitAnalysis {
type Value = InitValue;
type Node = BlockId;
type Dir = Forward;
fn boundary_value(&self) -> InitValue {
InitValue([0u64; 4])
}
fn transfer(&self, node: &BlockId, input: &InitValue) -> InitValue {
let mut state = input.clone();
if let Some(inits) = self.block_inits.get(node) {
for &(slot, initialized) in inits {
if initialized {
state.set_init(slot);
} else {
state.clear_init(slot);
}
}
}
state
}
}
fn collect_block_inits(code: &[u8], block_start: usize, block_end: usize) -> Vec<(u8, bool)> {
let block_code = match code.get(block_start..block_end) {
Some(s) if !s.is_empty() => s,
_ => return Vec::new(),
};
let mut inits = Vec::new();
let mut stream = InstructionStream::new(block_code);
while let Some(Ok((_, _, instr))) = stream.next_instruction() {
if let Some((reg, reg_type)) = write_effect(&instr) {
inits.push((reg.slot(), reg_type != RegType::Unset));
}
}
inits
}
pub(crate) fn check_uninit_registers_with_cfg(
program: &Program,
cfg: &crate::dataflow::cfg::Cfg<BlockId>,
) -> Result<(), VerifierError> {
let code = program.code();
let mut starts: Vec<BlockId> = cfg.nodes().to_vec();
starts.sort_unstable();
let ranges: Vec<(usize, usize)> = starts
.iter()
.enumerate()
.map(|(i, &s)| {
let end = starts.get(i + 1).copied().unwrap_or(code.len());
(s, end)
})
.collect();
let block_inits: HashMap<BlockId, Vec<(u8, bool)>> = ranges
.iter()
.filter_map(|&(start, end)| {
let inits = collect_block_inits(code, start, end);
if inits.is_empty() {
None
} else {
Some((start, inits))
}
})
.collect();
let analysis = InitAnalysis { block_inits };
let result = solve(&analysis, cfg);
let top = InitValue::top();
for &(block_start, block_end) in &ranges {
let before = result
.before(&block_start)
.cloned()
.unwrap_or_else(InitValue::top);
if before == top {
continue;
}
let mut regs = [RegType::Any; 256];
for slot in 0u8..=255 {
if !before.is_init(slot)
&& let Some(e) = regs.get_mut(usize::from(slot))
{
*e = RegType::Unset;
}
}
let block_code = code.get(block_start..block_end).unwrap_or(&[]);
let mut stream = InstructionStream::new(block_code);
while let Some(item) = stream.next_instruction() {
let Ok((rel_pos, _, instr)) = item else { break };
let abs_pos = block_start + rel_pos;
check_reads(abs_pos, &instr, ®s)?;
if let Some((reg, reg_type)) = write_effect(&instr)
&& let Some(e) = regs.get_mut(usize::from(reg.slot()))
{
*e = if reg_type == RegType::Unset {
RegType::Unset
} else {
RegType::Any
};
}
}
}
Ok(())
}
pub fn check_uninit_registers(program: &Program) -> Result<(), VerifierError> {
let code = program.code();
if code.is_empty() {
return Ok(());
}
let Some(CfgContext { cfg, .. }) = build_cfg(code, program.jump_table()) else {
return Ok(());
};
check_uninit_registers_with_cfg(program, &cfg)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::codec;
use crate::{Instruction, Program, Register};
fn prog(instrs: &[Instruction]) -> Program {
Program::new(instrs.iter().flat_map(codec::encode).collect())
}
#[test]
fn empty_program_ok() {
assert!(check_uninit_registers(&Program::new(vec![])).is_ok());
}
#[test]
fn stow_then_load_ok() {
let p = prog(&[
Instruction::Push1 { val: [7] },
Instruction::Stow { reg: Register(0) },
Instruction::Load { reg: Register(0) },
Instruction::Halt {},
]);
assert!(check_uninit_registers(&p).is_ok());
}
#[test]
fn load_unset_register_is_error() {
let p = prog(&[Instruction::Load { reg: Register(1) }, Instruction::Halt {}]);
let err = check_uninit_registers(&p).unwrap_err();
assert_eq!(err.variant_name(), "ReadUnsetRegister");
}
#[test]
fn drop_clears_register() {
let p = prog(&[
Instruction::Push1 { val: [1] },
Instruction::Stow { reg: Register(0) },
Instruction::Drop { reg: Register(0) },
Instruction::Load { reg: Register(0) },
Instruction::Halt {},
]);
let err = check_uninit_registers(&p).unwrap_err();
assert_eq!(err.variant_name(), "ReadUnsetRegister");
}
#[test]
fn one_branch_skips_write_is_error() {
let p = prog(&[
Instruction::Push1 { val: [1] },
Instruction::JumpI1 { label: 0 }, Instruction::Stow { reg: Register(0) }, Instruction::Target {}, Instruction::Load { reg: Register(0) }, Instruction::Halt {},
]);
let err = check_uninit_registers(&p).unwrap_err();
assert_eq!(err.variant_name(), "ReadUnsetRegister");
}
#[test]
fn both_branches_write_ok() {
let p = prog(&[
Instruction::Push1 { val: [1] },
Instruction::JumpI1 { label: 0 },
Instruction::Push1 { val: [2] },
Instruction::Stow { reg: Register(0) }, Instruction::Jump2 { label: 1 },
Instruction::Target {}, Instruction::Push1 { val: [3] },
Instruction::Stow { reg: Register(0) }, Instruction::Target {}, Instruction::Load { reg: Register(0) },
Instruction::Halt {},
]);
assert!(check_uninit_registers(&p).is_ok());
}
#[test]
fn unreachable_block_no_false_positive() {
let p = prog(&[
Instruction::Push1 { val: [1] },
Instruction::Jump2 { label: 0 },
Instruction::Stow { reg: Register(0) }, Instruction::Target {}, Instruction::Push1 { val: [2] },
Instruction::Stow { reg: Register(0) }, Instruction::Load { reg: Register(0) },
Instruction::Halt {},
]);
assert!(check_uninit_registers(&p).is_ok());
}
}