diff --git a/crates/verifier/src/ctx.rs b/crates/verifier/src/ctx.rs new file mode 100644 index 00000000..4a0441dd --- /dev/null +++ b/crates/verifier/src/ctx.rs @@ -0,0 +1,39 @@ +//! Verification context + +use sonatina_ir::{module::FuncRef, ControlFlowGraph, Function}; + +use crate::{ + error::{ErrorData, ErrorKind, TraceInfo}, + ErrorStack, +}; + +pub struct VerificationCtx<'a> { + pub func_ref: FuncRef, + pub func: &'a Function, + pub cfg: ControlFlowGraph, + pub error_stack: ErrorStack, +} + +impl<'a> VerificationCtx<'a> { + pub fn new(func_ref: FuncRef, func: &'a Function) -> Self { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(func); + + Self { + func_ref, + func, + cfg, + error_stack: ErrorStack::default(), + } + } + + pub fn report_nonfatal(&mut self, errs: &[ErrorData]) { + for e in errs { + let _err_ref = self.error_stack.push(*e); + } + } + + pub fn report_fatal(&mut self, kind: ErrorKind, trace_info: TraceInfo) { + self.error_stack.fatal_error = Some(ErrorData::new(kind, trace_info)); + } +} diff --git a/crates/verifier/src/error_stack.rs b/crates/verifier/src/error_stack.rs index 6f6ec236..08b0e749 100644 --- a/crates/verifier/src/error_stack.rs +++ b/crates/verifier/src/error_stack.rs @@ -5,12 +5,13 @@ use crate::error::{Error, ErrorData, ErrorRef}; #[derive(Debug, Default)] pub struct ErrorStack { - pub errors: PrimaryMap, + pub fatal_error: Option, + pub non_fatal_errors: PrimaryMap, } impl ErrorStack { pub fn push(&mut self, err: ErrorData) -> ErrorRef { - self.errors.push(err) + self.non_fatal_errors.push(err) } pub fn into_errs_iter( @@ -18,8 +19,16 @@ impl ErrorStack { func: &Function, func_ref: FuncRef, ) -> impl IntoIterator> { - self.errors - .into_iter() + let Self { + fatal_error, + non_fatal_errors: mut errs, + } = self; + + if let Some(err) = fatal_error { + errs.push(err); + } + + errs.into_iter() .map(move |(_, err)| Error::new(err, func, func_ref)) } } diff --git a/crates/verifier/src/lib.rs b/crates/verifier/src/lib.rs index d32fd422..0fe1e587 100644 --- a/crates/verifier/src/lib.rs +++ b/crates/verifier/src/lib.rs @@ -1,2 +1,9 @@ +pub mod ctx; pub mod error; pub mod error_stack; +pub mod pass; +pub mod passes; + +pub use ctx::VerificationCtx; +pub use error_stack::ErrorStack; +pub use pass::VerificationPass; diff --git a/crates/verifier/src/pass.rs b/crates/verifier/src/pass.rs new file mode 100644 index 00000000..24270ba1 --- /dev/null +++ b/crates/verifier/src/pass.rs @@ -0,0 +1,14 @@ +//! Verification pass + +use crate::VerificationCtx; + +pub trait VerificationPass { + fn run(&mut self, ctx: &mut VerificationCtx) -> VerificationResult; +} + +#[derive(Debug, PartialEq, Eq)] +pub enum VerificationResult { + Pass, + Fail, + FailFatal, +} diff --git a/crates/verifier/src/passes/block/end_in_terminator.rs b/crates/verifier/src/passes/block/end_in_terminator.rs new file mode 100644 index 00000000..4df5ff9a --- /dev/null +++ b/crates/verifier/src/passes/block/end_in_terminator.rs @@ -0,0 +1,137 @@ +use crate::{ + error::{ + ErrorKind::{NotEndedByTerminator, TerminatorBeforeEnd}, + TraceInfoBuilder, + }, + pass::VerificationResult, + VerificationCtx, VerificationPass, +}; + +pub struct EndInTerminator; + +impl VerificationPass for EndInTerminator { + fn run(&mut self, ctx: &mut VerificationCtx) -> VerificationResult { + let layout = &ctx.func.layout; + let dfg = &ctx.func.dfg; + + for block in layout.iter_block() { + let last_inst = layout.last_inst_of(block).expect("pass dependency error"); + + // check last instruction in block is terminator + if !dfg.is_terminator(last_inst) { + let trace_info = TraceInfoBuilder::new(ctx.func_ref).block(block).build(); + ctx.report_fatal(NotEndedByTerminator(last_inst), trace_info); + + return VerificationResult::FailFatal; + } + + // check no instruction mid-block is terminator + for inst in layout.iter_inst(block) { + if inst == last_inst { + break; + } + + if dfg.is_terminator(inst) { + let trace_info = TraceInfoBuilder::new(ctx.func_ref).block(block).build(); + ctx.report_fatal(TerminatorBeforeEnd(inst), trace_info); + + return VerificationResult::FailFatal; + } + } + } + + VerificationResult::Pass + } +} + +#[cfg(test)] +mod tests { + use sonatina_ir::{ + builder::test_util::test_func_builder, + inst::{ + control_flow::{Jump, Return}, + logic::Xor, + }, + isa::Isa, + Type, + }; + + use super::*; + + #[test] + fn last_inst_not_terminator() { + let (evm, mut builder) = test_func_builder(&[Type::I1], Type::Unit); + let is = evm.inst_set(); + + let b0 = builder.append_block(); + + let arg = builder.args()[0]; + + builder.switch_to_block(b0); + let c1 = builder.make_imm_value(false); + builder.insert_inst_with(|| Xor::new(is, arg, c1), Type::I1); + + builder.seal_all(); + + let module = builder.finish().build(); + let func_ref = module.iter_functions().next().unwrap(); + let func = &module.funcs[func_ref]; + + let mut ctx = VerificationCtx::new(func_ref, func); + let res = EndInTerminator.run(&mut ctx); + assert_eq!(res, VerificationResult::FailFatal); + + let errs = ctx + .error_stack + .into_errs_iter(func, func_ref) + .into_iter() + .collect::>(); + assert_eq!(1, errs.len()); + + assert_eq!( + "last instruction not terminator, xor v0 0.i1 +trace_info: +0: block0 +1: func public %test_func(i1) -> unit", + errs[0].to_string() + ); + } + + #[test] + fn terminator_mid_block() { + let (evm, mut builder) = test_func_builder(&[], Type::Unit); + let is = evm.inst_set(); + + let b0 = builder.append_block(); + let b1 = builder.append_block(); + + builder.switch_to_block(b0); + builder.insert_inst_no_result(Jump::new(is, b1)); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.seal_all(); + + let module = builder.finish().build(); + let func_ref = module.iter_functions().next().unwrap(); + let func = &module.funcs[func_ref]; + + let mut ctx = VerificationCtx::new(func_ref, func); + let res = EndInTerminator.run(&mut ctx); + assert_eq!(res, VerificationResult::FailFatal); + + let errs = ctx + .error_stack + .into_errs_iter(func, func_ref) + .into_iter() + .collect::>(); + assert_eq!(1, errs.len()); + + assert_eq!( + "terminator instruction mid-block, jump block1 +trace_info: +0: block0 +1: func public %test_func() -> unit", + errs[0].to_string() + ); + } +} diff --git a/crates/verifier/src/passes/block/mod.rs b/crates/verifier/src/passes/block/mod.rs new file mode 100644 index 00000000..510dfdc9 --- /dev/null +++ b/crates/verifier/src/passes/block/mod.rs @@ -0,0 +1,5 @@ +//! Passes to verify block integrity + +pub mod end_in_terminator; + +pub use end_in_terminator::EndInTerminator; diff --git a/crates/verifier/src/passes/mod.rs b/crates/verifier/src/passes/mod.rs new file mode 100644 index 00000000..a863eaad --- /dev/null +++ b/crates/verifier/src/passes/mod.rs @@ -0,0 +1 @@ +pub mod block;