use std::fmt;
use super::{Capture, CodeUnit, Format, NO_SPILL, Opcode, flags};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ValidateErrorKind {
EmptyCode,
FallsOffEnd,
LayoutOverflow,
Register(u32),
WideRegister(u32),
CallWindow,
RegisterRange,
Target(u32),
Constant(u32),
Child(u32),
UnpublishedChild(u32),
Capture(u32),
ChildCapture(u32),
HandlerRange,
HandlerNesting,
LineTable,
Flags,
Site(u32),
Spill,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ValidateError {
pub at: Option<u32>,
pub kind: ValidateErrorKind,
}
impl fmt::Display for ValidateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid code unit: {:?}", self.kind)?;
if let Some(at) = self.at {
write!(f, " at {at}")?;
}
Ok(())
}
}
impl std::error::Error for ValidateError {}
fn fail<T>(at: Option<u32>, kind: ValidateErrorKind) -> Result<T, ValidateError> {
Err(ValidateError { at, kind })
}
pub fn validate<'a>(
unit: &CodeUnit,
published: &dyn Fn(super::CodeUnitId) -> Option<&'a CodeUnit>,
) -> Result<(), ValidateError> {
use ValidateErrorKind as E;
let len = unit.code.len() as u32;
let registers = unit.register_count as u32;
let Some(last) = unit.code.last() else {
return fail(None, E::EmptyCode);
};
if !last.op.is_terminator() {
return fail(Some(len - 1), E::FallsOffEnd);
}
if unit.temp_base() > registers {
return fail(None, E::LayoutOverflow);
}
if unit.has(flags::ARROW) && unit.has(flags::CONSTRUCTOR) {
return fail(None, E::Flags);
}
let positions = &unit.debug.positions;
if positions.windows(2).any(|pair| pair[0].pc >= pair[1].pc)
|| positions
.last()
.is_some_and(|last| last.pc as usize >= unit.code.len())
{
return fail(None, E::LineTable);
}
for (index, &child) in unit.children.iter().enumerate() {
let Some(child_unit) = published(child) else {
return fail(Some(index as u32), E::UnpublishedChild(child.0));
};
for capture in &child_unit.captures {
match *capture {
Capture::None => {}
Capture::Register(r) if (r as u32) < registers => {}
Capture::Outer(i) if (i as usize) < unit.captures.len() => {}
Capture::Slot(i) if unit.spill_register != NO_SPILL && i < unit.spill_slots => {}
Capture::Register(r) | Capture::Outer(r) | Capture::Slot(r) => {
return fail(Some(index as u32), E::ChildCapture(r as u32));
}
}
}
}
for (pc, insn) in unit.code.iter().enumerate() {
let at = Some(pc as u32);
let register = |r: u8| {
if (r as u32) < registers {
Ok(())
} else {
fail(at, E::Register(r as u32))
}
};
let target = |t: u32| {
if t < len {
Ok(())
} else {
fail(at, E::Target(t))
}
};
match insn.op.format() {
Format::None | Format::Key => {}
Format::A | Format::AInt | Format::AKey | Format::ACount => {
register(insn.a)?;
}
Format::ACapture => {
register(insn.a)?;
if insn.imm as usize >= unit.captures.len() {
return fail(at, E::Capture(insn.imm));
}
}
Format::AB | Format::ABInt | Format::ABKey => {
register(insn.a)?;
register(insn.b)?;
}
Format::ABC => {
register(insn.a)?;
register(insn.b)?;
register(insn.c)?;
}
Format::AConst => {
register(insn.a)?;
let last = insn.imm as u64 + u64::from(insn.op == Opcode::NewRegExp);
if last >= u64::from(unit.constant_count) {
return fail(at, E::Constant(insn.imm));
}
}
Format::ABRange => {
register(insn.a)?;
if insn.imm > 0 && insn.b as u64 + insn.imm as u64 > registers as u64 {
return fail(at, E::RegisterRange);
}
}
Format::AChild => {
register(insn.a)?;
if insn.imm as usize >= unit.children.len() {
return fail(at, E::Child(insn.imm));
}
}
Format::Call => {
register(insn.a)?;
let operands = match insn.op {
Opcode::Call | Opcode::TailCall => 1,
Opcode::CallSpread | Opcode::NewSpread => 3,
_ => 2,
};
if insn.b as u32 + operands + insn.c as u32 > registers {
return fail(at, E::CallWindow);
}
}
Format::Jump => target(insn.imm)?,
Format::AJump | Format::AShortJump => {
register(insn.a)?;
target(insn.imm)?;
}
Format::ABJump => {
register(insn.a)?;
register(insn.b)?;
target(insn.imm)?;
}
Format::AWide => {
register(insn.a)?;
if insn.imm >= registers {
return fail(at, E::WideRegister(insn.imm));
}
}
}
if matches!(
insn.op,
Opcode::LoadSlot | Opcode::StoreSlot | Opcode::NewSpill
) {
let spill = if insn.op == Opcode::NewSpill {
insn.a
} else {
insn.b
};
let slot = if insn.op == Opcode::NewSpill {
u32::from(unit.spill_slots).saturating_sub(1)
} else {
insn.imm
};
if unit.spill_register == NO_SPILL
|| u16::from(spill) != unit.spill_register
|| (insn.op != Opcode::NewSpill && slot >= u32::from(unit.spill_slots))
|| (insn.op == Opcode::NewSpill && insn.imm != u32::from(unit.spill_slots))
{
return fail(at, E::Spill);
}
}
if let Some(site) = insn.site()
&& site >= unit.ic_count
{
return fail(at, E::Site(site));
}
}
for (index, handler) in unit.handlers.iter().enumerate() {
let at = Some(index as u32);
if handler.start >= handler.end || handler.end > len {
return fail(at, E::HandlerRange);
}
if handler.target >= len {
return fail(at, E::Target(handler.target));
}
if handler.register as u32 >= registers {
return fail(at, E::Register(handler.register as u32));
}
for later in &unit.handlers[index + 1..] {
let disjoint = later.end <= handler.start || handler.end <= later.start;
let encloses = later.start <= handler.start && handler.end <= later.end;
if !disjoint && !encloses {
return fail(at, E::HandlerNesting);
}
}
}
Ok(())
}