Skip to main content

dolang_bytecode/
verify.rs

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            // Sentinel value indicating neutral element of merge operation
222            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    // Byte offset into bytecode
291    offset: usize,
292    // Should the instruction at this offset start a new basic block?
293    start_bb: bool,
294}
295
296struct Block {
297    // Byte offset into bytecode
298    offset: usize,
299    // Data flow state has changed (no input cert), or
300    // Block was reached (input cert)
301    mark: bool,
302    // Data flow state
303    state: BlockState,
304}
305
306// Tracks function use (closure construction) relationships
307#[derive(Default, Clone)]
308struct Used {
309    used: bool,
310    uses: HashSet<usize>,
311}
312
313struct FuncVerifier<'a, C: Context> {
314    // Verification context
315    ctx: &'a C,
316    // Index of function being verified
317    index: usize,
318    // Function being verified
319    func: &'a Func<C::Phase>,
320    // Function use relations
321    used: &'a mut [Used],
322    // Byte offsets of logical instructions (and whether they start a basic block)
323    offsets: Vec<OffsetInfo>,
324    // Basic blocks
325    blocks: Vec<Block>,
326    // Queued block indices in data flow pass
327    queue: Vec<usize>,
328    // Maximum operand stack depth
329    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    // Iterate over instructions in bytecode slice from given offset
347    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    // Iterate instructions that have been previously verified as well-formed
357    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    // Pass 0: static limits, code well-formedness, branch target boundedness => offset array
362    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                // Nothing to validate syntactically
411                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                // Must wait for data flow to interpret these immediates
415                LoadUpvar(_, _) | StoreUpvar(_, _) | PushUpvars(_) => (),
416                // Disallow swaps that don't do anything, in case the implementation is unsafe with
417                // overlapping targets
418                Swap(i, j) => {
419                    if i == j {
420                        return Err(FuncError::inst(bytecode, before, InstError::NoOpSwap));
421                    }
422                }
423                // Call signature must be valid
424                Call(index) => self.ctx.pack(index).map(|_| ()).ok_or(FuncError::inst(
425                    bytecode,
426                    before,
427                    InstError::PackIndexOutOfBounds,
428                ))?,
429                // Symbol, call signature must be valid
430                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                // Index, call signature must be valid
445                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                // Constant index must be valid
460                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                // Local index must be in bounds
470                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/set symbols must be valid
480                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                // Closure function index must be valid;
490                // upvar compatibility checked during data flow
491                Close(index) => {
492                    self.ctx.function(index).map(|_| ()).ok_or(FuncError::inst(
493                        bytecode,
494                        before,
495                        InstError::FuncIndexOutOfBounds,
496                    ))?;
497                    // Note that we use function
498                    self.used[self.index].uses.insert(index);
499                }
500                // Branch targets must be in bounds; targets being valid instruction offsets
501                // is verified in the next pass
502                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                    // Note that next instruction should start a basic block
512                    need_bb = true;
513                }
514                // Pack index must be in bounds
515                Reify(index) => {
516                    let _ = self.ctx.pack(index).ok_or(FuncError::inst(
517                        bytecode,
518                        before,
519                        InstError::PackIndexOutOfBounds,
520                    ))?;
521                }
522                // Unpack index must be in bounds
523                Unpack(index) => {
524                    self.ctx.unpack_arity(index).ok_or(FuncError::inst(
525                        bytecode,
526                        before,
527                        InstError::UnpackIndexOutOfBounds,
528                    ))?;
529                }
530                // Function index must be valid; marks use relationship
531                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                // Upvar depth/indicator checked during data flow;
540                // terminal instruction
541                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    // Pass 1: branch target validity => basic blocks
554    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 targets must be the start of an instruction
567                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                        // Note that this instruction should start a basic block
577                        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 we have an input certificate, it must agree with us about basic block placement
592        if let Some(cert) = cert {
593            // Collect all basic block start offsets
594            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            // Certificate should have the same offsets, except that the entry block is
602            // omitted from the certificate because its flow state is known from its signature
603            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            // All is well, initialize blocks from certificate.  Entry block state will be set
614            // up by data flow pass.
615            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            // Initialize blocks to neutral state
628            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    // Model effect of single instruction on block state
647    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                // Enforce static upvar limit
738                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                // Pushes 2 values: result and indicator
806                block.push();
807                block.push();
808            }
809            NlBranch(depth, _) => {
810                // Terminal: validates upvar depth is in bounds
811                if *depth >= block.upvars.len() {
812                    return Err(InstError::UpvarDepthOutOfBounds);
813                }
814            }
815        }
816
817        Ok(())
818    }
819
820    // Pass 2: dataflow analysis
821    fn dataflow(&mut self, cert: Option<&Certificate>) -> FuncResult<()> {
822        use Inst::*;
823
824        let bytecode = self.ctx.slice(&self.func.bytecode);
825
826        // Initialize entry block state from function signature
827        let operands = self
828            .ctx
829            .unpack_arity(self.func.sig)
830            // Basic file verification should have caught this
831            .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            // Restrict bytecode slice to end of basic block
851            let slice = if let Some(end) = end {
852                &bytecode[0..end]
853            } else {
854                bytecode
855            };
856
857            // If we're generating a certificate, mark indicates whether block has changed state
858            if cert.is_none() {
859                self.blocks[bb].mark = false;
860            }
861
862            #[cfg(feature = "debug")]
863            eprintln!("  bb #{bb}: {}", block);
864
865            // Dummy value, immediately overwritten
866            let mut last = InstOffsets {
867                before: 0usize,
868                inst: Inst::Pop,
869                after: 0usize,
870            };
871
872            // Compute width of hex PC offsets for debug output
873            #[cfg(feature = "debug")]
874            let width = ((bytecode.len() - 1).max(1).ilog2() + 1).div_ceil(4).max(2) as usize;
875
876            // Step over all instructions in block
877            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            // Immediate fall-through successor block, if applicable
891            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            // Branch target successor block, if applicable
903            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            // Merge state into each sucessor block,
920            for sid in next.into_iter().chain(branch.into_iter()) {
921                let succ = &mut self.blocks[sid];
922                if cert.is_some() {
923                    // Certificate check case
924                    // Mark block as reached
925                    if !succ.mark {
926                        succ.mark = true;
927                        #[cfg(feature = "debug")]
928                        eprintln!("    mark #{}", sid);
929                        self.queue.push(sid);
930                    }
931                    // Check that claimed successor state is unchanged by merge
932                    // (and that merge doesn't otherwise catch an invariant violation)
933                    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                    // Certificate generation case, block is already marked for visit
946                    // Just merge predecessor state into it
947                    #[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                    // Certificate generation case, block is not marked for visit
954                    // Merge state into it, check for change
955                    let prev = succ.state.clone();
956                    block
957                        .merge(&mut succ.state)
958                        .map_err(|e| FuncError::inst(bytecode, succ.offset, e))?;
959                    // If the state did change, mark and queue block for visit
960                    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        // Verify all blocks were reached.  Syntactically unreachable blocks aren't unsafe per se,
971        // but could indicate a heap/JIT spray attack. We could also perform this check during
972        // certificate generation as a sanity test of the lowering pass, but don't at present.
973        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    // Compute certificate from scratch
989    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    // Check existing certificate is legitimate
1006    pub fn check(&mut self, cert: &Certificate) -> FuncResult<()> {
1007        self.syntax()?;
1008        self.basic_blocks(Some(cert))?;
1009        self.dataflow(Some(cert))?;
1010
1011        // Verify the certificate has the correct max operand stack depth
1012        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    // Verification context
1022    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    // Verify that all functions are transitively reachable from entry function by use in closure
1031    // construction.  This isn't unsafe per se, but could indicate a (lazy) attempt at heap/JIT
1032    // spraying.
1033    fn verify_used(used: &mut [Used]) -> Result<()> {
1034        let mut queue = Vec::new();
1035
1036        // Entry function is always used
1037        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}