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, ¶ms, &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}