#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::bytecode::error::StreamError;
use crate::bytecode::{Instruction, InstructionStream, JumpTable};
use super::error::VerifierError;
pub(super) fn stream_err(e: &StreamError) -> VerifierError {
match e {
StreamError::TruncatedInstruction { offset } => {
VerifierError::TruncatedInstruction { offset: *offset }
}
StreamError::UnknownOpcode { offset, byte } => VerifierError::BadOpcode {
offset: *offset,
byte: *byte,
},
StreamError::SeekOutOfBounds { target, .. } => {
VerifierError::TruncatedInstruction { offset: *target }
}
}
}
pub(crate) fn scan(code: &[u8]) -> (JumpTable, (u8, u8), Option<VerifierError>) {
let mut targets: Vec<usize> = Vec::new();
let mut loop_offsets: Vec<usize> = Vec::new();
let mut jump_refs: Vec<(usize, u16)> = Vec::new();
let mut first_error: Option<VerifierError> = None;
let mut input_slots: u8 = 0;
let mut output_slots: u8 = 0;
let mut stream = InstructionStream::new(code);
'scan: while let Some(item) = stream.next_instruction() {
let (pos, _label, instr) = match item {
Ok(v) => v,
Err(e) => {
let _ = first_error.get_or_insert_with(|| stream_err(&e));
break 'scan;
}
};
match instr {
Instruction::Target {} => targets.push(pos),
Instruction::Input { .. } => input_slots = input_slots.saturating_add(1),
Instruction::Output { .. } => output_slots = output_slots.saturating_add(1),
Instruction::Range {} | Instruction::Iter { .. } => loop_offsets.push(pos),
Instruction::Next {} if first_error.is_none() && loop_offsets.pop().is_none() => {
first_error = Some(VerifierError::NoActiveLoop { offset: pos });
}
Instruction::Lidx { .. } | Instruction::LVal { .. } if loop_offsets.is_empty() => {
let _ = first_error.get_or_insert(VerifierError::NoActiveLoop { offset: pos });
}
Instruction::Jump1 { label } | Instruction::JumpI1 { label } => {
jump_refs.push((pos, u16::from(label)));
}
Instruction::Jump2 { label } | Instruction::JumpI2 { label } => {
jump_refs.push((pos, label));
}
_ => {}
}
}
if first_error.is_none()
&& let Some(&outermost) = loop_offsets.first()
{
first_error = Some(VerifierError::UnmatchedLoop {
offset: outermost,
depth: loop_offsets.len(),
});
}
let jump_table = JumpTable::new(targets);
if first_error.is_none() {
let target_count = jump_table.len();
for (offset, label) in jump_refs {
if usize::from(label) >= target_count {
first_error = Some(VerifierError::UndefinedJumpTarget {
offset,
label,
target_count,
});
break;
}
}
}
(jump_table, (input_slots, output_slots), first_error)
}