1use std::{
2 collections::HashSet,
3 fmt::{self, Debug, Display, Formatter},
4 io, result,
5};
6
7use super::{
8 Arg, BUILTINS, BlockState, Certificate, DecError, Func, Inst, InstDecoder, InstOffsets,
9 LocalState, Phase, limit,
10};
11
12#[derive(PartialEq, Eq)]
13pub(crate) enum InstError {
14 InvalidOpcode,
15 Truncated,
16 IntegerOverflow,
17 OperandDepthConflict {
18 from: usize,
19 into: usize,
20 },
21 UpvarDepthConflict {
22 from: usize,
23 into: usize,
24 },
25 UpvarCountConflict {
26 depth: usize,
27 from: usize,
28 into: usize,
29 },
30 NoOpSwap,
31 OperandIndexOutOfBounds(usize),
32 OperandUnderflow,
33 BranchTargetOutOfBounds,
34 BranchTargetInvalid,
35 ConstIndexOutOfBounds,
36 SymIndexOutOfBounds,
37 BuiltinIndexOutOfBounds,
38 LocalIndexOutOfBounds,
39 LocalLoadUninitialized,
40 UpvarDepthOutOfBounds,
41 UpvarIndexOutOfBounds,
42 UpvarPushExceedsLimit,
43 UpvarUnderflow,
44 PackIndexOutOfBounds,
45 UnpackIndexOutOfBounds,
46 FuncIndexOutOfBounds,
47 UpvarSigMismatch,
48 ReturnWithExcessOperands,
49 ReturnWithExcessUpvars,
50 InstructionUnreachable,
51 NotAFixedPoint,
52 ReifyMismatch(usize, usize),
53}
54
55impl Display for InstError {
56 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57 use InstError::*;
58
59 match self {
60 InvalidOpcode => write!(f, "invalid opcode"),
61 Truncated => write!(f, "truncated instruction"),
62 IntegerOverflow => write!(f, "integer overflow"),
63 OperandDepthConflict { from, into } => {
64 write!(f, "operand stack depth conflict ({from} != {into})")
65 }
66 UpvarDepthConflict { from, into } => {
67 write!(f, "upvar stack depth conflict ({from} != {into})")
68 }
69 UpvarCountConflict { depth, from, into } => {
70 write!(f, "upvar count conflict at depth {depth} ({from} != {into}")
71 }
72 NoOpSwap => write!(f, "swap immediates are the same"),
73 OperandIndexOutOfBounds(i) => write!(f, "operand index out of bounds: {}", i),
74 OperandUnderflow => write!(f, "operand stack underflow"),
75 BranchTargetOutOfBounds => write!(f, "branch target out of bounds"),
76 BranchTargetInvalid => write!(f, "branch target is not an instruction boundary"),
77 ConstIndexOutOfBounds => write!(f, "constant table index out of bounds"),
78 SymIndexOutOfBounds => write!(f, "symbol table index out of bounds"),
79 BuiltinIndexOutOfBounds => write!(f, "builtin call index out of bounds"),
80 LocalIndexOutOfBounds => write!(f, "local index out of bounds"),
81 LocalLoadUninitialized => write!(f, "load of possibly uninitialized local"),
82 UpvarDepthOutOfBounds => write!(f, "upvar depth out of bounds"),
83 UpvarIndexOutOfBounds => write!(f, "upvar index out of bounds"),
84 UpvarPushExceedsLimit => write!(f, "upvar limit exceeded"),
85 UpvarUnderflow => write!(f, "upvar stack underflow"),
86 PackIndexOutOfBounds => write!(f, "pack table index out of bounds"),
87 UnpackIndexOutOfBounds => write!(f, "unpack table index out of bounds"),
88 FuncIndexOutOfBounds => write!(f, "function table index out of bounds"),
89 UpvarSigMismatch => write!(f, "closure upvar signature does not match state"),
90 ReturnWithExcessOperands => write!(f, "return with excess operands on stack"),
91 ReturnWithExcessUpvars => write!(f, "return with excess upvar records on stack"),
92 InstructionUnreachable => write!(f, "syntactically unreachable instruction"),
93 NotAFixedPoint => write!(f, "certificate is not a fixed point"),
94 ReifyMismatch(i, j) => write!(f, "reify pack/upvar mismatch ({i} != {j})"),
95 }
96 }
97}
98
99impl Debug for InstError {
100 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
101 Display::fmt(self, f)
102 }
103}
104
105#[derive(PartialEq, Eq)]
106pub(crate) enum FuncError {
107 Inst(usize, InstError, Option<String>),
108 UnexpectedEnd,
109 UpvarLimitExceeded,
110 BasicBlockMismatch,
111 OperandDepthMismatch,
112 Unused,
113}
114
115impl FuncError {
116 #[cfg(feature = "debug")]
117 fn disass(bytecode: &[u8], offset: usize) -> Option<String> {
118 let bytes = bytecode.get(offset..)?;
119 let mut cursor = io::Cursor::new(bytes);
120 let mut decoder = InstDecoder::new(&mut cursor);
121 decoder.next()?.ok().map(|inst| inst.to_string())
122 }
123
124 #[cfg(not(feature = "debug"))]
125 fn disass(_bytecode: &[u8], _offset: usize) -> Option<String> {
126 None
127 }
128
129 fn inst(bytecode: &[u8], inst: usize, inner: InstError) -> Self {
130 Self::Inst(inst, inner, Self::disass(bytecode, inst))
131 }
132}
133
134impl Display for FuncError {
135 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
136 use FuncError::*;
137
138 match self {
139 Inst(inst, inner, None) => write!(f, "pc 0x{inst:x}: {inner}"),
140 Inst(inst, inner, Some(disass)) => write!(f, "pc 0x{inst:x} [{disass}]: {inner}"),
141 Unused => write!(f, "function is unused"),
142 UnexpectedEnd => write!(f, "execution can fall off end of code"),
143 UpvarLimitExceeded => write!(f, "upvar limit limit exceeded"),
144 BasicBlockMismatch => write!(
145 f,
146 "certificate did not match expected basic block locations"
147 ),
148 OperandDepthMismatch => write!(
149 f,
150 "certificate did not match expected maximum operand stack depth"
151 ),
152 }
153 }
154}
155
156impl Debug for FuncError {
157 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
158 Display::fmt(self, f)
159 }
160}
161
162pub struct Error {
163 func: usize,
164 error: FuncError,
165}
166
167impl Display for Error {
168 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
169 write!(f, "function #{}: {}", self.func, self.error)
170 }
171}
172
173impl Debug for Error {
174 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
175 Display::fmt(self, f)
176 }
177}
178
179type FuncResult<T> = result::Result<T, FuncError>;
180
181type FlowResult<T> = result::Result<T, InstError>;
182
183pub type Result<T> = result::Result<T, Error>;
184
185pub trait Context {
186 type Phase: Phase;
187
188 fn slice<'a>(&'a self, bytes: &'a <Self::Phase as Phase>::Bytes) -> &'a [u8];
189 fn function(&self, index: usize) -> Option<&Func<Self::Phase>>;
190 fn pack(&self, index: usize) -> Option<impl Iterator<Item = Arg>>;
191 fn unpack_arity(&self, index: usize) -> Option<usize>;
192 fn symbol_valid(&self, index: usize) -> bool;
193 fn constant_valid(&self, index: usize) -> bool;
194}
195
196impl LocalState {
197 fn merge(&self, into: &mut Self) -> FlowResult<()> {
198 *into = match (self, &*into) {
199 (LocalState::Value, LocalState::Value) => LocalState::Value,
200 _ => LocalState::Invalid,
201 };
202 Ok(())
203 }
204}
205
206impl Display for LocalState {
207 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
208 Display::fmt(
209 match self {
210 LocalState::Invalid => "⊥",
211 LocalState::Value => "·",
212 },
213 f,
214 )
215 }
216}
217
218impl BlockState {
219 fn neutral() -> Self {
220 Self {
221 operands: usize::MAX,
223 locals: Default::default(),
224 upvars: Default::default(),
225 }
226 }
227
228 fn merge(&self, into: &mut Self) -> FlowResult<()> {
229 if into.operands == usize::MAX {
230 self.clone_into(into);
231 return Ok(());
232 }
233
234 if self.operands != into.operands {
235 return Err(InstError::OperandDepthConflict {
236 from: self.operands,
237 into: into.operands,
238 });
239 }
240 if self.upvars.len() != into.upvars.len() {
241 return Err(InstError::UpvarDepthConflict {
242 from: self.upvars.len(),
243 into: into.upvars.len(),
244 });
245 }
246 for (i, (l, r)) in self.upvars.iter().zip(into.upvars.iter()).enumerate() {
247 if l != r {
248 return Err(InstError::UpvarCountConflict {
249 depth: self.upvars.len() - i - 1,
250 from: *l,
251 into: *r,
252 });
253 }
254 }
255 for (l, r) in self.locals.iter().zip(into.locals.iter_mut()) {
256 l.merge(r)?
257 }
258 Ok(())
259 }
260
261 fn pop(&mut self) -> FlowResult<()> {
262 self.operands = self
263 .operands
264 .checked_sub(1)
265 .ok_or(InstError::OperandUnderflow)?;
266 Ok(())
267 }
268
269 fn push(&mut self) {
270 self.operands = self.operands.strict_add(1)
271 }
272}
273
274impl Display for BlockState {
275 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
276 write!(f, "operands: {}", self.operands)?;
277 write!(f, " locals: [")?;
278 for local in self.locals.iter() {
279 write!(f, "{local}")?
280 }
281 write!(f, "] upvars: [")?;
282 for upvar in self.upvars.iter() {
283 write!(f, "{upvar}")?
284 }
285 write!(f, "]")
286 }
287}
288
289struct OffsetInfo {
290 offset: usize,
292 start_bb: bool,
294}
295
296struct Block {
297 offset: usize,
299 mark: bool,
302 state: BlockState,
304}
305
306#[derive(Default, Clone)]
308struct Used {
309 used: bool,
310 uses: HashSet<usize>,
311}
312
313struct FuncVerifier<'a, C: Context> {
314 ctx: &'a C,
316 index: usize,
318 func: &'a Func<C::Phase>,
320 used: &'a mut [Used],
322 offsets: Vec<OffsetInfo>,
324 blocks: Vec<Block>,
326 queue: Vec<usize>,
328 max_operand_depth: usize,
330}
331
332impl<'a, C: Context> FuncVerifier<'a, C> {
333 fn new(ctx: &'a C, index: usize, func: &'a Func<C::Phase>, used: &'a mut [Used]) -> Self {
334 Self {
335 ctx,
336 index,
337 func,
338 used,
339 offsets: Vec::new(),
340 blocks: Vec::new(),
341 queue: Vec::new(),
342 max_operand_depth: 0,
343 }
344 }
345
346 fn insts<'b>(
348 code: &'b [u8],
349 start: usize,
350 ) -> impl Iterator<Item = super::DecResult<InstOffsets>> + 'b {
351 let mut cursor = io::Cursor::new(code);
352 cursor.set_position(start as u64);
353 InstDecoder::new(cursor).with_offsets()
354 }
355
356 fn insts_verified<'b>(code: &'b [u8], start: usize) -> impl Iterator<Item = InstOffsets> + 'b {
358 Self::insts(code, start).map(|i| i.expect("I/O error on slice?!"))
359 }
360
361 fn syntax(&mut self) -> FuncResult<()> {
363 use Inst::*;
364 let bytecode_len = self.ctx.slice(&self.func.bytecode).len();
365 if self
366 .func
367 .upvars
368 .iter()
369 .fold(0usize, |acc, count| acc.saturating_add(*count))
370 > limit::UPVAR_TOTAL
371 {
372 return Err(FuncError::UpvarLimitExceeded);
373 }
374
375 let mut need_bb = true;
376
377 let bytecode = self.ctx.slice(&self.func.bytecode);
378
379 let mut last = InstOffsets {
380 before: 0usize,
381 inst: Inst::Ret,
382 after: 0usize,
383 };
384
385 for item in Self::insts(bytecode, 0) {
386 last = item.map_err(|e| match e {
387 DecError::InvalidOpcode(offset) => {
388 FuncError::inst(bytecode, offset, InstError::InvalidOpcode)
389 }
390 DecError::Truncated(offset) => {
391 FuncError::inst(bytecode, offset, InstError::Truncated)
392 }
393 DecError::IntegerOverflow(offset) => {
394 FuncError::inst(bytecode, offset, InstError::IntegerOverflow)
395 }
396 _ => unreachable!(),
397 })?;
398 let InstOffsets {
399 before,
400 inst,
401 after,
402 } = last;
403 self.offsets.push(OffsetInfo {
404 offset: before,
405 start_bb: need_bb,
406 });
407 need_bb = false;
408
409 match inst {
410 Add | Div | Ediv | Dup | Mod | Mul | Neg | Not | BitNot | BitAnd | BitOr
412 | BitXor | Pop | Eq | Ne | Gt | Lt | Gte | Lte | Sub | Ret | PopUpvars | Index
413 | Assign | Next => (),
414 LoadUpvar(_, _) | StoreUpvar(_, _) | PushUpvars(_) => (),
416 Swap(i, j) => {
419 if i == j {
420 return Err(FuncError::inst(bytecode, before, InstError::NoOpSwap));
421 }
422 }
423 Call(index) => self.ctx.pack(index).map(|_| ()).ok_or(FuncError::inst(
425 bytecode,
426 before,
427 InstError::PackIndexOutOfBounds,
428 ))?,
429 MethodCall(sym, sig) => {
431 if !self.ctx.symbol_valid(sym) {
432 return Err(FuncError::inst(
433 bytecode,
434 before,
435 InstError::SymIndexOutOfBounds,
436 ));
437 }
438 self.ctx.pack(sig).map(|_| ()).ok_or(FuncError::inst(
439 bytecode,
440 before,
441 InstError::PackIndexOutOfBounds,
442 ))?
443 }
444 Builtin(idx, sig) => {
446 if idx >= BUILTINS.len() {
447 return Err(FuncError::inst(
448 bytecode,
449 before,
450 InstError::BuiltinIndexOutOfBounds,
451 ));
452 }
453 self.ctx.pack(sig).map(|_| ()).ok_or(FuncError::inst(
454 bytecode,
455 before,
456 InstError::PackIndexOutOfBounds,
457 ))?
458 }
459 LoadConst(index) => {
461 if !self.ctx.constant_valid(index) {
462 return Err(FuncError::inst(
463 bytecode,
464 before,
465 InstError::ConstIndexOutOfBounds,
466 ));
467 }
468 }
469 LoadLocal(index) | StoreLocal(index) => {
471 if index >= self.func.locals {
472 return Err(FuncError::inst(
473 bytecode,
474 before,
475 InstError::LocalIndexOutOfBounds,
476 ));
477 }
478 }
479 Get(index) | Set(index) => {
481 if !self.ctx.symbol_valid(index) {
482 return Err(FuncError::inst(
483 bytecode,
484 before,
485 InstError::SymIndexOutOfBounds,
486 ));
487 }
488 }
489 Close(index) => {
492 self.ctx.function(index).map(|_| ()).ok_or(FuncError::inst(
493 bytecode,
494 before,
495 InstError::FuncIndexOutOfBounds,
496 ))?;
497 self.used[self.index].uses.insert(index);
499 }
500 Branch(target) | BranchTrue(target) | BranchFalse(target) => {
503 after
504 .checked_add_signed(target)
505 .and_then(|abs| if abs >= bytecode_len { None } else { Some(()) })
506 .ok_or(FuncError::inst(
507 bytecode,
508 before,
509 InstError::BranchTargetOutOfBounds,
510 ))?;
511 need_bb = true;
513 }
514 Reify(index) => {
516 let _ = self.ctx.pack(index).ok_or(FuncError::inst(
517 bytecode,
518 before,
519 InstError::PackIndexOutOfBounds,
520 ))?;
521 }
522 Unpack(index) => {
524 self.ctx.unpack_arity(index).ok_or(FuncError::inst(
525 bytecode,
526 before,
527 InstError::UnpackIndexOutOfBounds,
528 ))?;
529 }
530 NlGuard(index) => {
532 self.ctx.function(index).map(|_| ()).ok_or(FuncError::inst(
533 bytecode,
534 before,
535 InstError::FuncIndexOutOfBounds,
536 ))?;
537 self.used[self.index].uses.insert(index);
538 }
539 NlBranch(_, _) => {
542 need_bb = true;
543 }
544 }
545 }
546 match last.inst {
547 Ret | Branch(_) | BranchTrue(_) | BranchFalse(_) | NlBranch(_, _) => (),
548 _ => return Err(FuncError::UnexpectedEnd),
549 }
550 Ok(())
551 }
552
553 fn basic_blocks(&mut self, cert: Option<&Certificate>) -> FuncResult<()> {
555 use Inst::*;
556
557 let bytecode = self.ctx.slice(&self.func.bytecode);
558
559 for item in Self::insts_verified(bytecode, 0) {
560 let InstOffsets {
561 before,
562 inst,
563 after,
564 } = item;
565 match inst {
566 Branch(target) | BranchTrue(target) | BranchFalse(target) => {
568 let offset = after.strict_add_signed(target);
569 match self.offsets.binary_search_by_key(
570 &offset,
571 |OffsetInfo {
572 offset,
573 start_bb: _,
574 }| *offset,
575 ) {
576 Ok(index) => self.offsets[index].start_bb = true,
578 Err(_) => {
579 return Err(FuncError::inst(
580 bytecode,
581 before,
582 InstError::BranchTargetInvalid,
583 ));
584 }
585 }
586 }
587 _ => (),
588 }
589 }
590
591 if let Some(cert) = cert {
593 let offsets: Vec<_> = self
595 .offsets
596 .iter()
597 .filter_map(
598 |OffsetInfo { offset, start_bb }| if *start_bb { Some(*offset) } else { None },
599 )
600 .collect();
601 if cert.blocks.len() != offsets.len() - 1
604 || !offsets
605 .iter()
606 .skip(1)
607 .zip(cert.blocks.iter())
608 .all(|(b, c)| *b == c.0)
609 {
610 return Err(FuncError::BasicBlockMismatch);
611 }
612
613 self.blocks.push(Block {
616 offset: 0,
617 mark: false,
618 state: BlockState::neutral(),
619 });
620 self.blocks
621 .extend(cert.blocks.iter().map(|(offset, state)| Block {
622 offset: *offset,
623 mark: false,
624 state: state.clone(),
625 }));
626 } else {
627 for OffsetInfo {
629 offset,
630 start_bb: bb,
631 } in self.offsets.iter()
632 {
633 if *bb {
634 self.blocks.push(Block {
635 offset: *offset,
636 mark: false,
637 state: BlockState::neutral(),
638 });
639 }
640 }
641 }
642
643 Ok(())
644 }
645
646 fn step(&mut self, block: &mut BlockState, inst: &Inst) -> FlowResult<()> {
648 use Inst::*;
649
650 match inst {
651 Branch(_) => (),
652 Neg | Not | BitNot => {
653 block.pop()?;
654 block.push()
655 }
656 Add | Div | Ediv | Mod | Mul | BitAnd | BitOr | BitXor | Eq | Ne | Gt | Lt | Gte
657 | Lte | Sub => {
658 block.pop()?;
659 block.pop()?;
660 block.push()
661 }
662 Dup => {
663 block.pop()?;
664 block.push();
665 block.push();
666 }
667 Swap(i, j) => {
668 if *i >= block.operands {
669 return Err(InstError::OperandIndexOutOfBounds(*i));
670 }
671 if *j >= block.operands {
672 return Err(InstError::OperandIndexOutOfBounds(*j));
673 }
674 }
675 Pop | BranchTrue(_) | BranchFalse(_) => {
676 block.pop()?;
677 }
678 LoadLocal(index) => {
679 if block.locals[*index] == LocalState::Invalid {
680 return Err(InstError::LocalLoadUninitialized);
681 }
682 block.push()
683 }
684 StoreLocal(index) => {
685 block.pop()?;
686 block.locals[*index] = LocalState::Value
687 }
688 Call(index) | MethodCall(_, index) => {
689 let arity = self.ctx.pack(*index).unwrap().count();
690 for _ in 0..arity {
691 block.pop()?;
692 }
693 block.pop()?;
694 block.push()
695 }
696 Builtin(_, sig) => {
697 let arity = self.ctx.pack(*sig).unwrap().count();
698 for _ in 0..arity {
699 block.pop()?;
700 }
701 block.push()
702 }
703 LoadConst(_) => block.push(),
704 LoadUpvar(index, depth) | StoreUpvar(index, depth) => {
705 if *depth >= block.upvars.len() {
706 return Err(InstError::UpvarDepthOutOfBounds);
707 }
708 if *index >= block.upvars[block.upvars.len() - 1 - *depth] {
709 return Err(InstError::UpvarIndexOutOfBounds);
710 }
711 if matches!(inst, LoadUpvar(..)) {
712 block.push()
713 } else {
714 block.pop()?
715 }
716 }
717 Get(_) => {
718 block.pop()?;
719 block.push()
720 }
721 Set(_) => {
722 block.pop()?;
723 block.pop()?
724 }
725 Index => {
726 block.pop()?;
727 block.pop()?;
728 block.push();
729 }
730 Assign => {
731 block.pop()?;
732 block.pop()?;
733 block.pop()?;
734 }
735 PushUpvars(count) => {
736 block.upvars.push(*count);
737 if block
739 .upvars
740 .iter()
741 .fold(0usize, |acc, count| acc.saturating_add(*count))
742 > limit::UPVAR_TOTAL
743 {
744 return Err(InstError::UpvarPushExceedsLimit);
745 }
746 }
747 PopUpvars => {
748 if block.upvars.len() == self.func.upvars.len() {
749 return Err(InstError::UpvarUnderflow);
750 }
751 block.upvars.pop();
752 }
753 Close(index) => {
754 let func = self
755 .ctx
756 .function(*index)
757 .ok_or(InstError::FuncIndexOutOfBounds)?;
758 if func.upvars != block.upvars {
759 return Err(InstError::UpvarSigMismatch);
760 }
761 block.push()
762 }
763 Ret => {
764 block.pop()?;
765 if block.operands != 0 {
766 return Err(InstError::ReturnWithExcessOperands);
767 }
768 if block.upvars.len() != self.func.upvars.len() {
769 return Err(InstError::ReturnWithExcessUpvars);
770 }
771 }
772 Reify(index) => {
773 if block.upvars.len() == self.func.upvars.len() {
774 return Err(InstError::UpvarUnderflow);
775 }
776 let count = block.upvars.pop().unwrap();
777 let arity = self.ctx.pack(*index).unwrap().count();
778 if count != arity {
779 return Err(InstError::ReifyMismatch(count, arity));
780 }
781 block.push()
782 }
783 Next => {
784 block.pop()?;
785 block.push();
786 block.push();
787 }
788 Unpack(index) => {
789 let arity = self.ctx.unpack_arity(*index).unwrap();
790 block.pop()?;
791 for _ in 0..arity {
792 block.push();
793 }
794 }
795 NlGuard(index) => {
796 let func = self
797 .ctx
798 .function(*index)
799 .ok_or(InstError::FuncIndexOutOfBounds)?;
800 block.upvars.push(0);
801 if func.upvars != block.upvars {
802 return Err(InstError::UpvarSigMismatch);
803 }
804 block.upvars.pop();
805 block.push();
807 block.push();
808 }
809 NlBranch(depth, _) => {
810 if *depth >= block.upvars.len() {
812 return Err(InstError::UpvarDepthOutOfBounds);
813 }
814 }
815 }
816
817 Ok(())
818 }
819
820 fn dataflow(&mut self, cert: Option<&Certificate>) -> FuncResult<()> {
822 use Inst::*;
823
824 let bytecode = self.ctx.slice(&self.func.bytecode);
825
826 let operands = self
828 .ctx
829 .unpack_arity(self.func.sig)
830 .expect("no unpack table entry for function");
832
833 self.blocks[0].state = BlockState {
834 operands,
835 locals: vec![LocalState::Invalid; self.func.locals],
836 upvars: self.func.upvars.clone(),
837 };
838 self.blocks[0].mark = true;
839
840 if operands > self.max_operand_depth {
841 self.max_operand_depth = operands
842 }
843
844 self.queue.push(0);
845
846 while let Some(bb) = self.queue.pop() {
847 let start = self.blocks[bb].offset;
848 let end = self.blocks.get(bb + 1).map(|b| b.offset);
849 let mut block = self.blocks[bb].state.clone();
850 let slice = if let Some(end) = end {
852 &bytecode[0..end]
853 } else {
854 bytecode
855 };
856
857 if cert.is_none() {
859 self.blocks[bb].mark = false;
860 }
861
862 #[cfg(feature = "debug")]
863 eprintln!(" bb #{bb}: {}", block);
864
865 let mut last = InstOffsets {
867 before: 0usize,
868 inst: Inst::Pop,
869 after: 0usize,
870 };
871
872 #[cfg(feature = "debug")]
874 let width = ((bytecode.len() - 1).max(1).ilog2() + 1).div_ceil(4).max(2) as usize;
875
876 for item in Self::insts_verified(slice, start) {
878 last = item;
879 #[cfg(feature = "debug")]
880 eprintln!(" {:0width$x} {}", last.before, last.inst);
881 self.step(&mut block, &last.inst)
882 .map_err(|e| FuncError::inst(slice, last.before, e))?;
883 #[cfg(feature = "debug")]
884 eprintln!(" ⮡ {}", block);
885 if block.operands > self.max_operand_depth {
886 self.max_operand_depth = block.operands
887 }
888 }
889
890 let next = match last.inst {
892 Branch(_) | Ret | NlBranch(_, _) => None,
893 _ => {
894 if bb + 1 != self.blocks.len() {
895 Some(bb + 1)
896 } else {
897 None
898 }
899 }
900 };
901
902 let branch = match last.inst {
904 Branch(index) | BranchTrue(index) | BranchFalse(index) => Some(
905 self.blocks
906 .binary_search_by_key(
907 &last.after.strict_add_signed(index),
908 |Block {
909 offset,
910 mark: _,
911 state: _,
912 }| { *offset },
913 )
914 .unwrap(),
915 ),
916 _ => None,
917 };
918
919 for sid in next.into_iter().chain(branch.into_iter()) {
921 let succ = &mut self.blocks[sid];
922 if cert.is_some() {
923 if !succ.mark {
926 succ.mark = true;
927 #[cfg(feature = "debug")]
928 eprintln!(" mark #{}", sid);
929 self.queue.push(sid);
930 }
931 let mut claimed = succ.state.clone();
934 block
935 .merge(&mut claimed)
936 .map_err(|e| FuncError::inst(bytecode, succ.offset, e))?;
937 if claimed != succ.state {
938 return Err(FuncError::inst(
939 bytecode,
940 succ.offset,
941 InstError::NotAFixedPoint,
942 ));
943 }
944 } else if succ.mark {
945 #[cfg(feature = "debug")]
948 eprintln!(" merge #{}", sid);
949 block
950 .merge(&mut succ.state)
951 .map_err(|e| FuncError::inst(bytecode, succ.offset, e))?
952 } else {
953 let prev = succ.state.clone();
956 block
957 .merge(&mut succ.state)
958 .map_err(|e| FuncError::inst(bytecode, succ.offset, e))?;
959 if succ.state != prev {
961 #[cfg(feature = "debug")]
962 eprintln!(" changed #{}", sid);
963 succ.mark = true;
964 self.queue.push(sid);
965 }
966 }
967 }
968 }
969
970 if cert.is_some() {
974 for block in self.blocks.iter() {
975 if !block.mark {
976 return Err(FuncError::inst(
977 bytecode,
978 block.offset,
979 InstError::InstructionUnreachable,
980 ));
981 }
982 }
983 }
984
985 Ok(())
986 }
987
988 pub fn compute(mut self) -> FuncResult<Certificate> {
990 self.syntax()?;
991 self.basic_blocks(None)?;
992 self.dataflow(None)?;
993
994 Ok(Certificate {
995 max_operand_depth: self.max_operand_depth,
996 blocks: self
997 .blocks
998 .into_iter()
999 .skip(1)
1000 .map(|b| (b.offset, b.state))
1001 .collect(),
1002 })
1003 }
1004
1005 pub fn check(&mut self, cert: &Certificate) -> FuncResult<()> {
1007 self.syntax()?;
1008 self.basic_blocks(Some(cert))?;
1009 self.dataflow(Some(cert))?;
1010
1011 if cert.max_operand_depth != self.max_operand_depth {
1013 return Err(FuncError::OperandDepthMismatch);
1014 }
1015
1016 Ok(())
1017 }
1018}
1019
1020pub struct Verifier<'a, C: Context> {
1021 ctx: &'a C,
1023}
1024
1025impl<'a, C: Context> Verifier<'a, C> {
1026 pub fn new(ctx: &'a C) -> Self {
1027 Self { ctx }
1028 }
1029
1030 fn verify_used(used: &mut [Used]) -> Result<()> {
1034 let mut queue = Vec::new();
1035
1036 used[0].used = true;
1038 queue.push(0);
1039
1040 while let Some(index) = queue.pop() {
1041 for reli in used[index].uses.clone().into_iter() {
1042 let rel = &mut used[reli];
1043 if !rel.used {
1044 rel.used = true;
1045 queue.push(reli);
1046 }
1047 }
1048 }
1049
1050 for (i, used) in used.iter().enumerate() {
1051 if !used.used {
1052 return Err(Error {
1053 func: i,
1054 error: FuncError::Unused,
1055 });
1056 }
1057 }
1058
1059 Ok(())
1060 }
1061
1062 pub fn compute(
1063 &self,
1064 funcs: impl IntoIterator<Item = &'a Func<C::Phase>>,
1065 ) -> Result<Box<[Certificate]>> {
1066 let mut certs = Vec::new();
1067 let funcs: Vec<_> = funcs.into_iter().collect();
1068 let mut used = vec![Default::default(); funcs.len()];
1069 for (i, func) in funcs.into_iter().enumerate() {
1070 #[cfg(feature = "debug")]
1071 eprintln!("Compute cert #{i}:");
1072 let verifier = FuncVerifier::new(self.ctx, i, func, &mut used);
1073 certs.push(
1074 verifier
1075 .compute()
1076 .map_err(|e| Error { func: i, error: e })?,
1077 );
1078 }
1079
1080 Self::verify_used(&mut used)?;
1081
1082 Ok(certs.into())
1083 }
1084
1085 pub fn check(
1086 &self,
1087 funcs: impl IntoIterator<Item = (&'a Func<C::Phase>, &'a Certificate)>,
1088 ) -> Result<()> {
1089 let funcs: Vec<_> = funcs.into_iter().collect();
1090 let mut used = vec![Default::default(); funcs.len()];
1091 for (i, (func, cert)) in funcs.into_iter().enumerate() {
1092 #[cfg(feature = "debug")]
1093 eprintln!("Check cert #{i}:");
1094 let mut verifier = FuncVerifier::new(self.ctx, i, func, &mut used);
1095 verifier
1096 .check(cert)
1097 .map_err(|e| Error { func: i, error: e })?
1098 }
1099
1100 Self::verify_used(&mut used)?;
1101
1102 Ok(())
1103 }
1104}
1105
1106#[cfg(test)]
1107mod test {
1108 use super::super::{Encode, Func, Opcode};
1109 use super::*;
1110
1111 struct Mock {
1112 packs: Vec<Vec<Arg>>,
1113 unpack_arities: Vec<usize>,
1114 symbols: usize,
1115 constants: usize,
1116 }
1117
1118 struct Input {
1119 sig: usize,
1120 locals: usize,
1121 upvars: Vec<usize>,
1122 bytecode: Box<[u8]>,
1123 }
1124
1125 struct Context {
1126 mock: Mock,
1127 funcs: Vec<Func<Test>>,
1128 }
1129
1130 struct Test;
1131
1132 impl Phase for Test {
1133 type Bytes = Box<[u8]>;
1134 }
1135
1136 impl super::Context for Context {
1137 type Phase = Test;
1138
1139 fn slice<'a>(&'a self, bytes: &'a <Self::Phase as Phase>::Bytes) -> &'a [u8] {
1140 bytes
1141 }
1142
1143 fn function(&self, index: usize) -> Option<&Func<Self::Phase>> {
1144 self.funcs.get(index)
1145 }
1146
1147 fn pack(&self, index: usize) -> Option<impl Iterator<Item = Arg>> {
1148 self.mock.packs.get(index).map(|v| v.iter().cloned())
1149 }
1150
1151 fn unpack_arity(&self, index: usize) -> Option<usize> {
1152 self.mock.unpack_arities.get(index).copied()
1153 }
1154
1155 fn symbol_valid(&self, index: usize) -> bool {
1156 index < self.mock.symbols
1157 }
1158
1159 fn constant_valid(&self, index: usize) -> bool {
1160 index < self.mock.constants
1161 }
1162 }
1163
1164 fn assemble(insts: &[Inst]) -> Box<[u8]> {
1165 let mut offsets = vec![0; insts.len() + 1];
1166 let mut bytecode = Vec::new();
1167 let mut changing = true;
1168
1169 while changing {
1170 changing = false;
1171 bytecode.clear();
1172
1173 for (i, inst) in insts.iter().enumerate() {
1174 let offset = bytecode.len();
1175 if offsets[i] != offset {
1176 changing = true;
1177 offsets[i] = offset
1178 }
1179 match inst {
1180 Inst::Branch(t) | Inst::BranchTrue(t) | Inst::BranchFalse(t) => {
1181 let offset = offsets[*t as usize] as isize - offsets[i + 1] as isize;
1182 match inst {
1183 Inst::Branch(_) => Inst::Branch(offset).encode(&mut bytecode).unwrap(),
1184 Inst::BranchTrue(_) => {
1185 Inst::BranchTrue(offset).encode(&mut bytecode).unwrap()
1186 }
1187 Inst::BranchFalse(_) => {
1188 Inst::BranchFalse(offset).encode(&mut bytecode).unwrap()
1189 }
1190 _ => unreachable!(),
1191 }
1192 }
1193 _ => inst.encode(&mut bytecode).unwrap(),
1194 }
1195 }
1196
1197 if offsets[insts.len()] != bytecode.len() {
1198 offsets[insts.len()] = bytecode.len();
1199 changing = true
1200 }
1201 }
1202
1203 bytecode.into()
1204 }
1205
1206 fn encode_raw(insts: &[Inst]) -> Box<[u8]> {
1207 let mut bytecode = Vec::new();
1208 for inst in insts {
1209 inst.encode(&mut bytecode).unwrap();
1210 }
1211 bytecode.into()
1212 }
1213
1214 fn link(mock: Mock, funcs: Vec<Input>) -> Context {
1215 Context {
1216 mock,
1217 funcs: funcs
1218 .into_iter()
1219 .map(
1220 |Input {
1221 sig,
1222 locals,
1223 upvars,
1224 bytecode,
1225 }| Func {
1226 bytecode,
1227 sig,
1228 locals,
1229 upvars,
1230 },
1231 )
1232 .collect(),
1233 }
1234 }
1235
1236 fn func(locals: usize, upvars: Vec<usize>, insts: Vec<Inst>) -> Input {
1237 Input {
1238 sig: 0,
1239 locals,
1240 upvars,
1241 bytecode: assemble(&insts),
1242 }
1243 }
1244
1245 fn raw_func(locals: usize, upvars: Vec<usize>, insts: Vec<Inst>) -> Input {
1246 Input {
1247 sig: 0,
1248 locals,
1249 upvars,
1250 bytecode: encode_raw(&insts),
1251 }
1252 }
1253
1254 fn run(ctx: &Context) -> super::Result<Box<[Certificate]>> {
1255 let verifier = Verifier::new(ctx);
1256 let certs = verifier.compute(ctx.funcs.iter())?;
1257 verifier.check(ctx.funcs.iter().zip(certs.iter()))?;
1258 Ok(certs)
1259 }
1260
1261 fn inst_error(ctx: Context, expect: InstError) {
1262 let res = run(&ctx);
1263 match res {
1264 Err(
1265 ref e @ Error {
1266 error: FuncError::Inst(_, ref got, _),
1267 ..
1268 },
1269 ) if expect == *got => {
1270 eprintln!("expected error: {e}");
1271 }
1272 _ => panic!("unexpected result: {res:?}"),
1273 }
1274 }
1275
1276 fn func_error(ctx: Context, expect: FuncError) {
1277 let res = run(&ctx);
1278 match res {
1279 Err(ref e @ Error { error: ref got, .. }) if expect == *got => {
1280 eprintln!("expected error: {e}");
1281 }
1282 _ => panic!("unexpected result: {res:?}"),
1283 }
1284 }
1285
1286 fn check_error(ctx: &Context, certs: &[Certificate], expect: FuncError) {
1287 let res = Verifier::new(ctx).check(ctx.funcs.iter().zip(certs.iter()));
1288 match res {
1289 Err(ref e @ Error { error: ref got, .. }) if expect == *got => {
1290 eprintln!("expected error: {e}");
1291 }
1292 _ => panic!("unexpected result: {res:?}"),
1293 }
1294 }
1295
1296 fn default_mock() -> Mock {
1297 Mock {
1298 packs: vec![],
1299 unpack_arities: vec![0],
1300 symbols: 0,
1301 constants: 1,
1302 }
1303 }
1304
1305 #[test]
1306 fn pc_cliff() {
1307 use Inst::*;
1308
1309 let mock = default_mock();
1310 let inputs = vec![func(0, vec![], vec![LoadConst(0)])];
1311
1312 func_error(link(mock, inputs), FuncError::UnexpectedEnd);
1313 }
1314
1315 #[test]
1316 fn empty_pop() {
1317 use Inst::*;
1318
1319 let mock = Mock {
1320 constants: 0,
1321 ..default_mock()
1322 };
1323 let inputs = vec![func(0, vec![], vec![Pop, Ret])];
1324
1325 inst_error(link(mock, inputs), InstError::OperandUnderflow);
1326 }
1327
1328 #[test]
1329 fn ret_excessive_operands() {
1330 use Inst::*;
1331
1332 let mock = Mock {
1333 packs: vec![],
1334 unpack_arities: vec![0],
1335 symbols: 0,
1336 constants: 1,
1337 };
1338
1339 let inputs = vec![func(0, vec![], vec![LoadConst(0), Dup, Ret])];
1340
1341 inst_error(link(mock, inputs), InstError::ReturnWithExcessOperands);
1342 }
1343
1344 #[test]
1345 fn ret_excessive_upvars() {
1346 use Inst::*;
1347
1348 let mock = Mock {
1349 packs: vec![],
1350 unpack_arities: vec![0],
1351 symbols: 0,
1352 constants: 1,
1353 };
1354
1355 let inputs = vec![func(0, vec![], vec![LoadConst(0), PushUpvars(1), Ret])];
1356
1357 inst_error(link(mock, inputs), InstError::ReturnWithExcessUpvars);
1358 }
1359
1360 #[test]
1361 fn operand_overflow() {
1362 use Inst::*;
1363
1364 let mock = Mock {
1365 packs: vec![],
1366 unpack_arities: vec![0],
1367 symbols: 0,
1368 constants: 1,
1369 };
1370
1371 let inputs = vec![func(0, vec![], vec![LoadConst(0), Branch(0)])];
1372
1373 inst_error(
1374 link(mock, inputs),
1375 InstError::OperandDepthConflict { from: 1, into: 0 },
1376 );
1377 }
1378
1379 #[test]
1380 fn noop_swap() {
1381 use Inst::*;
1382
1383 let mock = Mock {
1384 packs: vec![],
1385 unpack_arities: vec![0],
1386 symbols: 0,
1387 constants: 1,
1388 };
1389
1390 let inputs = vec![func(0, vec![], vec![LoadConst(0), Swap(0, 0), Ret])];
1391
1392 inst_error(link(mock, inputs), InstError::NoOpSwap);
1393 }
1394
1395 #[test]
1396 fn const_index_out_of_bounds() {
1397 use Inst::*;
1398
1399 let mock = Mock {
1400 packs: vec![],
1401 unpack_arities: vec![0],
1402 symbols: 0,
1403 constants: 1,
1404 };
1405
1406 let inputs = vec![func(0, vec![], vec![LoadConst(1), Ret])];
1407
1408 inst_error(link(mock, inputs), InstError::ConstIndexOutOfBounds);
1409 }
1410
1411 #[test]
1412 fn sym_index_out_of_bounds() {
1413 use Inst::*;
1414
1415 let mock = Mock {
1416 packs: vec![],
1417 unpack_arities: vec![0],
1418 symbols: 1,
1419 constants: 0,
1420 };
1421
1422 let inputs = vec![func(0, vec![], vec![Get(1), Ret])];
1423
1424 inst_error(link(mock, inputs), InstError::SymIndexOutOfBounds);
1425 }
1426
1427 #[test]
1428 fn builtin_index_out_of_bounds() {
1429 use Inst::*;
1430
1431 let mock = Mock {
1432 packs: vec![vec![]],
1433 unpack_arities: vec![0],
1434 symbols: 0,
1435 constants: 0,
1436 };
1437
1438 let inputs = vec![func(0, vec![], vec![Builtin(100, 0), Ret])];
1439
1440 inst_error(link(mock, inputs), InstError::BuiltinIndexOutOfBounds);
1441 }
1442
1443 #[test]
1444 fn local_index_out_of_bounds() {
1445 use Inst::*;
1446
1447 let mock = Mock {
1448 packs: vec![],
1449 unpack_arities: vec![0],
1450 symbols: 0,
1451 constants: 0,
1452 };
1453
1454 let inputs = vec![func(1, vec![], vec![LoadLocal(1), Ret])];
1455
1456 inst_error(link(mock, inputs), InstError::LocalIndexOutOfBounds);
1457 }
1458
1459 #[test]
1460 fn local_load_uninitialized() {
1461 use Inst::*;
1462
1463 let mock = Mock {
1464 packs: vec![],
1465 unpack_arities: vec![0],
1466 symbols: 0,
1467 constants: 0,
1468 };
1469
1470 let inputs = vec![func(1, vec![], vec![LoadLocal(0), Ret])];
1471
1472 inst_error(link(mock, inputs), InstError::LocalLoadUninitialized);
1473 }
1474
1475 #[test]
1476 fn upvar_depth_out_of_bounds() {
1477 use Inst::*;
1478
1479 let mock = Mock {
1480 packs: vec![],
1481 unpack_arities: vec![0],
1482 symbols: 0,
1483 constants: 0,
1484 };
1485
1486 let inputs = vec![func(0, vec![], vec![LoadUpvar(0, 1), Ret])];
1487
1488 inst_error(link(mock, inputs), InstError::UpvarDepthOutOfBounds);
1489 }
1490
1491 #[test]
1492 fn upvar_index_out_of_bounds() {
1493 use Inst::*;
1494
1495 let mock = Mock {
1496 packs: vec![],
1497 unpack_arities: vec![0],
1498 symbols: 0,
1499 constants: 0,
1500 };
1501
1502 let inputs = vec![func(0, vec![1], vec![LoadUpvar(2, 0), Ret])];
1503
1504 inst_error(link(mock, inputs), InstError::UpvarIndexOutOfBounds);
1505 }
1506
1507 #[test]
1508 fn pack_index_out_of_bounds() {
1509 use Inst::*;
1510
1511 let mock = Mock {
1512 packs: vec![vec![]],
1513 unpack_arities: vec![0],
1514 symbols: 0,
1515 constants: 0,
1516 };
1517
1518 let inputs = vec![func(0, vec![], vec![Call(1), Ret])];
1519
1520 inst_error(link(mock, inputs), InstError::PackIndexOutOfBounds);
1521 }
1522
1523 #[test]
1524 fn unpack_index_out_of_bounds() {
1525 use Inst::*;
1526
1527 let mock = Mock {
1528 packs: vec![],
1529 unpack_arities: vec![0],
1530 symbols: 0,
1531 constants: 0,
1532 };
1533
1534 let inputs = vec![func(0, vec![], vec![Unpack(1), Ret])];
1535
1536 inst_error(link(mock, inputs), InstError::UnpackIndexOutOfBounds);
1537 }
1538
1539 #[test]
1540 fn func_index_out_of_bounds() {
1541 use Inst::*;
1542
1543 let mock = Mock {
1544 packs: vec![],
1545 unpack_arities: vec![0],
1546 symbols: 0,
1547 constants: 0,
1548 };
1549
1550 let inputs = vec![func(0, vec![], vec![Close(1), Ret])];
1551
1552 inst_error(link(mock, inputs), InstError::FuncIndexOutOfBounds);
1553 }
1554
1555 #[test]
1556 fn upvar_sig_mismatch() {
1557 use Inst::*;
1558
1559 let mock = Mock {
1560 packs: vec![],
1561 unpack_arities: vec![0],
1562 symbols: 0,
1563 constants: 0,
1564 };
1565
1566 let inputs = vec![func(0, vec![], vec![PushUpvars(1), Close(0), Ret])];
1567
1568 inst_error(link(mock, inputs), InstError::UpvarSigMismatch);
1569 }
1570
1571 #[test]
1572 fn reify_mismatch() {
1573 use super::super::Arg;
1574 use Inst::*;
1575
1576 let mock = Mock {
1577 packs: vec![vec![Arg::Value, Arg::Value]],
1578 unpack_arities: vec![0],
1579 symbols: 0,
1580 constants: 0,
1581 };
1582
1583 let inputs = vec![func(0, vec![], vec![PushUpvars(1), Reify(0), Ret])];
1584
1585 inst_error(link(mock, inputs), InstError::ReifyMismatch(1, 2));
1586 }
1587
1588 #[test]
1589 fn valid_straight_line_compute_and_check() {
1590 use Inst::*;
1591
1592 let ctx = link(
1593 default_mock(),
1594 vec![func(0, vec![], vec![LoadConst(0), Ret])],
1595 );
1596 run(&ctx).unwrap();
1597 }
1598
1599 #[test]
1600 fn valid_branching_compute_and_check() {
1601 use Inst::*;
1602
1603 let ctx = link(
1604 default_mock(),
1605 vec![func(
1606 0,
1607 vec![],
1608 vec![
1609 LoadConst(0),
1610 BranchTrue(4),
1611 LoadConst(0),
1612 Branch(5),
1613 LoadConst(0),
1614 Ret,
1615 ],
1616 )],
1617 );
1618 run(&ctx).unwrap();
1619 }
1620
1621 #[test]
1622 fn valid_multifunc_reachability() {
1623 use Inst::*;
1624
1625 let ctx = link(
1626 default_mock(),
1627 vec![
1628 func(0, vec![], vec![Close(1), Ret]),
1629 func(0, vec![], vec![Close(2), Ret]),
1630 func(0, vec![], vec![LoadConst(0), Ret]),
1631 ],
1632 );
1633 run(&ctx).unwrap();
1634 }
1635
1636 #[test]
1637 fn valid_local_init_path() {
1638 use Inst::*;
1639
1640 let ctx = link(
1641 default_mock(),
1642 vec![func(
1643 1,
1644 vec![],
1645 vec![LoadConst(0), StoreLocal(0), LoadLocal(0), Ret],
1646 )],
1647 );
1648 run(&ctx).unwrap();
1649 }
1650
1651 #[test]
1652 fn valid_method_call_builtin_and_unpack_paths() {
1653 use Inst::*;
1654
1655 let ctx = link(
1656 Mock {
1657 packs: vec![vec![Arg::Value]],
1658 unpack_arities: vec![0, 1],
1659 symbols: 1,
1660 constants: 1,
1661 },
1662 vec![func(
1663 0,
1664 vec![],
1665 vec![
1666 LoadConst(0),
1667 LoadConst(0),
1668 MethodCall(0, 0),
1669 Builtin(crate::builtin::THROW, 0),
1670 Unpack(1),
1671 Ret,
1672 ],
1673 )],
1674 );
1675 run(&ctx).unwrap();
1676 }
1677
1678 #[test]
1679 fn valid_get_set_index_assign_next_and_nonlocal_paths() {
1680 use Inst::*;
1681
1682 let ctx = link(
1683 Mock {
1684 packs: vec![],
1685 unpack_arities: vec![0],
1686 symbols: 1,
1687 constants: 1,
1688 },
1689 vec![func(
1690 0,
1691 vec![],
1692 vec![
1693 LoadConst(0),
1694 Get(0),
1695 LoadConst(0),
1696 Set(0),
1697 LoadConst(0),
1698 LoadConst(0),
1699 Index,
1700 Pop,
1701 LoadConst(0),
1702 LoadConst(0),
1703 LoadConst(0),
1704 Assign,
1705 LoadConst(0),
1706 Next,
1707 Pop,
1708 Ret,
1709 ],
1710 )],
1711 );
1712 run(&ctx).unwrap();
1713
1714 let nonlocal = link(
1715 Mock {
1716 packs: vec![],
1717 unpack_arities: vec![0],
1718 symbols: 0,
1719 constants: 1,
1720 },
1721 vec![
1722 func(0, vec![], vec![NlGuard(1), Pop, Pop, LoadConst(0), Ret]),
1723 func(0, vec![0], vec![LoadConst(0), Ret]),
1724 ],
1725 );
1726 run(&nonlocal).unwrap();
1727
1728 let nlbranch_only = link(
1729 Mock {
1730 packs: vec![],
1731 unpack_arities: vec![0],
1732 symbols: 0,
1733 constants: 1,
1734 },
1735 vec![func(0, vec![1], vec![NlBranch(0, 0)])],
1736 );
1737 run(&nlbranch_only).unwrap();
1738 }
1739
1740 #[test]
1741 fn invalid_opcode() {
1742 let ctx = link(
1743 Mock {
1744 constants: 0,
1745 ..default_mock()
1746 },
1747 vec![Input {
1748 sig: 0,
1749 locals: 0,
1750 upvars: vec![],
1751 bytecode: vec![0].into(),
1752 }],
1753 );
1754
1755 inst_error(ctx, InstError::InvalidOpcode);
1756 }
1757
1758 #[test]
1759 fn truncated_instruction() {
1760 let ctx = link(
1761 Mock {
1762 constants: 0,
1763 ..default_mock()
1764 },
1765 vec![Input {
1766 sig: 0,
1767 locals: 0,
1768 upvars: vec![],
1769 bytecode: vec![Opcode::Call as u8].into(),
1770 }],
1771 );
1772
1773 inst_error(ctx, InstError::Truncated);
1774 }
1775
1776 #[test]
1777 fn branch_target_out_of_bounds() {
1778 use Inst::*;
1779
1780 let ctx = link(default_mock(), vec![raw_func(0, vec![], vec![Branch(99)])]);
1781 inst_error(ctx, InstError::BranchTargetOutOfBounds);
1782 }
1783
1784 #[test]
1785 fn branch_target_invalid() {
1786 use Inst::*;
1787
1788 let ctx = link(
1789 default_mock(),
1790 vec![raw_func(0, vec![], vec![LoadConst(0), Branch(-3)])],
1791 );
1792 inst_error(ctx, InstError::BranchTargetInvalid);
1793 }
1794
1795 #[test]
1796 fn method_call_symbol_out_of_bounds() {
1797 use Inst::*;
1798
1799 let ctx = link(
1800 Mock {
1801 packs: vec![vec![Arg::Value]],
1802 unpack_arities: vec![0],
1803 symbols: 1,
1804 constants: 1,
1805 },
1806 vec![func(
1807 0,
1808 vec![],
1809 vec![LoadConst(0), LoadConst(0), MethodCall(1, 0), Ret],
1810 )],
1811 );
1812 inst_error(ctx, InstError::SymIndexOutOfBounds);
1813 }
1814
1815 #[test]
1816 fn builtin_pack_index_out_of_bounds() {
1817 use Inst::*;
1818
1819 let ctx = link(
1820 Mock {
1821 packs: vec![],
1822 unpack_arities: vec![0],
1823 symbols: 0,
1824 constants: 1,
1825 },
1826 vec![func(
1827 0,
1828 vec![],
1829 vec![LoadConst(0), Builtin(crate::builtin::ARGS, 0), Ret],
1830 )],
1831 );
1832 inst_error(ctx, InstError::PackIndexOutOfBounds);
1833 }
1834
1835 #[test]
1836 fn reify_pack_index_out_of_bounds() {
1837 use Inst::*;
1838
1839 let ctx = link(
1840 Mock {
1841 packs: vec![],
1842 unpack_arities: vec![0],
1843 symbols: 0,
1844 constants: 1,
1845 },
1846 vec![func(0, vec![], vec![PushUpvars(1), Reify(0), Ret])],
1847 );
1848 inst_error(ctx, InstError::PackIndexOutOfBounds);
1849 }
1850
1851 #[test]
1852 fn nlguard_func_index_out_of_bounds() {
1853 use Inst::*;
1854
1855 let ctx = link(default_mock(), vec![func(0, vec![], vec![NlGuard(1), Ret])]);
1856 inst_error(ctx, InstError::FuncIndexOutOfBounds);
1857 }
1858
1859 #[test]
1860 fn upvar_limit_exceeded_in_signature() {
1861 let ctx = link(
1862 default_mock(),
1863 vec![func(0, vec![limit::UPVAR_TOTAL + 1], vec![Inst::Ret])],
1864 );
1865 func_error(ctx, FuncError::UpvarLimitExceeded);
1866 }
1867
1868 #[test]
1869 fn swap_operand_index_out_of_bounds() {
1870 use Inst::*;
1871
1872 let ctx = link(
1873 default_mock(),
1874 vec![func(0, vec![], vec![LoadConst(0), Swap(1, 0), Ret])],
1875 );
1876 inst_error(ctx, InstError::OperandIndexOutOfBounds(1));
1877 }
1878
1879 #[test]
1880 fn store_local_operand_underflow() {
1881 use Inst::*;
1882
1883 let ctx = link(
1884 Mock {
1885 constants: 0,
1886 ..default_mock()
1887 },
1888 vec![func(1, vec![], vec![StoreLocal(0), Ret])],
1889 );
1890 inst_error(ctx, InstError::OperandUnderflow);
1891 }
1892
1893 #[test]
1894 fn store_upvar_operand_underflow() {
1895 use Inst::*;
1896
1897 let ctx = link(
1898 Mock {
1899 constants: 0,
1900 ..default_mock()
1901 },
1902 vec![func(0, vec![1], vec![StoreUpvar(0, 0), Ret])],
1903 );
1904 inst_error(ctx, InstError::OperandUnderflow);
1905 }
1906
1907 #[test]
1908 fn unary_and_binary_operand_underflow() {
1909 use Inst::*;
1910
1911 inst_error(
1912 link(
1913 Mock {
1914 constants: 0,
1915 ..default_mock()
1916 },
1917 vec![func(0, vec![], vec![Neg, Ret])],
1918 ),
1919 InstError::OperandUnderflow,
1920 );
1921 inst_error(
1922 link(
1923 Mock {
1924 constants: 0,
1925 ..default_mock()
1926 },
1927 vec![func(0, vec![], vec![Add, Ret])],
1928 ),
1929 InstError::OperandUnderflow,
1930 );
1931 }
1932
1933 #[test]
1934 fn get_set_index_and_assign_underflow() {
1935 use Inst::*;
1936
1937 inst_error(
1938 link(
1939 Mock {
1940 symbols: 1,
1941 constants: 0,
1942 ..default_mock()
1943 },
1944 vec![func(0, vec![], vec![Get(0), Ret])],
1945 ),
1946 InstError::OperandUnderflow,
1947 );
1948 inst_error(
1949 link(
1950 Mock {
1951 symbols: 1,
1952 constants: 1,
1953 ..default_mock()
1954 },
1955 vec![func(0, vec![], vec![LoadConst(0), Set(0), Ret])],
1956 ),
1957 InstError::OperandUnderflow,
1958 );
1959 inst_error(
1960 link(
1961 default_mock(),
1962 vec![func(0, vec![], vec![LoadConst(0), Index, Ret])],
1963 ),
1964 InstError::OperandUnderflow,
1965 );
1966 inst_error(
1967 link(
1968 default_mock(),
1969 vec![func(
1970 0,
1971 vec![],
1972 vec![LoadConst(0), LoadConst(0), Assign, Ret],
1973 )],
1974 ),
1975 InstError::OperandUnderflow,
1976 );
1977 }
1978
1979 #[test]
1980 fn call_method_and_builtin_operand_underflow() {
1981 use Inst::*;
1982
1983 inst_error(
1984 link(
1985 Mock {
1986 packs: vec![vec![Arg::Value]],
1987 unpack_arities: vec![0],
1988 symbols: 0,
1989 constants: 1,
1990 },
1991 vec![func(0, vec![], vec![LoadConst(0), Call(0), Ret])],
1992 ),
1993 InstError::OperandUnderflow,
1994 );
1995 inst_error(
1996 link(
1997 Mock {
1998 packs: vec![vec![Arg::Value]],
1999 unpack_arities: vec![0],
2000 symbols: 1,
2001 constants: 1,
2002 },
2003 vec![func(0, vec![], vec![LoadConst(0), MethodCall(0, 0), Ret])],
2004 ),
2005 InstError::OperandUnderflow,
2006 );
2007 inst_error(
2008 link(
2009 Mock {
2010 packs: vec![vec![Arg::Value]],
2011 unpack_arities: vec![0],
2012 symbols: 0,
2013 constants: 0,
2014 },
2015 vec![func(0, vec![], vec![Builtin(crate::builtin::ARGS, 0), Ret])],
2016 ),
2017 InstError::OperandUnderflow,
2018 );
2019 }
2020
2021 #[test]
2022 fn popupvars_underflow() {
2023 use Inst::*;
2024
2025 let ctx = link(default_mock(), vec![func(0, vec![], vec![PopUpvars, Ret])]);
2026 inst_error(ctx, InstError::UpvarUnderflow);
2027 }
2028
2029 #[test]
2030 fn push_upvars_exceeds_limit() {
2031 use Inst::*;
2032
2033 let ctx = link(
2034 default_mock(),
2035 vec![func(
2036 0,
2037 vec![],
2038 vec![PushUpvars(limit::UPVAR_TOTAL + 1), LoadConst(0), Ret],
2039 )],
2040 );
2041 inst_error(ctx, InstError::UpvarPushExceedsLimit);
2042 }
2043
2044 #[test]
2045 fn nlbranch_depth_out_of_bounds() {
2046 use Inst::*;
2047
2048 let ctx = link(
2049 Mock {
2050 constants: 0,
2051 ..default_mock()
2052 },
2053 vec![func(0, vec![], vec![NlBranch(0, 0)])],
2054 );
2055 inst_error(ctx, InstError::UpvarDepthOutOfBounds);
2056 }
2057
2058 #[test]
2059 fn upvar_depth_conflict() {
2060 use Inst::*;
2061
2062 let ctx = link(
2063 default_mock(),
2064 vec![func(
2065 0,
2066 vec![],
2067 vec![
2068 LoadConst(0),
2069 BranchTrue(5),
2070 PushUpvars(1),
2071 LoadConst(0),
2072 Branch(6),
2073 LoadConst(0),
2074 Ret,
2075 ],
2076 )],
2077 );
2078 inst_error(ctx, InstError::UpvarDepthConflict { from: 1, into: 0 });
2079 }
2080
2081 #[test]
2082 fn upvar_count_conflict() {
2083 use Inst::*;
2084
2085 let ctx = link(
2086 default_mock(),
2087 vec![func(
2088 0,
2089 vec![],
2090 vec![
2091 LoadConst(0),
2092 BranchTrue(4),
2093 PushUpvars(1),
2094 Branch(6),
2095 PushUpvars(2),
2096 Branch(6),
2097 PopUpvars,
2098 LoadConst(0),
2099 Ret,
2100 ],
2101 )],
2102 );
2103 inst_error(
2104 ctx,
2105 InstError::UpvarCountConflict {
2106 depth: 0,
2107 from: 1,
2108 into: 2,
2109 },
2110 );
2111 }
2112
2113 #[test]
2114 fn unused_function_is_rejected() {
2115 use Inst::*;
2116
2117 let ctx = link(
2118 default_mock(),
2119 vec![
2120 func(0, vec![], vec![LoadConst(0), Ret]),
2121 func(0, vec![], vec![LoadConst(0), Ret]),
2122 ],
2123 );
2124 func_error(ctx, FuncError::Unused);
2125 }
2126
2127 #[test]
2128 fn instruction_unreachable_during_check() {
2129 use Inst::*;
2130
2131 let ctx = link(
2132 default_mock(),
2133 vec![func(
2134 0,
2135 vec![],
2136 vec![LoadConst(0), Branch(4), LoadConst(0), Pop, Ret],
2137 )],
2138 );
2139 inst_error(ctx, InstError::InstructionUnreachable);
2140 }
2141
2142 #[test]
2143 fn certificate_basic_block_mismatch() {
2144 use Inst::*;
2145
2146 let ctx = link(
2147 default_mock(),
2148 vec![func(
2149 0,
2150 vec![],
2151 vec![
2152 LoadConst(0),
2153 BranchTrue(4),
2154 LoadConst(0),
2155 Branch(5),
2156 LoadConst(0),
2157 Ret,
2158 ],
2159 )],
2160 );
2161 let mut certs = Verifier::new(&ctx).compute(ctx.funcs.iter()).unwrap();
2162 assert!(certs[0].blocks.len() >= 2);
2163 certs[0].blocks.remove(0);
2164 check_error(&ctx, &certs, FuncError::BasicBlockMismatch);
2165 }
2166
2167 #[test]
2168 fn certificate_not_a_fixed_point() {
2169 use Inst::*;
2170
2171 let ctx = link(
2172 default_mock(),
2173 vec![func(
2174 0,
2175 vec![],
2176 vec![
2177 LoadConst(0),
2178 BranchTrue(4),
2179 LoadConst(0),
2180 Branch(5),
2181 LoadConst(0),
2182 Ret,
2183 ],
2184 )],
2185 );
2186 let mut certs = Verifier::new(&ctx).compute(ctx.funcs.iter()).unwrap();
2187 certs[0].blocks[0].1.operands = usize::MAX;
2188 let res = Verifier::new(&ctx).check(ctx.funcs.iter().zip(certs.iter()));
2189 match res {
2190 Err(Error {
2191 error: FuncError::Inst(offset, InstError::NotAFixedPoint, _),
2192 ..
2193 }) if offset == certs[0].blocks[0].0 => {}
2194 _ => panic!("unexpected result: {res:?}"),
2195 }
2196 }
2197
2198 #[test]
2199 fn certificate_operand_depth_mismatch() {
2200 use Inst::*;
2201
2202 let ctx = link(
2203 default_mock(),
2204 vec![func(0, vec![], vec![LoadConst(0), Ret])],
2205 );
2206 let mut certs = Verifier::new(&ctx).compute(ctx.funcs.iter()).unwrap();
2207 certs[0].max_operand_depth += 1;
2208 check_error(&ctx, &certs, FuncError::OperandDepthMismatch);
2209 }
2210}