Skip to main content

cove_ir/bytecode/
verify.rs

1//! The check that makes encoded bytes safe to execute without checking again.
2//!
3//! Issue #245's boundary: **encode, verify once, then trust**. After this
4//! answers `Ok`, a dispatch loop may read `a`, `b` and `c` as frame offsets
5//! and the payload as a table index without asking whether either is in
6//! range, because this asked. What stays a run-time question stays one —
7//! division by zero, an object's layout against the layout the instruction
8//! names, element bounds, fuel, deadlines, cancellation, host failure.
9//!
10//! # This is not [`mod@crate::verify`], and the difference is the point
11//!
12//! [`mod@crate::verify`] checks a **lowering**: it reads `Function::code` as
13//! [`Inst`]s and asks whether `crate::lower` produced a well
14//! formed program. A fault there is a bug in this compiler, it is reported by
15//! a panic, and it is about instructions that are Rust values and therefore
16//! cannot be malformed — an `Inst::Copy` always has a `dst`, a `src` and a
17//! `layout`, whatever they name.
18//!
19//! This checks **bytes**. Sixteen bytes can say things no `Inst` can: an
20//! opcode that names nothing, a `flags` byte that is not zero, an operand in a
21//! field the opcode does not use. So this runs first over the structure — that
22//! is [`decode`], which refuses anything that is not the canonical encoding of
23//! some instruction — and then over the same program-relative facts the other
24//! one checks, driven by [`Op::fields`] rather than by a match on an enum.
25//!
26//! The two therefore overlap on purpose and neither replaces the other. One is
27//! a compiler's self-check over its own output; the other is a loader's check
28//! over an input, and it must be **safe against arbitrary bytes** even while
29//! the format is internal, because a verifier that is only safe against its
30//! own encoder is not a verifier. Nothing in this module indexes with a value
31//! it has not bounded, and nothing panics on any sixteen bytes at all.
32
33use crate::inst::{Inst, Len};
34use crate::layout::{LayoutId, Shape};
35use crate::program::{Function, FunctionId, Program};
36use crate::repr::Repr;
37use crate::Slot;
38
39use super::decode::decode;
40use super::encode::Encoded;
41use super::op::{Half, Op, Operand, Payload};
42use super::EncodedInst;
43
44/// A way in which encoded bytes are not something that may be run.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct Fault {
47    /// The function the fault is in, as `module.name`.
48    pub function: String,
49    /// The instruction it is at, or `None` when the fault is the run's own —
50    /// a program and an encoding of different lengths, say.
51    pub pc: Option<usize>,
52    pub what: String,
53}
54
55impl std::fmt::Display for Fault {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self.pc {
58            Some(pc) => write!(f, "{}+{pc}: {}", self.function, self.what),
59            None => write!(f, "{}: {}", self.function, self.what),
60        }
61    }
62}
63
64/// Checks a whole encoded program against the program it was encoded from.
65///
66/// Every fault rather than the first, for [`mod@crate::verify`]'s reason: one
67/// cause usually shows up in several places, and seeing all of them is what
68/// says which one it is.
69pub fn verify(program: &Program, encoded: &Encoded) -> Result<(), Vec<Fault>> {
70    let mut faults = Vec::new();
71    if encoded.functions.len() != program.functions.len() {
72        faults.push(Fault {
73            function: "<program>".to_string(),
74            pc: None,
75            what: format!(
76                "has {} encoded functions and the program has {}",
77                encoded.functions.len(),
78                program.functions.len()
79            ),
80        });
81        return Err(faults);
82    }
83    for (index, code) in encoded.functions.iter().enumerate() {
84        let id = FunctionId(index as u32);
85        // The 1:1 encoding is what keeps `Function::spans`, `Local`'s pc
86        // ranges and `Table::targets` meaning what they meant, so a run of a
87        // different length is a fault about the whole function rather than
88        // about one of its instructions.
89        let function = program.function(id);
90        if code.len() != function.code.len() {
91            faults.push(Fault {
92                function: function.qualified(),
93                pc: None,
94                what: format!(
95                    "is {} encoded instructions and the function has {}, so a pc means two \
96                     things",
97                    code.len(),
98                    function.code.len()
99                ),
100            });
101        }
102        faults.extend(verify_function(program, id, code));
103    }
104    if faults.is_empty() {
105        Ok(())
106    } else {
107        Err(faults)
108    }
109}
110
111/// Checks one run of encoded instructions against the frame it runs in.
112///
113/// The entry point that takes bytes nothing produced: `code` is read as the
114/// authority on what will execute, and `program.function(id)` supplies the
115/// frame, the parameters and the answer it has to agree with.
116pub fn verify_function(program: &Program, id: FunctionId, code: &[EncodedInst]) -> Vec<Fault> {
117    let mut check = Check {
118        program,
119        function: program.function(id),
120        code,
121        faults: Vec::new(),
122    };
123    if code.is_empty() {
124        check.fault(None, "has no instructions, so there is nowhere to begin");
125    }
126    for pc in 0..code.len() {
127        check.inst(pc);
128    }
129    check.faults
130}
131
132struct Check<'a> {
133    program: &'a Program,
134    function: &'a Function,
135    code: &'a [EncodedInst],
136    faults: Vec<Fault>,
137}
138
139impl Check<'_> {
140    fn fault(&mut self, pc: Option<usize>, what: impl Into<String>) {
141        self.faults.push(Fault {
142            function: self.function.qualified(),
143            pc,
144            what: what.into(),
145        });
146    }
147
148    fn inst(&mut self, pc: usize) {
149        let bytes = self.code[pc];
150        let at = Some(pc);
151        // The structure first: a defined opcode, a zero `flags`, a zero in
152        // every field the opcode does not use, an in-range constant, a
153        // displacement that lands on some program counter. `decode` is that
154        // check, and it is the only thing here that reads a byte it has not
155        // been told the shape of.
156        let inst = match decode(bytes, pc as u32) {
157            Ok(inst) => inst,
158            Err(why) => {
159                self.fault(at, why.to_string());
160                return;
161            }
162        };
163        let op = match Op::from_number(bytes.opcode()) {
164            Some(op) => op,
165            // Unreachable: `decode` answered `Ok`, so the opcode is defined.
166            None => return,
167        };
168        self.payload(at, op, bytes);
169        self.slots(at, op, bytes);
170        self.meaning(at, &inst);
171    }
172
173    /// Every id in the payload indexes its table, and every half the opcode
174    /// leaves for a number is a number the field can hold.
175    ///
176    /// A `Count` and an `Offset` are 32 bits in a 32-bit half, so their range
177    /// as a *field* is the field's own and there is nothing to check here;
178    /// they are named here so that the table is read exhaustively rather than
179    /// by omission. `Half::Count` is not therefore unchecked everywhere:
180    /// `Op::AllocImm`'s is checked against the layout the same instruction
181    /// names, in [`Check::meaning`], because that is a fact about what the
182    /// two fields mean *together* rather than about either field's range.
183    fn payload(&mut self, at: Option<usize>, op: Op, bytes: EncodedInst) {
184        let Payload::Halves(lo, hi) = op.fields().payload else {
185            return;
186        };
187        for (half, value) in [(lo, bytes.lo()), (hi, bytes.hi())] {
188            let len = match half {
189                Half::Unused | Half::Count | Half::Offset | Half::Case => continue,
190                Half::Function => self.program.functions.len(),
191                Half::Str => self.program.strings.len(),
192                Half::Layout => self.program.layouts.len(),
193                Half::Table => self.program.tables.len(),
194                Half::Args => self.program.args.len(),
195                Half::Builtin => self.program.builtins.len(),
196                Half::HostOp => self.program.host_ops.len(),
197            };
198            if value as usize >= len {
199                let what = half.name();
200                self.fault(at, format!("names {what} {value}, and there are {len}"));
201            }
202        }
203    }
204
205    /// The central check, and the one a sixteen-bit slot operand buys.
206    ///
207    /// *Every slot is inside the function frame* is not forty-nine rules but
208    /// one rule over three fields, driven by which of the three the opcode
209    /// declares live. A field it does not use was already required to be zero
210    /// by [`decode`].
211    fn slots(&mut self, at: Option<usize>, op: Op, bytes: EncodedInst) {
212        let fields = op.fields();
213        let held = [bytes.a(), bytes.b(), bytes.c()];
214        let names = ["a", "b", "c"];
215        for ((operand, value), name) in fields.operands().into_iter().zip(held).zip(names) {
216            let slot = Slot::from(value);
217            match operand {
218                Operand::Unused => {}
219                Operand::Word(want) => {
220                    let Some(found) = self.function.repr(slot) else {
221                        self.outside(at, name, slot);
222                        continue;
223                    };
224                    if !want.is_empty() && !want.contains(&found) {
225                        let names: Vec<&str> = want.iter().map(|repr| repr.name()).collect();
226                        self.fault(
227                            at,
228                            format!(
229                                "slot {slot} holds {found}, and this opcode wants {}",
230                                names.join(" or ")
231                            ),
232                        );
233                    }
234                }
235                // The head of a run whose width the payload's layout gives.
236                // The layout half was checked above, so a missing one here is
237                // a fault already reported and this declines to guess.
238                Operand::Value => match self.named_layout(op, bytes) {
239                    Some(layout) => {
240                        self.fits(at, slot, layout, &format!("the value at {name}"));
241                    }
242                    None => {
243                        if self.function.repr(slot).is_none() {
244                            self.outside(at, name, slot);
245                        }
246                    }
247                },
248            }
249        }
250    }
251
252    /// The facts that are about what the instruction *says* rather than about
253    /// where its operands are: a branch inside this function, a switch table
254    /// whose targets are, a call whose arguments are the callee's parameters,
255    /// and a destination as wide as the answer written into it.
256    ///
257    /// These read the decoded instruction because they are the checks whose
258    /// shape differs per opcode; everything uniform is above.
259    fn meaning(&mut self, at: Option<usize>, inst: &Inst) {
260        match *inst {
261            Inst::Jump { to } | Inst::BranchFalse { to, .. } => self.target(at, to),
262            Inst::Switch { table, .. } => {
263                // The table stays immutable program metadata with absolute
264                // targets, and this is where absolute breaks loudly if a
265                // table is ever shared between two sites.
266                if let Some(table) = self.program.tables.get(table.index()) {
267                    let targets = table.targets.clone();
268                    let default = table.default;
269                    for to in targets.into_iter().chain(std::iter::once(default)) {
270                        self.target(at, to);
271                    }
272                }
273            }
274            Inst::Return { src } => {
275                let returns = self.function.returns;
276                self.fits(at, src, returns, "what is returned");
277            }
278            Inst::Call { dst, callee, args } => {
279                let Some(target) = self.program.functions.get(callee.index()) else {
280                    return;
281                };
282                let returns = target.returns;
283                let params = target.params.clone();
284                let name = target.qualified();
285                self.fits(at, dst, returns, "the destination of a call");
286                self.args_match(at, args, &params, &name);
287            }
288            // Only the arguments. The destination is checked by the uniform
289            // pass above, from `Operand::Value` and the layout half this
290            // opcode's payload carries — there is no declared callee here to
291            // read an answer's layout off, which is why the instruction
292            // carries it.
293            Inst::CallClosure { args, .. } => self.args_fit(at, args),
294            Inst::CallHost { dst, op, args } | Inst::CallResource { dst, op, args, .. } => {
295                if let Some(op) = self.program.host_ops.get(op.index()) {
296                    let result = op.result;
297                    self.fits(at, dst, result, "the answer of a host call");
298                }
299                self.args_fit(at, args);
300            }
301            Inst::CallBuiltin { dst, builtin, args } => {
302                if let Some(builtin) = self.program.builtins.get(builtin.index()) {
303                    let result = builtin.result;
304                    self.fits(at, dst, result, "the answer of a builtin");
305                }
306                self.args_fit(at, args);
307            }
308            // `Op::CopyBytes`'s five operands live behind this `ArgsId`
309            // rather than in `a`, `b` and `c`, and `args_fit` is the same
310            // uniform check every other call's arguments get: each one's
311            // location is a value of the layout it was given. There is no
312            // declared callee to check the *count* or the individual layouts
313            // against — `crate::verify`'s `check_copy_bytes_args` is where
314            // the fixed shape (`dst`, `dst_at`, `src`, `src_at`, `len`) is a
315            // lowering-time fact — so this only re-derives the one thing a
316            // malformed encoding could still get wrong: an argument whose
317            // slot does not fit the width its own carried layout claims.
318            Inst::CopyBytes { args } => self.args_fit(at, args),
319            // `Op::AppendBytes`'s four operands live behind an `ArgsId` for the
320            // same reason and are checked by the same uniform rule;
321            // `crate::verify`'s `check_append_bytes_args` is where the fixed
322            // shape (`buffer`, `src`, `from`, `to`) is a lowering-time fact.
323            Inst::AppendBytes { args } => self.args_fit(at, args),
324            // `Len::Count` is the one `Len` form this check can settle ahead
325            // of time: both halves of the payload are right here, so the
326            // layout `Op::AllocImm` names and the count it carries are known
327            // without running anything. `Len::Slot` is not — its count is a
328            // value the running program computes — so that is a run-time
329            // question `Machine::allocate` answers the same way this does:
330            // checked, and never a wraparound.
331            // A case index is the other half-pair this check can settle
332            // ahead of time, and for the same reason: the layout and the case
333            // are both in this instruction's payload, so whether the enum has
334            // that case is knowable without running anything.
335            //
336            // It is checked here rather than left to `crate::verify` because
337            // this verifier reads *bytes*. `crate::verify` reads an `Inst` the
338            // lowering built and can trust that a `CaseId` came from a case
339            // that exists; nothing may be trusted about a number decoded out
340            // of a payload half, and an out-of-range one would otherwise
341            // reach a `switch` and take its default — a wrong answer rather
342            // than a refusal.
343            Inst::Tag { layout, case, .. } => {
344                let Some(described) = self.program.layouts.get(layout.index()) else {
345                    return;
346                };
347                match &described.shape {
348                    Shape::Enum { cases, .. } => {
349                        if case.index() >= cases.len() {
350                            let count = cases.len();
351                            self.fault(
352                                at,
353                                format!(
354                                    "names {case} of `{}`, which has {count} case(s)",
355                                    described.name
356                                ),
357                            );
358                        }
359                    }
360                    _ => self.fault(
361                        at,
362                        format!(
363                            "writes a case of `{}`, which is not an enum",
364                            described.name
365                        ),
366                    ),
367                }
368            }
369            Inst::Alloc {
370                layout,
371                len: Len::Count(count),
372                ..
373            } => {
374                let Some(described) = self.program.layouts.get(layout.index()) else {
375                    return;
376                };
377                if described
378                    .try_payload_words(count, &self.program.layouts)
379                    .is_none()
380                {
381                    self.fault(
382                        at,
383                        format!(
384                            "allocates {count} of `{}`, whose payload size overflows",
385                            described.name
386                        ),
387                    );
388                }
389            }
390            _ => {}
391        }
392    }
393
394    /// Which layout the payload names, where the opcode says it names one.
395    fn named_layout(&self, op: Op, bytes: EncodedInst) -> Option<LayoutId> {
396        let Payload::Halves(lo, hi) = op.fields().payload else {
397            return None;
398        };
399        let id = match (lo, hi) {
400            (Half::Layout, _) => LayoutId(bytes.lo()),
401            (_, Half::Layout) => LayoutId(bytes.hi()),
402            _ => return None,
403        };
404        self.program.layouts.get(id.index()).map(|_| id)
405    }
406
407    fn outside(&mut self, at: Option<usize>, field: &str, slot: Slot) {
408        let size = self.function.frame_size();
409        self.fault(
410            at,
411            format!("{field} names slot {slot}, outside a frame of {size}"),
412        );
413    }
414
415    /// A branch or a switch target lands on an instruction of this function.
416    ///
417    /// Under a 1:1 encoding that is any pc in `[0, code.len())`, which is why
418    /// "every target is an instruction boundary" needs no arithmetic: a
419    /// boundary is what a pc is.
420    fn target(&mut self, at: Option<usize>, to: u32) {
421        if to as usize >= self.code.len() {
422            let len = self.code.len();
423            self.fault(at, format!("jumps to {to}, past the {len} instructions"));
424        }
425    }
426
427    /// The location at `slot` is a value of `layout`: it is inside the frame,
428    /// and its words are the layout's words in order.
429    ///
430    /// The same rule [`mod@crate::verify`] turns on, made about a decoded operand
431    /// rather than about an enum field. The extent is what keeps a multiword
432    /// copy near the top of a frame from reaching the frame above it, and the
433    /// words are what keep a collection from tracing a `Float` or missing a
434    /// `Ref`.
435    fn fits(&mut self, at: Option<usize>, slot: Slot, layout: LayoutId, what: &str) {
436        let Some(described) = self.program.layouts.get(layout.index()) else {
437            let size = self.program.layouts.len();
438            self.fault(
439                at,
440                format!("{what} is layout {layout}, and there are {size}"),
441            );
442            return;
443        };
444        let words: Vec<Repr> = described.words.clone();
445        let name = described.name.clone();
446        let size = self.function.frame_size();
447        if u64::from(slot) + words.len() as u64 > u64::from(size) {
448            self.fault(
449                at,
450                format!(
451                    "{what} is `{name}`, {} words at slot {slot}, and the frame has {size}",
452                    words.len()
453                ),
454            );
455            return;
456        }
457        for (offset, want) in words.iter().enumerate() {
458            let found = self.function.reprs[slot as usize + offset];
459            if found != *want {
460                self.fault(
461                    at,
462                    format!(
463                        "{what} is `{name}`, whose word {offset} is {want}, but slot {} holds \
464                         {found}",
465                        slot as usize + offset
466                    ),
467                );
468                return;
469            }
470        }
471    }
472
473    /// Every argument is a value location of the layout it names, inside this
474    /// frame.
475    fn args_fit(&mut self, at: Option<usize>, args: crate::ArgsId) {
476        let Some(list) = self.program.args.get(args.index()) else {
477            return;
478        };
479        for (index, arg) in list.clone().into_iter().enumerate() {
480            self.fits(at, arg.slot, arg.layout, &format!("argument {index}"));
481        }
482    }
483
484    /// The same, where the callee declares what it takes: the arity is the
485    /// callee's, each argument's layout is the parameter's, and each location
486    /// is a value of it.
487    fn args_match(
488        &mut self,
489        at: Option<usize>,
490        args: crate::ArgsId,
491        want: &[LayoutId],
492        name: &str,
493    ) {
494        let Some(list) = self.program.args.get(args.index()) else {
495            return;
496        };
497        let passed = list.clone();
498        if passed.len() != want.len() {
499            self.fault(
500                at,
501                format!(
502                    "passes {} arguments to `{name}`, which declares {}",
503                    passed.len(),
504                    want.len()
505                ),
506            );
507            return;
508        }
509        for (index, (arg, layout)) in passed.into_iter().zip(want).enumerate() {
510            if arg.layout != *layout {
511                let passed = self.name_of(arg.layout);
512                let declared = self.name_of(*layout);
513                self.fault(
514                    at,
515                    format!(
516                        "argument {index} of `{name}` is passed as a `{passed}`, and the \
517                         parameter is a `{declared}`"
518                    ),
519                );
520                continue;
521            }
522            self.fits(
523                at,
524                arg.slot,
525                *layout,
526                &format!("argument {index} of `{name}`"),
527            );
528        }
529    }
530
531    /// What a layout is called, or its id where the table is too short.
532    fn name_of(&self, layout: LayoutId) -> String {
533        match self.program.layouts.get(layout.index()) {
534            Some(held) => held.name.to_string(),
535            None => layout.to_string(),
536        }
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use std::sync::Arc;
543
544    use cove_diag::{FileId, Span};
545
546    use super::*;
547    use crate::bytecode::encode::{encode, encode_function, encode_program};
548    use crate::bytecode::{instructions, EncodedInst};
549    use crate::inst::{ArithOp, Num};
550    use crate::layout::{Case, Layout, Shape};
551    use crate::program::{Arg, Table};
552    use crate::repr::RefMap;
553    use crate::{ArgsId, LayoutId, StrId, TableId};
554
555    const INT: LayoutId = LayoutId(0);
556    const STR: LayoutId = LayoutId(1);
557    /// Two `Int` words, so that a value location has an extent to run off the
558    /// end of.
559    const POINT: LayoutId = LayoutId(2);
560    const BOXED: LayoutId = LayoutId(3);
561    /// A two-case enum, for the one semantic check a case index needs.
562    const ENUM: LayoutId = LayoutId(4);
563
564    fn layouts() -> Vec<Layout> {
565        vec![
566            Layout::word("Int", Repr::Int),
567            Layout::object("String", Shape::Str),
568            Layout::inline(
569                "Point",
570                Shape::Struct {
571                    fields: Vec::new(),
572                    opaque: false,
573                },
574                vec![Repr::Int, Repr::Int],
575            ),
576            Layout::object("Any", Shape::Boxed),
577            Layout::inline(
578                "m.E",
579                Shape::Enum {
580                    cases: vec![
581                        Case {
582                            name: Arc::from("A"),
583                            parts: Vec::new(),
584                        },
585                        Case {
586                            name: Arc::from("B"),
587                            parts: Vec::new(),
588                        },
589                    ],
590                    payload: vec![Repr::Int],
591                },
592                vec![Repr::Tag, Repr::Int],
593            ),
594        ]
595    }
596
597    fn span() -> Span {
598        Span::new(FileId(0), 0, 0)
599    }
600
601    /// A frame of `[int, int, ref, bool]`, answering an `Int`.
602    fn function(code: Vec<Inst>) -> Function {
603        let reprs = vec![Repr::Int, Repr::Int, Repr::Ref, Repr::Bool];
604        Function {
605            module: Arc::from("m"),
606            name: Arc::from("f"),
607            params: Vec::new(),
608            spans: vec![span(); code.len()],
609            refs: RefMap::of(&reprs),
610            reprs,
611            returns: INT,
612            captures: Vec::new(),
613            code,
614            locals: Vec::new(),
615            inlined: Vec::new(),
616            span: span(),
617            is_async: false,
618            stub: false,
619        }
620    }
621
622    /// The same, with a fifth slot that is a tag.
623    ///
624    /// A frame of its own rather than a wider shared one: two tests here turn
625    /// on the frame's exact width — a slot past it, and a value location
626    /// running off the top of it — and widening the fixture would have made
627    /// them pass for the wrong reason.
628    fn tagged_program(code: Vec<Inst>) -> Program {
629        let reprs = vec![Repr::Int, Repr::Int, Repr::Ref, Repr::Bool, Repr::Tag];
630        let mut held = program(code);
631        held.functions[0].refs = RefMap::of(&reprs);
632        held.functions[0].reprs = reprs;
633        held
634    }
635
636    fn program(code: Vec<Inst>) -> Program {
637        Program {
638            functions: vec![function(code)],
639            layouts: layouts(),
640            str_layout: STR,
641            boxed_layout: BOXED,
642            ..Program::default()
643        }
644    }
645
646    /// What the verifier says about a run of bytes, in its own words.
647    fn faults(program: &Program, code: &[EncodedInst]) -> Vec<String> {
648        verify_function(program, FunctionId(0), code)
649            .into_iter()
650            .map(|fault| fault.what)
651            .collect()
652    }
653
654    /// The bytes of one instruction, encoded at pc 0.
655    fn at(inst: Inst) -> EncodedInst {
656        encode(&inst, 0).expect("the instruction encodes")
657    }
658
659    /// Sets one byte of an instruction, which is how these tests write bytes
660    /// no encoder produced.
661    fn with(code: EncodedInst, offset: usize, byte: u8) -> EncodedInst {
662        let mut bytes = *code.bytes();
663        bytes[offset] = byte;
664        EncodedInst::from_bytes(bytes)
665    }
666
667    /// The whole point of the boundary: bytes the encoder produced from a
668    /// well formed lowering pass, and after that the dispatch loop may index
669    /// without asking.
670    #[test]
671    fn a_well_formed_encoding_has_nothing_to_say_about_it() {
672        let held = program(vec![
673            Inst::Int { dst: 0, value: 7 },
674            Inst::Arith {
675                num: Num::Int,
676                op: ArithOp::Add,
677                dst: 0,
678                a: 0,
679                b: 1,
680            },
681            Inst::Return { src: 0 },
682        ]);
683        let code = encode_function(&held.functions[0]).expect("it encodes");
684        assert_eq!(faults(&held, &code), Vec::<String>::new());
685        assert_eq!(
686            verify(&held, &encode_program(&held).expect("encodes")),
687            Ok(())
688        );
689    }
690
691    /// A case index no encoder produced is refused here, not left to a
692    /// `switch` to answer wrongly.
693    ///
694    /// `crate::verify` makes the same check of an `Inst` the lowering built,
695    /// and that is not enough: this verifier is the "verify arbitrary bytes
696    /// once, then trust" boundary, and a case index is one half of a payload
697    /// — sixteen million values, of which two are cases of this enum. An
698    /// out-of-range one is not memory-unsafe, because `Op::Switch` reads a
699    /// table with a default; it is worse than a refusal in a different way,
700    /// which is that the program keeps running and takes a branch nothing
701    /// wrote.
702    #[test]
703    fn a_case_index_past_the_enums_cases_is_refused() {
704        let held = tagged_program(vec![Inst::Return { src: 0 }]);
705        let good = at(Inst::Tag {
706            dst: 4,
707            layout: ENUM,
708            case: crate::CaseId(1),
709        });
710        assert_eq!(faults(&held, &[good]), Vec::<String>::new());
711
712        // The payload's low half is the case, little end first, so one byte
713        // is the whole of the mutation.
714        let code = [with(good, 8, 7)];
715        assert_eq!(
716            faults(&held, &code),
717            ["names case7 of `m.E`, which has 2 case(s)"]
718        );
719    }
720
721    /// And a layout that is not an enum has no case for one to name.
722    ///
723    /// Reachable only by mutating the payload's *high* half, since the
724    /// lowering never writes a tag of a struct.
725    #[test]
726    fn a_case_of_something_that_is_not_an_enum_is_refused() {
727        let held = tagged_program(vec![Inst::Return { src: 0 }]);
728        let good = at(Inst::Tag {
729            dst: 4,
730            layout: ENUM,
731            case: crate::CaseId(0),
732        });
733        let code = [with(good, 12, POINT.0 as u8)];
734        assert_eq!(
735            faults(&held, &code),
736            ["writes a case of `Point`, which is not an enum"]
737        );
738    }
739
740    /// A byte that names no operation stops the instruction being read at
741    /// all, rather than reaching a table with an index nothing bounded.
742    #[test]
743    fn an_opcode_no_encoder_produced_is_refused() {
744        let held = program(vec![Inst::Return { src: 0 }]);
745        let code = [with(at(Inst::Return { src: 0 }), 0, 200)];
746        assert_eq!(faults(&held, &code), ["opcode 200 names no operation"]);
747    }
748
749    /// `flags` is reserved and must be zero, which is ADR 0041's decision
750    /// about a byte it deliberately found no use for.
751    #[test]
752    fn a_nonzero_flags_byte_is_refused() {
753        let held = program(vec![Inst::Return { src: 0 }]);
754        let code = [with(at(Inst::Return { src: 0 }), 1, 4)];
755        assert_eq!(
756            faults(&held, &code),
757            ["flags is 4, and it is reserved and must be zero"]
758        );
759    }
760
761    /// The check the whole format rests on. A slot operand is sixteen bits,
762    /// so any of 65,536 values can appear in it, and only the ones inside
763    /// this frame may be read as a frame offset.
764    #[test]
765    fn a_slot_past_the_frame_is_refused() {
766        let held = program(vec![Inst::Return { src: 0 }]);
767        let code = [at(Inst::Unit { dst: 9 })];
768        assert_eq!(
769            faults(&held, &code),
770            ["a names slot 9, outside a frame of 4"]
771        );
772        // The same rule over `b` and `c`, because it is one rule over three
773        // fields rather than one per instruction.
774        let code = [at(Inst::Arith {
775            num: Num::Int,
776            op: ArithOp::Add,
777            dst: 0,
778            a: 4,
779            b: 5,
780        })];
781        assert_eq!(
782            faults(&held, &code),
783            [
784                "b names slot 4, outside a frame of 4",
785                "c names slot 5, outside a frame of 4"
786            ]
787        );
788    }
789
790    /// A slot inside the frame is not enough when the operand heads a *run*
791    /// of words: two words at the last slot reach the frame above.
792    #[test]
793    fn a_value_location_that_runs_off_the_top_of_the_frame_is_refused() {
794        let held = program(vec![Inst::Return { src: 0 }]);
795        let code = [at(Inst::Copy {
796            dst: 3,
797            src: 0,
798            layout: POINT,
799        })];
800        assert_eq!(
801            faults(&held, &code),
802            ["the value at a is `Point`, 2 words at slot 3, and the frame has 4"]
803        );
804        // The words are checked as well as the extent, because a location
805        // whose second word is a reference is what a collection would trace.
806        let code = [at(Inst::Copy {
807            dst: 1,
808            src: 0,
809            layout: POINT,
810        })];
811        assert_eq!(
812            faults(&held, &code),
813            ["the value at a is `Point`, whose word 1 is int, but slot 2 holds ref"]
814        );
815    }
816
817    /// The opcode says which `Repr`s its operands may hold, so a `float`
818    /// addition over `int` words is a refusal here and not a wrong answer
819    /// later. This is `crate::verify`'s `expect` check, driven by an opcode
820    /// instead of by a match on an enum.
821    #[test]
822    fn a_slot_whose_repr_the_opcode_does_not_admit_is_refused() {
823        let held = program(vec![Inst::Return { src: 0 }]);
824        let code = [at(Inst::Arith {
825            num: Num::Float,
826            op: ArithOp::Add,
827            dst: 0,
828            a: 1,
829            b: 1,
830        })];
831        assert_eq!(
832            faults(&held, &code),
833            [
834                "slot 0 holds int, and this opcode wants float",
835                "slot 1 holds int, and this opcode wants float",
836                "slot 1 holds int, and this opcode wants float"
837            ]
838        );
839    }
840
841    /// A branch is relative and its target has to land on an instruction of
842    /// this function — which, under a 1:1 encoding, is any pc it has.
843    #[test]
844    fn a_branch_past_the_last_instruction_is_refused() {
845        let held = program(vec![Inst::Return { src: 0 }]);
846        let code = [
847            encode(&Inst::Jump { to: 7 }, 0).expect("encodes"),
848            at(Inst::Return { src: 0 }),
849        ];
850        assert_eq!(
851            faults(&held, &code),
852            ["jumps to 7, past the 2 instructions"]
853        );
854        // One past the last is off the end; the last itself is not.
855        let code = [
856            encode(&Inst::Jump { to: 2 }, 0).expect("encodes"),
857            at(Inst::Return { src: 0 }),
858        ];
859        assert_eq!(
860            faults(&held, &code),
861            ["jumps to 2, past the 2 instructions"]
862        );
863        let code = [
864            encode(&Inst::Jump { to: 1 }, 0).expect("encodes"),
865            at(Inst::Return { src: 0 }),
866        ];
867        assert_eq!(faults(&held, &code), Vec::<String>::new());
868    }
869
870    /// A switch table stays immutable program metadata with absolute targets,
871    /// and this is where absolute breaks loudly.
872    #[test]
873    fn a_switch_target_past_the_last_instruction_is_refused() {
874        let mut held = program(vec![Inst::Return { src: 0 }]);
875        held.tables.push(Table {
876            targets: vec![0, 5],
877            default: 9,
878        });
879        let code = [
880            at(Inst::Switch {
881                on: 0,
882                table: TableId(0),
883            }),
884            at(Inst::Return { src: 0 }),
885        ];
886        assert_eq!(
887            faults(&held, &code),
888            [
889                "jumps to 5, past the 2 instructions",
890                "jumps to 9, past the 2 instructions"
891            ]
892        );
893    }
894
895    /// Every id in the payload indexes its own table, and a program that
896    /// names one it does not have is refused before anything indexes with it.
897    #[test]
898    fn an_id_past_the_end_of_its_table_is_refused() {
899        let held = program(vec![Inst::Return { src: 0 }]);
900        let code = [at(Inst::Str {
901            dst: 2,
902            text: StrId(0),
903        })];
904        assert_eq!(faults(&held, &code), ["names string 0, and there are 0"]);
905
906        let code = [at(Inst::Copy {
907            dst: 0,
908            src: 1,
909            layout: LayoutId(40),
910        })];
911        assert_eq!(faults(&held, &code), ["names layout 40, and there are 5"]);
912    }
913
914    /// Issue #269: an `alloc.imm` carries its count in the payload's own
915    /// bytes, so a hand-built one — not something `crate::lower` would ever
916    /// emit, which is the point — can say anything a `u32` can say. This one
917    /// says a count whose product with `Array`'s one-word stride does not fit
918    /// `u32`, and the check this test is for is what stands between that and
919    /// `Machine::allocate` under-allocating the object by exactly the amount
920    /// the multiply wrapped by.
921    #[test]
922    fn an_alloc_imm_whose_count_times_stride_overflows_is_refused() {
923        let layouts = vec![
924            // Two words wide, so `u32::MAX` elements — which alone still
925            // fits `u32` — times this stride does not.
926            Layout::inline(
927                "Point",
928                Shape::Struct {
929                    fields: Vec::new(),
930                    opaque: false,
931                },
932                vec![Repr::Int, Repr::Int],
933            ),
934            Layout::object(
935                "Array",
936                Shape::Elements {
937                    elem: LayoutId(0),
938                    growable: false,
939                },
940            ),
941        ];
942        let held = Program {
943            functions: vec![function(vec![Inst::Return { src: 0 }])],
944            layouts,
945            str_layout: LayoutId(0),
946            boxed_layout: LayoutId(0),
947            ..Program::default()
948        };
949        let code = [at(Inst::Alloc {
950            dst: 2,
951            layout: LayoutId(1),
952            len: Len::Count(u32::MAX),
953        })];
954        assert_eq!(
955            faults(&held, &code),
956            ["allocates 4294967295 of `Array`, whose payload size overflows"]
957        );
958    }
959
960    /// A call's arity is the callee's, not the call site's.
961    #[test]
962    fn a_call_that_passes_the_wrong_number_of_arguments_is_refused() {
963        let mut held = program(vec![Inst::Return { src: 0 }]);
964        let mut callee = function(vec![Inst::Return { src: 0 }]);
965        callee.name = Arc::from("g");
966        callee.params = vec![INT, INT];
967        held.functions.push(callee);
968        held.args.push(vec![Arg {
969            slot: 0,
970            layout: INT,
971        }]);
972        let code = [at(Inst::Call {
973            dst: 0,
974            callee: FunctionId(1),
975            args: ArgsId(0),
976        })];
977        assert_eq!(
978            faults(&held, &code),
979            ["passes 1 arguments to `m.g`, which declares 2"]
980        );
981    }
982
983    /// A function with no instructions has nowhere to begin, and a dispatch
984    /// loop that trusted its bounds would read whatever followed it.
985    #[test]
986    fn a_run_with_no_instructions_is_refused() {
987        let held = program(vec![Inst::Return { src: 0 }]);
988        assert_eq!(
989            faults(&held, &[]),
990            ["has no instructions, so there is nowhere to begin"]
991        );
992    }
993
994    /// A byte stream that stops mid-instruction never becomes instructions,
995    /// so the verifier is never handed a partial one. Sixteen bytes is the
996    /// only length an instruction has.
997    #[test]
998    fn a_truncated_stream_never_reaches_the_verifier() {
999        let code = at(Inst::Return { src: 0 });
1000        let whole = code.bytes().to_vec();
1001        assert!(instructions(&whole).is_ok());
1002        assert!(instructions(&whole[..15]).is_err());
1003        assert!(instructions(&[whole.clone(), whole[..4].to_vec()].concat()).is_err());
1004    }
1005
1006    /// An encoding of a different length is a fault about the whole function
1007    /// rather than about one instruction: a pc would mean two things, and
1008    /// spans, local ranges and switch targets are all indexed by one.
1009    #[test]
1010    fn an_encoding_of_a_different_length_than_the_function_is_refused() {
1011        let held = program(vec![Inst::Return { src: 0 }]);
1012        let encoded = Encoded {
1013            functions: vec![vec![
1014                at(Inst::Return { src: 0 }),
1015                at(Inst::Return { src: 0 }),
1016            ]],
1017        };
1018        let said: Vec<String> = verify(&held, &encoded)
1019            .expect_err("two encoded instructions against one")
1020            .into_iter()
1021            .map(|fault| fault.what)
1022            .collect();
1023        assert_eq!(
1024            said,
1025            ["is 2 encoded instructions and the function has 1, so a pc means two things"]
1026        );
1027    }
1028
1029    /// The verifier is a reader of input, and the format being internal does
1030    /// not make its bytes trusted. Nothing here panics, indexes out of range,
1031    /// or loops, whatever the bytes are.
1032    #[test]
1033    fn arbitrary_bytes_answer_rather_than_panic() {
1034        let mut held = program(vec![Inst::Return { src: 0 }]);
1035        held.strings.push(Arc::from("one"));
1036        held.args.push(Vec::new());
1037        held.tables.push(Table {
1038            targets: vec![0],
1039            default: 0,
1040        });
1041        let mut bytes = [0u8; EncodedInst::BYTES];
1042        for seed in 0u32..20_000 {
1043            for (offset, byte) in bytes.iter_mut().enumerate() {
1044                *byte = (seed
1045                    .wrapping_mul(2_654_435_761)
1046                    .rotate_left(offset as u32 * 5)
1047                    ^ offset as u32) as u8;
1048            }
1049            let code = [EncodedInst::from_bytes(bytes)];
1050            let _ = verify_function(&held, FunctionId(0), &code);
1051        }
1052    }
1053}