Skip to main content

rucc_safety/
lib.rs

1//! The memory safety monitor: check insertion over the IR.
2//!
3//! Design: `spec/safe-memory/06-instrumentation.md` section 6.3.
4//!
5//! The one decision this crate exists to make is *when* checks are inserted. Every sanitizer that
6//! came before instruments after the optimizer, so that the optimizer cannot delete its checks,
7//! and pays the full naive cost of every one of them forever. We insert before the optimizer and
8//! let it discharge what it can prove, which is only possible because a check is an instruction
9//! with defined semantics rather than a call the optimizer has no opinion about.
10//!
11//! # What is here so far
12//!
13//! The three checks milestone S1 in `spec/safe-memory/16-milestones.md` asks for: bounds and
14//! lifetime on every access, and a derivation check on every pointer computed from another
15//! pointer. Nothing is discharged, so a function comes out with a check in front of everything,
16//! which is the baseline every elimination claim at S4 is measured against.
17//!
18//! And the boundary, in [`mod@wrap`]: a call the program wrote to one of the C library functions
19//! `rucc-safe-rt` has a row for is pointed at that row's wrapper instead, so the judgements happen
20//! before the call rather than not at all. That is milestone S2 and
21//! `spec/safe-memory/10-boundaries.md` section 10.3 is what it implements.
22//!
23//! And the other end of it, in [`mod@lower`]: after the optimizer has run, every check still standing
24//! becomes a call to the runtime carrying the index of a row in a table this crate puts in the
25//! object. That module is where the reason S1's checks are calls rather than compares is argued.
26//!
27//! And the rest of the boundary, in [`mod@boundary`]: the places where a pointer crosses between
28//! this build and code nobody instrumented, which is a function of this file that somebody else can
29//! call and a call this file makes to a library that has no wrapper. Neither can be modelled, so
30//! each of them is counted instead, which is what section 10.2 says the honest answer to a question
31//! you cannot answer is.
32//!
33//! And what all of that came to, in [`mod@summary`]: the counts `--emit=safety-summary` prints,
34//! which are what `spec/safe-memory/10-boundaries.md` section 10.2 means by a trust set that is
35//! counted per build rather than asserted.
36//!
37//! And the first of the planes, in [`mod@plane`]: every store records what the bytes it wrote were
38//! stored through, which is the judgement C 6.5 says a store makes, and every copy carries whatever
39//! the bytes it read said over to the bytes it wrote, which is the other half of the same rule.
40//! Those two are every write the type plane has, and every read that names a type now asks the
41//! plane whether the bytes agree with it, which is judgement J3. The order was deliberate: a check
42//! against a plane that only some of the writes maintain reports on programs that are correct, so
43//! the writes went in first and the question went in once they were all in.
44//!
45//! And the second of them, over the same two writes: every store records that the bytes it wrote
46//! hold something, and every copy carries whether the bytes it read held anything over to the bytes
47//! it wrote. The init plane is one bit per byte, so a store records the same thing whatever it
48//! stored, and a copy is the reason padding a member by member fill never touched is still padding
49//! nothing wrote after the structure moves. Every read now asks whether anything ever wrote the
50//! bytes it is about to read, which is document 03's Y6, and the writes went in first for the
51//! reason the type plane's did.
52//!
53//! And the one check that is not about a single access, in [`mod@promise`]: a block that declares
54//! `restrict` pointers keeps a record of what each of them reached, and every access through one of
55//! them asks whether another got there first. That is judgement J8 and it is off unless the build
56//! asks for it with `-fsafety-restrict`, which is the only check here that is, and the reason is on
57//! [`rucc_session::Promise`].
58//!
59//! The padding rule of `spec/safe-memory/09-type-init-and-races.md` section 9.3 arrives here as one
60//! number. A store carries how much padding the member it went through owns, and the range it
61//! records is the wider of that and what it wrote, which is all of `-fsafety-init=nopadding`. How
62//! far the padding goes takes a record's layout and this crate reads IR, so the front end is what
63//! decides it and `MemInfo.owns` is how the answer travels.
64//!
65//! The two questions a read asks come apart in one place. A read the front end named no type for
66//! asks the type plane nothing, because the question there is which type the bytes hold, and it
67//! asks the init plane the same thing every other read does, because the question there is about
68//! the bytes rather than about the access. What no `load` in any program asks about is padding: a
69//! read compiled into a `load` reads a member and a member is never padding, so the reads that
70//! cover padding are `memcmp` of two structures, hashing one and handing one to `write`, every one
71//! of which is a call into the movement group of [`mod@wrap`]. Which is why the flag selects what a
72//! store records rather than what a read asks about: the reads that would need it are not `load`s.
73//!
74//! The race check is not here, because the epoch plane is not written at all and a check against a
75//! plane nobody maintains would either report on every access or on none. That is S6. Neither are
76//! the other plane writes: `meta_begin` and `meta_end` for an automatic instance need the escape
77//! analysis of document 08 section 8.4, and until that exists the only instances the runtime knows
78//! about are the ones the allocator reports, which is also why a store to a local records into a
79//! plane that is not there and costs a call that decides nothing.
80//!
81//! # Why the rank matters
82//!
83//! `rucc-safety` is rank 10, alongside `rucc-lower` and `rucc-opt`, so it can depend on neither.
84//! That is the constraint and not an inconvenience: it consumes IR and produces IR, it never sees
85//! the AST, and `rucc-driver` at rank 13 is what sequences it between the two.
86//! `spec/safe-memory/15-integration.md` section 15.1 argues it out.
87//!
88//! # Stability
89//!
90//! Every crate in the workspace is published, and publishing implies a promise. This one is
91//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
92//! Depend on the `rucc` binary's behaviour, not on this.
93
94#![doc(html_root_url = "https://docs.rs/rucc-safety/0.10.21")]
95
96pub mod boundary;
97pub mod lower;
98pub mod plane;
99pub mod promise;
100pub mod summary;
101pub mod wrap;
102
103pub use boundary::{Sites, WITNESS, witness};
104pub use lower::{Descriptor, SECTION, lower};
105pub use plane::Plane;
106pub use promise::{Kept, promise};
107pub use summary::{Frames, Summary, summarize};
108pub use wrap::{INTERPOSED, PREFIX, redirect};
109
110use rucc_ir::{Def, Extra, Func, Imm, Inst, InstData, Module, Opcode, Type, Value};
111pub use rucc_session::{Promise, Subobject};
112
113/// How many checks a run of [`insert`] put in.
114///
115/// Reported rather than discarded because the number of checks a function starts with is the
116/// denominator of everything document 13 measures, and it is not recoverable later: by the time
117/// the optimizer has run, the checks that were discharged are gone and nothing says how many
118/// there were.
119///
120/// The three counts are kept apart rather than added up because they are discharged by different
121/// rules and at very different rates. Document 07 expects bounds to go away often, lifetime to go
122/// away when the instance does not escape, and derivation to survive, so one number would hide
123/// exactly the thing the measurement is for.
124#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
125pub struct Counts {
126    /// Accesses that were given a bounds check.
127    pub checked: usize,
128    /// Accesses that were given a lifetime check, which is the same set as `checked`.
129    pub live: usize,
130    /// Pointers computed from another pointer that were given a derivation check.
131    pub derived: usize,
132    /// Accesses that got nothing, because the pointer they go through is not a value this pass
133    /// can take the capability of.
134    pub skipped: usize,
135    /// Stores that recorded what the bytes they wrote were stored through.
136    ///
137    /// Not in `--emit=safety-summary` yet, which is the one count here that is not. The summary
138    /// reports a class as a pair, how many went in and how many are left, and nothing discharges a
139    /// plane write today, so the pair would be one number written twice. It goes in beside the
140    /// first rule that removes one.
141    pub judged: usize,
142    /// Copies that carried whatever the bytes they read said over to the bytes they wrote.
143    ///
144    /// Kept apart from `judged` for the reason the three check counts are kept apart. A store
145    /// records a type the compiler knows and a copy records one only the plane knows, so the two
146    /// are discharged by different rules: a store into storage nothing watches can be dropped by
147    /// looking at the store, and a copy cannot be looked at the same way.
148    pub carried: usize,
149    /// Accesses that asked the plane whether the bytes agree with the type they name.
150    ///
151    /// Fewer than `checked`, and the reason is in `ask`: an access the front end did not name a
152    /// type for has no question to put. Without `-fsafety-subobject` it is fewer again, because
153    /// only a read asks, and the reason a store asks only when somebody asked for it is on
154    /// [`rucc_session::Subobject`].
155    pub asked: usize,
156    /// Stores that recorded that the bytes they wrote hold what they wrote.
157    ///
158    /// The same set as `checked` minus the reads, and unlike `judged` it does not thin: a store
159    /// records into the init plane whatever it was storing through, because what the init plane
160    /// holds is whether anything was stored at all.
161    pub wrote: usize,
162    /// Reads that asked the plane whether anything ever wrote the bytes they are about to read.
163    ///
164    /// The same set as the reads in `checked`, and unlike `asked` it does not thin: the question is
165    /// whether the bytes hold anything at all, which is a question about every read whatever type
166    /// the front end did or did not name for it.
167    pub filled: usize,
168    /// Copies that carried whether the bytes they read held anything over to the bytes they wrote.
169    ///
170    /// The same set as `carried`, and counted beside it for the reason `wrote` is counted beside
171    /// `judged`: the two planes will be discharged by different rules, so the day one of them
172    /// thins the numbers have to be able to differ.
173    pub moved: usize,
174    /// Accesses that asked their block whether another `restrict` pointer of it got there first.
175    ///
176    /// Zero without `-fsafety-restrict`, and zero in the overwhelming majority of functions with
177    /// it, because the only accesses that ask are the ones the front end traced back to a
178    /// `restrict` declaration. [`mod@promise`] is where both of those are argued.
179    pub promised: usize,
180    /// Blocks that opened a scope, which is one per `restrict` clique that has an access in it.
181    ///
182    /// Kept apart from `promised` because it is the part of the cost that is paid per call rather
183    /// than per access: two calls and a stack slot, against which a block that checks a thousand
184    /// accesses and a block that checks one look very different.
185    pub scoped: usize,
186}
187
188impl Counts {
189    /// Adds another function's counts to these.
190    fn add(&mut self, other: Counts) {
191        self.checked += other.checked;
192        self.live += other.live;
193        self.derived += other.derived;
194        self.skipped += other.skipped;
195        self.judged += other.judged;
196        self.carried += other.carried;
197        self.asked += other.asked;
198        self.wrote += other.wrote;
199        self.filled += other.filled;
200        self.moved += other.moved;
201        self.promised += other.promised;
202        self.scoped += other.scoped;
203    }
204
205    /// Adds what the `restrict` walk of one function came to.
206    ///
207    /// A second function rather than a second [`Counts`] because that walk counts two things and
208    /// has no opinion about the other ten, and a conversion that filled in ten zeroes would let a
209    /// later count be lost by being added to a zero.
210    fn add_kept(&mut self, kept: Kept) {
211        self.promised += kept.promised;
212        self.scoped += kept.scoped;
213    }
214}
215
216/// Puts checks in every function a module defines.
217///
218/// The whole module rather than a function at a time, because that is the unit the driver hands
219/// around and because the pass has nothing to say about the order: no check depends on anything
220/// outside the function it is in. A declaration has no body and is skipped, for the same reason
221/// the back end skips it.
222///
223/// Whether this runs at all is `-fsafety=`, and the driver decides it. This crate does not read
224/// the flag, because a pass that decides for itself whether it runs is a pass whose effect cannot
225/// be read off the pipeline.
226pub fn run(module: &mut Module, subobject: Subobject, promise: Promise) -> Counts {
227    // Before the walk, because the entries live in the module and a function is borrowed out of
228    // the module while its stores are being instrumented. It is also the reason this is the entry
229    // point rather than [`insert`]: there is one plane per module and every function records into
230    // the same one.
231    let plane = Plane::build(module);
232    // The one thing a pointer typed access cannot work out for itself, which is how wide it is.
233    let width = u64::from(module.datalayout.pointer_bits / 8);
234    let mut counts = Counts::default();
235    for id in module.funcs() {
236        if !module[id].is_declaration() {
237            counts.add(insert(&mut module[id], &plane, width, subobject, promise));
238        }
239    }
240    counts
241}
242
243/// Puts checks in front of every access and every derivation in a function.
244///
245/// Section 6.3: every `load` and `store` gets `check_bounds` and `check_live`, with the
246/// capability coming from `cap_of` on the pointer operand, and every `ptr_add` gets
247/// `check_deriv` on the pointer it was computed from. The size and the alignment are the
248/// access's own, since a check that asked about a different number of bytes from the access it
249/// guards would be checking something the program does not do.
250///
251/// The two access checks are separate instructions rather than one fused check, which section
252/// 6.2.2 asks for and which matters more than it looks: the common case document 07 is built
253/// around is that the bounds check is discharged and the lifetime check is not, or the other way
254/// round for a local whose frame the compiler can see. One instruction would mean keeping both
255/// whenever either survived. Where both do survive, the backend fuses them behind one branch.
256///
257/// Nothing is discharged here. A `check_bounds` on a pointer whose bounds are statically obvious
258/// is still emitted, and the fact propagation in `rucc-opt` is what removes it. That split is the
259/// whole design: this pass is a walk anybody can read, and the deletions are rules that are
260/// verified.
261pub fn insert(
262    func: &mut Func,
263    plane: &Plane,
264    width: u64,
265    subobject: Subobject,
266    promise: Promise,
267) -> Counts {
268    let mut counts = Counts::default();
269    let insts: Vec<Inst> =
270        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
271    for inst in insts {
272        match func[inst].opcode {
273            Opcode::Load | Opcode::Store => match pointer_of(func, inst) {
274                Some(pointer) => {
275                    let capability = check(func, inst, pointer, width);
276                    counts.checked += 1;
277                    counts.live += 1;
278                    if func[inst].opcode == Opcode::Store {
279                        // In front of the store, and only when the build asked for it. Every other
280                        // question at a store is a recording made afterwards, and this is the one
281                        // that can refuse, so it has to be asked while the bytes still say what
282                        // they said before.
283                        if subobject.asks() && ask(func, plane, inst, pointer, capability, width) {
284                            counts.asked += 1;
285                        }
286                        // The init plane's write goes in first so that the type plane's ends up in
287                        // front of it, since both are inserted after the store and the one that
288                        // goes in second is the one that lands nearer to it.
289                        if wrote(func, inst, pointer, width) {
290                            counts.wrote += 1;
291                        }
292                        if judge(func, plane, inst, pointer, width) {
293                            counts.judged += 1;
294                        }
295                    } else {
296                        // Both go in front of the read, and the one that goes in second is the one
297                        // that lands nearer to it, so this order prints the type question and then
298                        // the init question. Either order is correct: neither reads what the other
299                        // wrote and the read happens after both.
300                        if ask(func, plane, inst, pointer, capability, width) {
301                            counts.asked += 1;
302                        }
303                        if filled(func, inst, pointer, capability, width) {
304                            counts.filled += 1;
305                        }
306                    }
307                }
308                None => counts.skipped += 1,
309            },
310            Opcode::Memcpy | Opcode::Memmove => {
311                // Second for the reason a store's two are in the order they are in.
312                if moved(func, inst) {
313                    counts.moved += 1;
314                }
315                if carry(func, inst) {
316                    counts.carried += 1;
317                }
318            }
319            Opcode::PtrAdd => {
320                if derivation(func, inst) {
321                    counts.derived += 1;
322                } else {
323                    counts.skipped += 1;
324                }
325            }
326            _ => {}
327        }
328    }
329    // Last, so that the check it puts in front of an access lands after the bounds check that is
330    // already there. It is its own walk rather than another arm above because what it puts in is
331    // not one check per access: the scopes are per function and the two calls that keep one go in
332    // the entry block and at every exit.
333    if promise.checks() {
334        counts.add_kept(promise::promise(func, width));
335    }
336    counts
337}
338
339/// The pointer an access goes through.
340///
341/// A `load` reads through its first operand and a `store` writes through its second, the value
342/// being written coming first because that is the order the text writes them in.
343fn pointer_of(func: &Func, access: Inst) -> Option<Value> {
344    let args = &func[func[access].args];
345    let at = match func[access].opcode {
346        Opcode::Load => 0,
347        Opcode::Store => 1,
348        _ => return None,
349    };
350    let &value = args.get(at)?;
351    func[value].ty.is_ptr().then_some(value)
352}
353
354/// Puts `cap_of`, `check_bounds` and `check_live` immediately before one access.
355///
356/// Gives back the capability the two checks read, so that a third check on the same access can read
357/// the same one rather than taking it again. An access with no payload gets nothing and answers
358/// nothing, which is the shape a caller has to handle anyway.
359fn check(func: &mut Func, access: Inst, pointer: Value, width: u64) -> Option<Value> {
360    let span = func.span(access);
361    let Extra::Mem(info) = func[access].extra else { return None };
362    let mut info = func[info];
363    info.size = covered(func, access, info.size, width);
364    // Not the padding after it. What a check is about is the bytes the access touches, and the
365    // padding is about what a store records rather than about what it reads or writes.
366    info.owns = 0;
367
368    let capability = cap_of(func, pointer, access);
369
370    // The check reads the same bytes the access does, so it carries the access's own payload
371    // rather than a copy of it that could later disagree.
372    let args = func.push_values(&[capability, pointer]);
373    let extra = Extra::Mem(func.add_mem(info));
374    let bounds =
375        func.create_inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[], span);
376    func.insert_before(bounds, access);
377
378    // No payload on this one. Whether the capability still names whoever owns the address is a
379    // question about the pointer and not about how many bytes are being read through it.
380    let args = func.push_values(&[capability, pointer]);
381    let live = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[], span);
382    func.insert_before(live, access);
383
384    Some(capability)
385}
386
387/// Puts a `meta_type` immediately after one store, recording what its bytes were stored through.
388///
389/// The judgement of C 6.5: a store through an lvalue of type `T` sets the effective type of what it
390/// wrote to `T`, and the plane is where that is written down. What the store names is the aliasing
391/// node the walk put on it, and [`Plane::entry`] is the translation from that to the entry the
392/// plane holds, including the two cases that are not a type.
393///
394/// After the store rather than before it, which is the one thing about the placement that matters.
395/// The bytes are stored through that type once the store has happened, and a plane that said so
396/// first would be describing a store that the bounds check in front of it may yet refuse.
397///
398/// The length is a value rather than a field of the payload because that is the shape the opcode
399/// has, and it is a `meta_type` over a range because one store writes a run of bytes. Where the
400/// value comes from is [`extent`].
401fn judge(func: &mut Func, plane: &Plane, store: Inst, pointer: Value, width: u64) -> bool {
402    let Extra::Mem(info) = func[store].extra else { return false };
403    let size = covered(func, store, func[info].size, width);
404    // A store whose width nothing states covers no bytes anybody can name, and a plane write over
405    // nothing is an instruction with no effect.
406    if size == 0 {
407        return false;
408    }
409    let node = plane.entry(func[info].tbaa);
410
411    let span = func.span(store);
412    let (made, length) = extent(func, store, size);
413    let args = func.push_values(&[pointer, length]);
414    let data = InstData { args, extra: Extra::Node(node), ..InstData::new(Opcode::MetaType) };
415    let judged = func.create_inst(data, &[], span);
416    // After the constant it reads rather than after the store, since both go in the same place and
417    // the one that goes in second ends up in front.
418    func.insert_after(judged, made);
419    true
420}
421
422/// Puts a `check_type` immediately before one read, asking whether the bytes agree with the type
423/// they are about to be read as.
424///
425/// Judgement J3, and the half of the type plane that decides something. The two writes record what
426/// a store and a copy left behind, and this is the question they were recorded for: the effective
427/// type rule of C 6.5 says an object's stored value may only be read through a type compatible with
428/// the one it was stored through, and the plane is where the compiler wrote down which that was.
429///
430/// # Why only a read
431///
432/// A store does not ask, it answers. The plane covers storage the allocator reported and nothing
433/// else, which is exactly the storage C gives no declared type, and the effective type of such an
434/// object is whatever the last store through it set. So a store cannot disagree with the plane: it
435/// is what makes the plane say what it says, and a check in front of one would refuse the reuse of
436/// a buffer that the standard permits.
437///
438/// # Why a read that names no type asks nothing
439///
440/// An access whose payload carries no aliasing node is an access the front end did not say the type
441/// of, which is a copy of an aggregate, an array, or anything else reached by address. That is not
442/// the same as reading bytes nothing has been stored through, and the plane's untyped entry means
443/// the second one. Asking with it would refuse every read of a structure whose members had been
444/// stored through their own types, which is every correct program that has one.
445///
446/// The check goes in front of the read, behind the bounds and lifetime checks that are also in
447/// front of it. A question about what the bytes say is worth asking only once somebody owns them,
448/// and what the runtime answers for an address no region covers is nothing rather than a refusal.
449fn ask(
450    func: &mut Func,
451    plane: &Plane,
452    read: Inst,
453    pointer: Value,
454    capability: Option<Value>,
455    width: u64,
456) -> bool {
457    let Some(capability) = capability else { return false };
458    let Extra::Mem(at) = func[read].extra else { return false };
459    let mut info = func[at];
460    let Some(node) = info.tbaa else { return false };
461    info.size = covered(func, read, info.size, width);
462    // A read whose width nothing states reads no bytes anybody can name, the same way a store of
463    // none writes none.
464    if info.size == 0 {
465        return false;
466    }
467    // The payload the check carries is the access's, with the aliasing node replaced by the plane
468    // entry for it, because the plane and the aliasing tree are two vocabularies and the question is
469    // put in the plane's.
470    info.tbaa = Some(plane.entry(Some(node)));
471    // As in `access_checks`, and here it could never be anything else: a read carries no padding.
472    info.owns = 0;
473
474    let span = func.span(read);
475    let args = func.push_values(&[capability, pointer]);
476    let extra = Extra::Mem(func.add_mem(info));
477    let data = InstData { args, extra, ..InstData::new(Opcode::CheckType) };
478    let asked = func.create_inst(data, &[], span);
479    func.insert_before(asked, read);
480    true
481}
482
483/// Puts a `meta_type_copy` immediately after one copy, carrying what its source said to its
484/// destination.
485///
486/// The other half of the judgement C 6.5 describes. A copy does not store through a type, so there
487/// is no type for the compiler to record: what the copied bytes are is whatever the bytes they came
488/// from were, and the only place that is written down is the plane over the source. So this names
489/// two ranges and no node, and the runtime moves the entries across.
490///
491/// Without it the destination would keep whatever the bytes there said before the copy, which is
492/// the thing that makes a check against the plane unusable. A structure copied into a fresh
493/// allocation would come out untyped at best and, once the allocation had been reused, wrong at
494/// worst, and the very next read of a field would be refused on a program that is correct.
495///
496/// After the copy rather than before it, for the same reason a store's judgement goes after the
497/// store. The bytes say the new thing once the copy has happened. Reading the source's plane
498/// afterwards is the same answer as reading it before, overlap included, because a copy writes no
499/// plane entries of its own.
500fn carry(func: &mut Func, copy: Inst) -> bool {
501    let Extra::Mem(info) = func[copy].extra else { return false };
502    // A copy of a known size is what the opcode is, and the verifier refuses one whose payload says
503    // zero, so this is a shape that does not arise rather than a case being handled.
504    let size = func[info].size;
505    if size == 0 {
506        return false;
507    }
508    let [to, from] = func[func[copy].args] else { return false };
509
510    let span = func.span(copy);
511    let (made, length) = extent(func, copy, size);
512    let args = func.push_values(&[to, from, length]);
513    let data = InstData { args, ..InstData::new(Opcode::MetaTypeCopy) };
514    let carried = func.create_inst(data, &[], span);
515    // After the constant it reads rather than after the copy, since both go in the same place and
516    // the one that goes in second ends up in front.
517    func.insert_after(carried, made);
518    true
519}
520
521/// Puts a `meta_init` immediately after one store, recording that its bytes hold what it wrote.
522///
523/// The judgement of `spec/safe-memory/09-type-init-and-races.md` section 9.2, and the write the
524/// init plane is made of. An instance beginning is the only thing that makes a byte unwritten, and
525/// this is the only thing that makes one written again, so between the two of them the plane holds
526/// exactly the bytes the monitor watched a store land on.
527///
528/// After the store, and for the same reason the type plane's judgement goes after one: the bytes
529/// hold what was written once the store has happened, and saying so first would be describing a
530/// store the bounds check in front of it may yet refuse.
531///
532/// # Where the padding rule lives
533///
534/// Section 9.3 says a store that writes an object as a whole initializes it as a whole, padding
535/// included, and that a member by member fill leaves the padding alone. Nothing here implements
536/// that, and nothing has to. Both arrive as a range and the range is the access's own width: a
537/// store through a member of a structure is a `store` of the member's width and names the member,
538/// and a structure assigned whole, a `= {0}`, a `memset` and a `memcpy` are all a copy of `sizeof`
539/// bytes and name the object. The rule falls out of what the front end already lowered rather than
540/// out of anything this pass knows about structures, which is what keeps it one rule rather than a
541/// special case per shape.
542///
543/// # Why this does not thin
544///
545/// A store records into the init plane whatever type it was storing through, including the two
546/// cases the type plane has no entry for. What the init plane holds is whether anything was stored
547/// at all, and the answer to that does not depend on what the store thought it was writing, so
548/// every store that covers a byte records it.
549fn wrote(func: &mut Func, store: Inst, pointer: Value, width: u64) -> bool {
550    let Extra::Mem(info) = func[store].extra else { return false };
551    // The padding after a member, where the front end was asked to say how far it goes. That is
552    // the whole of `-fsafety-init=nopadding` and it is a number rather than a mode here, because
553    // what the padding is takes a record's layout and this pass reads IR.
554    let size = covered(func, store, func[info].size, width).max(u64::from(func[info].owns));
555    // A store whose width nothing states writes no bytes anybody can name, the same way the type
556    // plane's judgement over one records nothing.
557    if size == 0 {
558        return false;
559    }
560
561    let span = func.span(store);
562    let (made, length) = extent(func, store, size);
563    let args = func.push_values(&[pointer, length]);
564    let data = InstData { args, ..InstData::new(Opcode::MetaInit) };
565    let judged = func.create_inst(data, &[], span);
566    // After the constant it reads rather than after the store, since both go in the same place and
567    // the one that goes in second ends up in front.
568    func.insert_after(judged, made);
569    true
570}
571
572/// Puts a `check_init` in front of one read, asking whether anything ever wrote the bytes it is
573/// about to read.
574///
575/// Document 03's Y6, and the class MSan exists for. The two writes are in, a store recording that
576/// the bytes it wrote hold something and a copy carrying whether the bytes it read held anything,
577/// so the plane now says something true about every byte a program wrote and this is the question
578/// those writes were recorded for.
579///
580/// # Why every read and not only the ones that named a type
581///
582/// [`ask`] passes over a read the front end named no type for, because a question about which type
583/// bytes hold has nothing to ask when the access names none. This one has no such case: the plane
584/// holds one bit per byte and the bit says whether anything was ever stored there, which is a fact
585/// about the bytes and not about the access, so a read of an aggregate by address asks it just as a
586/// read of an `int` does.
587///
588/// # Padding
589///
590/// It does not come up here and that is worth writing down, because section 9.3 is where the
591/// padding rule lives and this is the check the rule is about. A read compiled into a `load` reads
592/// a member, and a member is never padding, so no `load` in any program covers a byte a
593/// member-by-member fill left alone. The reads that do cover padding are `memcmp` of two
594/// structures, hashing one, and handing one to `write`, and every one of those is a call into the
595/// movement group of `crate::wrap` rather than a `load`. That is where `-fsafety-init=padding` will
596/// have something to select, and it is why the flag is not here.
597///
598/// # Why a whole width and not a byte
599///
600/// The payload's size, the same one [`ask`] uses, so a read that straddles the end of what was
601/// written is refused on the first byte nothing wrote rather than on the byte the address names.
602/// A read of four bytes where two were written is a read of memory that was never written, and
603/// reporting it at the access is the only place a report means anything.
604fn filled(
605    func: &mut Func,
606    read: Inst,
607    pointer: Value,
608    capability: Option<Value>,
609    width: u64,
610) -> bool {
611    let Some(capability) = capability else { return false };
612    let Extra::Mem(at) = func[read].extra else { return false };
613    let mut info = func[at];
614    info.size = covered(func, read, info.size, width);
615    // A read whose width nothing states reads no bytes anybody can name, as in [`ask`].
616    if info.size == 0 {
617        return false;
618    }
619    // The plane holds no types, so whatever the access named is not a thing this question is in
620    // terms of, and carrying it would suggest the check compares against it.
621    info.tbaa = None;
622
623    let span = func.span(read);
624    let args = func.push_values(&[capability, pointer]);
625    let extra = Extra::Mem(func.add_mem(info));
626    let data = InstData { args, extra, ..InstData::new(Opcode::CheckInit) };
627    let asked = func.create_inst(data, &[], span);
628    func.insert_before(asked, read);
629    true
630}
631
632/// Puts a `meta_init_copy` immediately after one copy, carrying whether its source held anything
633/// over to its destination.
634///
635/// The other half of the same write, and the thing that makes an infoleak visible rather than what
636/// hides it. A copy writes no values of its own: whether a destination byte holds anything is
637/// whether the byte it came from did, and the only place that is written down is the plane over the
638/// source. So this names two ranges and a length and nothing else, exactly as the type plane's
639/// carriage does.
640///
641/// A structure filled member by member and then handed whole to `write` or to a socket is the case
642/// worth stating. Marking the destination written would lose it, because the bytes that leave the
643/// program would be bytes the plane had just been told were fine, and those are exactly the bytes
644/// of CWE-200. Carrying the source's answer keeps the padding unwritten all the way to the
645/// boundary, which is where the read that matters happens.
646fn moved(func: &mut Func, copy: Inst) -> bool {
647    let Extra::Mem(info) = func[copy].extra else { return false };
648    // As in `carry`: the verifier refuses a copy whose payload says zero, so this is a shape that
649    // does not arise rather than a case being handled.
650    let size = func[info].size;
651    if size == 0 {
652        return false;
653    }
654    let [to, from] = func[func[copy].args] else { return false };
655
656    let span = func.span(copy);
657    let (made, length) = extent(func, copy, size);
658    let args = func.push_values(&[to, from, length]);
659    let data = InstData { args, ..InstData::new(Opcode::MetaInitCopy) };
660    let carried = func.create_inst(data, &[], span);
661    func.insert_after(carried, made);
662    true
663}
664
665/// The constant a plane write over a range reads its length from, put in just after `at`.
666///
667/// Gives back the instruction as well as the value, because the caller inserts itself after the
668/// constant rather than after `at`: both go in the same place, and the one that goes in second ends
669/// up in front of the one that went in first.
670///
671/// Written in sixty four bits here and put into the target's width by [`lower::lower`], which is
672/// where the only thing that knows the target's width is.
673fn extent(func: &mut Func, at: Inst, size: u64) -> (Inst, Value) {
674    let span = func.span(at);
675    let word = Type::int(64);
676    let extra = Extra::Imm(func.add_imm(Imm::int(i128::from(size), word)));
677    let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[word], span);
678    func.insert_after(made, at);
679    let length = func[made].results().next().expect("a constant created with one result has one");
680    (made, length)
681}
682
683/// How many bytes an access covers.
684///
685/// An ordinary `load` or `store` leaves the `size` field of its payload at zero and takes its width
686/// from the type instead, which is fine for an access and no use at all to a check: a check is
687/// asked how many bytes are being touched and has no type of its own to read. So the width is
688/// worked out here and written into the copy of the payload the check carries, and an access that
689/// did fill the field in keeps what it said.
690///
691/// `width` is the target's pointer width in bytes, and it is a parameter because a pointer is the
692/// one type in the IR that has no width of its own. Reading a zero off `Type::PTR` and passing it
693/// on is what made every check over a pointer decide over a single byte, which is #953.
694fn covered(func: &Func, access: Inst, stated: u64, width: u64) -> u64 {
695    if stated != 0 {
696        return stated;
697    }
698    // A `load` produces the value and a `store` takes it as its first operand.
699    let ty = match func[access].opcode {
700        Opcode::Load => func[access].results().next().map(|value| func[value].ty),
701        Opcode::Store => func[func[access].args].first().map(|&value| func[value].ty),
702        _ => None,
703    };
704    ty.map_or(0, |ty| {
705        if ty.is_ptr() {
706            return width;
707        }
708        u64::from(ty.bits().div_ceil(8)) * u64::from(ty.lanes())
709    })
710}
711
712/// Puts `cap_of` and `check_deriv` immediately before one `ptr_add`.
713///
714/// Judgement J2, which is the one that catches a pointer walking off its object *before* anything
715/// is read through it. C says computing such a pointer is already undefined, and catching it here
716/// rather than at the eventual access is what lets the report name the loop that ran too far
717/// instead of whatever unrelated line finally dereferenced the result.
718///
719/// The check is handed the pointer the derivation produced, so it goes immediately after the
720/// derivation rather than in front of it like the access checks. That is what section 6.2.2's
721/// third operand means: the judgement is about where the derived pointer landed, and there is
722/// nothing to decide before it has landed.
723///
724/// The fourth operand is the stride, which is how wide one element of whatever is being stepped
725/// over is. Document 03 section 3.1 widened S5's window to `[lo - stride, hi]`, so the runtime
726/// cannot decide the low end without it, and it is a value rather than a constant because a walk
727/// over a variable length array steps by a width the program computes.
728fn derivation(func: &mut Func, add: Inst) -> bool {
729    let Some(&base) = func[func[add].args].first() else { return false };
730    if !func[base].ty.is_ptr() {
731        return false;
732    }
733    let Some(derived) = func[add].results().next() else { return false };
734
735    let span = func.span(add);
736    let width = stride(func, add);
737    let capability = cap_of(func, base, add);
738    let args = func.push_values(&[capability, base, derived, width]);
739    let check = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[], span);
740    func.insert_after(check, add);
741    true
742}
743
744/// How wide one element of the thing a `ptr_add` steps over is.
745///
746/// C computes a byte offset before the pointer arithmetic happens, so `ptr_add` takes bytes and the
747/// element width is not in it. What is in it is the shape the frontend left behind, because this
748/// pass runs before the optimizer and the offset operand is still exactly what lowering emitted:
749/// `mul index, k` for a constant width, `mul index, w` for one the program computes, either of them
750/// under a `sub 0, ...` for a walk that goes backwards, and the bare index when the width is one.
751///
752/// So the width is read back off that shape. Getting it wrong is not a soundness question: the
753/// stride only decides how far below an object a derivation may land before it is refused, and an
754/// access below the object is refused by judgement J1 either way. A shape nobody recognises answers
755/// one byte, which is the strict reading of C and is where this check was before the window moved.
756fn stride(func: &mut Func, add: Inst) -> Value {
757    // The offset is the one operand of a `ptr_add` that is an integer, so its type is the width an
758    // address is computed in and is the type the check's fourth operand has to have.
759    let Some(&offset) = func[func[add].args].get(1) else { return one(func, add, Type::int(64)) };
760    let word = func[offset].ty;
761    // A walk that goes backwards negates the offset rather than the width, so the shape underneath
762    // is the same one a forward walk has.
763    let forwards = match operand_of(func, offset, Opcode::Sub, 0) {
764        Some(zero) if is_zero(func, zero) => operand_of(func, offset, Opcode::Sub, 1),
765        _ => None,
766    };
767    let scaled = forwards.unwrap_or(offset);
768    match operand_of(func, scaled, Opcode::Mul, 1) {
769        // The width is the right operand because `step` builds the multiply that way round, with
770        // the index on the left and the size of one element on the right.
771        Some(width) if func[width].ty == word => width,
772        _ => one(func, add, word),
773    }
774}
775
776/// Operand `index` of the instruction that produced `value`, when that instruction is `opcode`.
777fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
778    let Def::Result { inst, .. } = func[value].def else { return None };
779    if func[inst].opcode != opcode {
780        return None;
781    }
782    func[func[inst].args].get(index).copied()
783}
784
785/// Whether a value is a constant zero, which is the left half of how a backwards walk is spelled.
786fn is_zero(func: &Func, value: Value) -> bool {
787    let Def::Result { inst, .. } = func[value].def else { return false };
788    match func[inst].extra {
789        Extra::Imm(imm) if func[inst].opcode == Opcode::IConst => func[imm].bits() == 0,
790        _ => false,
791    }
792}
793
794/// A stride of one byte, which is what a shape this pass does not recognise answers.
795fn one(func: &mut Func, at: Inst, ty: Type) -> Value {
796    let span = func.span(at);
797    let extra = Extra::Imm(func.add_imm(Imm::int(1, ty)));
798    let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
799    func.insert_before(made, at);
800    func[made].results().next().expect("a constant created with one result has one")
801}
802
803/// Puts a `cap_of` for `pointer` immediately before `at`, and gives back what it produced.
804fn cap_of(func: &mut Func, pointer: Value, at: Inst) -> Value {
805    let span = func.span(at);
806    let args = func.push_values(&[pointer]);
807    let cap =
808        func.create_inst(InstData { args, ..InstData::new(Opcode::CapOf) }, &[Type::CAP], span);
809    func.insert_before(cap, at);
810    func[cap].results().next().expect("cap_of produces one value")
811}
812
813#[cfg(test)]
814mod tests {
815    use rucc_base::Interner;
816    use rucc_ir::{
817        Builder, Flags, MemInfo, MemOrder, Meta, MetaNode, PlaneNode, Restrict, Signature,
818        TbaaNode, print_func, verify_func,
819    };
820    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
821
822    use super::*;
823
824    fn target() -> TargetInfo {
825        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
826    }
827
828    /// A module to record into, and the plane entries it holds.
829    ///
830    /// The plane is the module's, so a test that instruments a bare function still has to have one
831    /// to hand. It is empty of types here, since the functions these tests build name none.
832    fn planed(names: &mut Interner, unit: &str) -> (Module, Plane) {
833        let mut module = Module::new(names.intern(unit), &target());
834        let plane = Plane::build(&mut module);
835        (module, plane)
836    }
837
838    /// A function that loads through its parameter and stores what it read back.
839    fn one_of_each(names: &mut Interner) -> Func {
840        let i32_ = Type::int(32);
841        let mut func = Func::new(
842            names.intern("both"),
843            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
844        );
845        let entry = func.create_block();
846        let p = func.append_param(entry, Type::PTR);
847
848        let info = MemInfo {
849            size: 4,
850            align: 4,
851            order: MemOrder::NotAtomic,
852            tbaa: None,
853            owns: 0,
854            restrict: Restrict::NONE,
855        };
856        let mut b = Builder::new(&mut func, entry);
857        let args = b.func().push_values(&[p]);
858        let extra = Extra::Mem(b.func().add_mem(info));
859        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
860        let args = b.func().push_values(&[loaded, p]);
861        let extra = Extra::Mem(b.func().add_mem(info));
862        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
863        b.ret(&[loaded]);
864        func
865    }
866
867    /// The same shape, with the two accesses said to go through two `restrict` pointers of a block.
868    fn promising(names: &mut Interner) -> Func {
869        let i32_ = Type::int(32);
870        let mut func = Func::new(
871            names.intern("kernel"),
872            Signature::new().with_params(&[Type::PTR, Type::PTR]),
873        );
874        let entry = func.create_block();
875        let to = func.append_param(entry, Type::PTR);
876        let from = func.append_param(entry, Type::PTR);
877
878        let info = MemInfo {
879            size: 4,
880            align: 4,
881            order: MemOrder::NotAtomic,
882            tbaa: None,
883            owns: 0,
884            restrict: Restrict { clique: 1, base: 1 },
885        };
886        let mut b = Builder::new(&mut func, entry);
887        let read = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..info };
888        let loaded = b.load(i32_, from, read, Flags::default());
889        b.store(loaded, to, info, Flags::default());
890        b.ret(&[]);
891        func
892    }
893
894    #[test]
895    fn the_restrict_checks_wait_until_the_build_asks_for_them() {
896        // The one check in this crate that is off by default. What it costs is paid by the blocks
897        // that declare `restrict` pointers and nobody else, and what it reports includes programs
898        // the standard permits, so which it is is the build's decision. `rucc_session::Promise` is
899        // where that is argued.
900        let mut names = Interner::new();
901        let (_, plane) = planed(&mut names, "kernel.c");
902
903        let mut quiet = promising(&mut names);
904        let counts = insert(&mut quiet, &plane, 8, Subobject::Off, Promise::Off);
905        assert_eq!((counts.promised, counts.scoped), (0, 0));
906
907        let mut asked = promising(&mut names);
908        let counts = insert(&mut asked, &plane, 8, Subobject::Off, Promise::Blocks);
909        assert_eq!((counts.promised, counts.scoped), (2, 1));
910    }
911
912    #[test]
913    fn every_access_gets_a_bounds_check_and_a_lifetime_check() {
914        let mut names = Interner::new();
915        let mut func = one_of_each(&mut names);
916        let (module, plane) = planed(&mut names, "both.c");
917        assert_eq!(
918            insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
919            Counts { checked: 2, live: 2, judged: 1, wrote: 1, filled: 1, ..Counts::default() }
920        );
921
922        assert_eq!(
923            print_func(&module, &func, &names),
924            // The plane writes are after the store and not in front of it. The bytes were stored
925            // through that type, and were stored at all, once the store has happened, and the
926            // check in front of it may yet refuse the store both of them are about.
927            "func @both(ptr) -> i32, linkage(external) {\n\
928             block0(%0: ptr):\n    \
929             %1 = cap_of %0\n    \
930             check_bounds %1, %0, size 4, align 4\n    \
931             check_live %1, %0\n    \
932             check_init %1, %0, size 4, align 4\n    \
933             %2 = load.i32 %0, size 4, align 4\n    \
934             %3 = cap_of %0\n    \
935             check_bounds %3, %0, size 4, align 4\n    \
936             check_live %3, %0\n    \
937             store %2 -> %0, size 4, align 4\n    \
938             %4 = iconst.i64 4\n    \
939             meta_type %0, %4, tbaa !1\n    \
940             %5 = iconst.i64 4\n    \
941             meta_init %0, %5\n    \
942             return %2\n\
943             }\n"
944        );
945    }
946
947    /// A function that loads a pointer through its parameter.
948    ///
949    /// The payload states no size, which is what the front end emits: a load takes its width from
950    /// the type it produces, and for a pointer that is the one type with no width of its own.
951    fn one_pointer_read(names: &mut Interner) -> Func {
952        let mut func = Func::new(
953            names.intern("deref"),
954            Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR]),
955        );
956        let entry = func.create_block();
957        let p = func.append_param(entry, Type::PTR);
958
959        let info = MemInfo {
960            size: 0,
961            align: 8,
962            order: MemOrder::NotAtomic,
963            tbaa: None,
964            owns: 0,
965            restrict: Restrict::NONE,
966        };
967        let mut b = Builder::new(&mut func, entry);
968        let args = b.func().push_values(&[p]);
969        let extra = Extra::Mem(b.func().add_mem(info));
970        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
971        b.ret(&[loaded]);
972        func
973    }
974
975    #[test]
976    fn an_access_that_reads_a_pointer_is_checked_over_the_targets_pointer_width() {
977        // A pointer is the one type in the IR with no width of its own, so the width has to come
978        // from the target. Answering zero is what left a bounds check over a pointer deciding
979        // about a single byte and left the init question out of it altogether, which was #953.
980        let mut names = Interner::new();
981        let mut func = one_pointer_read(&mut names);
982        let (module, plane) = planed(&mut names, "deref.c");
983        assert_eq!(
984            insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
985            Counts { checked: 1, live: 1, filled: 1, ..Counts::default() }
986        );
987
988        let printed = print_func(&module, &func, &names);
989        assert!(printed.contains("check_bounds %1, %0, size 8, align 8\n"), "{printed}");
990        assert!(printed.contains("check_init %1, %0, size 8, align 8\n"), "{printed}");
991    }
992
993    #[test]
994    fn a_pointer_width_of_four_is_what_a_thirty_two_bit_target_gets() {
995        // The number is the target's and not this crate's, so a build for a target where a pointer
996        // is four bytes asks about four.
997        let mut names = Interner::new();
998        let mut func = one_pointer_read(&mut names);
999        let (module, plane) = planed(&mut names, "deref.c");
1000        insert(&mut func, &plane, 4, Subobject::Off, Promise::Off);
1001
1002        let printed = print_func(&module, &func, &names);
1003        assert!(printed.contains("check_bounds %1, %0, size 4, align 8\n"), "{printed}");
1004    }
1005
1006    /// A module with one aliasing node under the root, and the plane built over it.
1007    ///
1008    /// Two nodes rather than one, because the root is the character type and a type of its own has
1009    /// to hang under something. What comes back is the module, the plane, and the node for `int`.
1010    fn typed(names: &mut Interner, unit: &str) -> (Module, Plane, Meta) {
1011        let mut module = Module::new(names.intern(unit), &target());
1012        let root = names.intern("char");
1013        let root =
1014            module.add_meta(MetaNode::Tbaa(TbaaNode { name: root, parent: None, offset: 0 }));
1015        let int = names.intern("int");
1016        let int =
1017            module.add_meta(MetaNode::Tbaa(TbaaNode { name: int, parent: Some(root), offset: 0 }));
1018        let plane = Plane::build(&mut module);
1019        (module, plane, int)
1020    }
1021
1022    /// A function that reads through its parameter as an `int`, naming that type on the access.
1023    fn reading(names: &mut Interner, node: Option<Meta>) -> Func {
1024        let i32_ = Type::int(32);
1025        let mut func = Func::new(
1026            names.intern("read"),
1027            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1028        );
1029        let entry = func.create_block();
1030        let p = func.append_param(entry, Type::PTR);
1031        let info = MemInfo {
1032            size: 0,
1033            align: 4,
1034            order: MemOrder::NotAtomic,
1035            tbaa: node,
1036            owns: 0,
1037            restrict: Restrict::NONE,
1038        };
1039        let mut b = Builder::new(&mut func, entry);
1040        let args = b.func().push_values(&[p]);
1041        let extra = Extra::Mem(b.func().add_mem(info));
1042        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1043        b.ret(&[loaded]);
1044        func
1045    }
1046
1047    #[test]
1048    fn a_read_asks_the_plane_whether_the_bytes_agree_with_the_type_it_reads_them_as() {
1049        // Judgement J3, which is what the two plane writes were recorded for. The question is put
1050        // in the plane's vocabulary rather than the aliasing tree's, so what the check carries is
1051        // the entry for `int` and not the node for it.
1052        let mut names = Interner::new();
1053        let (module, plane, int) = typed(&mut names, "read.c");
1054        let mut func = reading(&mut names, Some(int));
1055
1056        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).asked, 1);
1057
1058        let printed = print_func(&module, &func, &names);
1059        let entry = plane.entry(Some(int));
1060        assert_eq!(module[entry], MetaNode::Plane(PlaneNode::Type(int)));
1061        // Four bytes, which the payload does not say and the type of the value read does, and the
1062        // check is in front of the read rather than after it.
1063        let wanted = format!("check_type %1, %0, size 4, align 4, tbaa !{}\n", entry.index());
1064        assert!(printed.contains(&wanted), "{printed}");
1065        let asked = printed.find(&wanted).expect("the check is there");
1066        let read = printed.find("load.i32").expect("and so is the read");
1067        assert!(asked < read, "{printed}");
1068
1069        if let Err(errors) = verify_func(&module, &func, &names) {
1070            panic!("that was expected to be believed: {errors:#?}");
1071        }
1072    }
1073
1074    #[test]
1075    fn a_read_the_front_end_named_no_type_for_asks_nothing() {
1076        // An aggregate, an array, or anything else reached by address. The plane's untyped entry
1077        // means bytes nothing has stored through, which is a different statement from the front
1078        // end not having said what the access is through, and asking with it would refuse every
1079        // read of a structure whose members were stored through their own types.
1080        let mut names = Interner::new();
1081        let (module, plane, _) = typed(&mut names, "copy.c");
1082        let mut func = reading(&mut names, None);
1083
1084        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).asked, 0);
1085        let printed = print_func(&module, &func, &names);
1086        assert!(!printed.contains("check_type"), "{printed}");
1087    }
1088
1089    /// A function that writes through its parameter as an `int`, naming that type on the access.
1090    fn writing(names: &mut Interner, node: Option<Meta>) -> Func {
1091        let i32_ = Type::int(32);
1092        let mut func =
1093            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i32_]));
1094        let entry = func.create_block();
1095        let p = func.append_param(entry, Type::PTR);
1096        let v = func.append_param(entry, i32_);
1097        let info = MemInfo {
1098            size: 0,
1099            align: 4,
1100            order: MemOrder::NotAtomic,
1101            tbaa: node,
1102            owns: 0,
1103            restrict: Restrict::NONE,
1104        };
1105        let mut b = Builder::new(&mut func, entry);
1106        let args = b.func().push_values(&[v, p]);
1107        let extra = Extra::Mem(b.func().add_mem(info));
1108        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1109        b.ret(&[]);
1110        func
1111    }
1112
1113    #[test]
1114    fn a_store_asks_the_plane_too_once_the_build_has_said_the_member_matters() {
1115        // Row S4, which is a write that leaves one member and lands in the next. Read literally a
1116        // store like that is a program retyping storage it owns, which C 6.5 permits, so the
1117        // question is only put when somebody asked for it to be put.
1118        let mut names = Interner::new();
1119        let (module, plane, int) = typed(&mut names, "member.c");
1120        let mut func = writing(&mut names, Some(int));
1121
1122        let counts = insert(&mut func, &plane, 8, Subobject::Members, Promise::Off);
1123        assert_eq!((counts.asked, counts.judged), (1, 1));
1124
1125        let printed = print_func(&module, &func, &names);
1126        let entry = plane.entry(Some(int));
1127        let wanted = format!("check_type %2, %0, size 4, align 4, tbaa !{}\n", entry.index());
1128        assert!(printed.contains(&wanted), "{printed}");
1129        // In front of the store, because the bytes say what they said before it runs, and the
1130        // recording this pass makes afterwards is what would make the answer yes.
1131        let asked = printed.find(&wanted).expect("the check is there");
1132        let wrote = printed.find("store %1").expect("and so is the store");
1133        let recorded = printed.find("meta_type").expect("and so is the recording");
1134        assert!(asked < wrote && wrote < recorded, "{printed}");
1135
1136        if let Err(errors) = verify_func(&module, &func, &names) {
1137            panic!("that was expected to be believed: {errors:#?}");
1138        }
1139    }
1140
1141    #[test]
1142    fn a_store_the_front_end_named_no_type_for_asks_nothing_whatever_the_build_asked() {
1143        // The same reason a read of one does not. An access with no aliasing node is one the front
1144        // end did not say the type of, which is not the same as bytes nothing has been stored
1145        // through, and the plane has no way to tell the question apart from the answer.
1146        let mut names = Interner::new();
1147        let (module, plane, _) = typed(&mut names, "aggregate.c");
1148        let mut func = writing(&mut names, None);
1149
1150        assert_eq!(insert(&mut func, &plane, 8, Subobject::Members, Promise::Off).asked, 0);
1151        let printed = print_func(&module, &func, &names);
1152        assert!(!printed.contains("check_type"), "{printed}");
1153    }
1154
1155    #[test]
1156    fn a_store_answers_the_question_rather_than_asking_it() {
1157        // The plane covers storage the allocator reported, which is the storage C gives no declared
1158        // type, and the effective type of one of those is whatever the last store set. So a store
1159        // cannot disagree with the plane unless the build asked it to, and a check in front of one
1160        // by default would refuse the reuse of a buffer that the standard permits.
1161        let mut names = Interner::new();
1162        let (module, plane, int) = typed(&mut names, "write.c");
1163        let i32_ = Type::int(32);
1164        let mut func =
1165            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i32_]));
1166        let entry = func.create_block();
1167        let p = func.append_param(entry, Type::PTR);
1168        let v = func.append_param(entry, i32_);
1169        let info = MemInfo {
1170            size: 0,
1171            align: 4,
1172            order: MemOrder::NotAtomic,
1173            tbaa: Some(int),
1174            owns: 0,
1175            restrict: Restrict::NONE,
1176        };
1177        let mut b = Builder::new(&mut func, entry);
1178        let args = b.func().push_values(&[v, p]);
1179        let extra = Extra::Mem(b.func().add_mem(info));
1180        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1181        b.ret(&[]);
1182
1183        let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1184        assert_eq!((counts.judged, counts.asked), (1, 0));
1185        let printed = print_func(&module, &func, &names);
1186        assert!(!printed.contains("check_type"), "{printed}");
1187    }
1188
1189    #[test]
1190    fn a_store_records_the_type_it_stored_through() {
1191        // The judgement of C 6.5, which is the half of the type plane the compiler makes rather
1192        // than asks. The access names a type, so the entry the store records is that type rather
1193        // than the distinguished value a store that names nothing records.
1194        let mut names = Interner::new();
1195        let (module, plane, int) = typed(&mut names, "typed.c");
1196
1197        let i32_ = Type::int(32);
1198        let mut func =
1199            Func::new(names.intern("record"), Signature::new().with_params(&[Type::PTR, i32_]));
1200        let entry = func.create_block();
1201        let p = func.append_param(entry, Type::PTR);
1202        let v = func.append_param(entry, i32_);
1203        let info = MemInfo {
1204            size: 0,
1205            align: 4,
1206            order: MemOrder::NotAtomic,
1207            tbaa: Some(int),
1208            owns: 0,
1209            restrict: Restrict::NONE,
1210        };
1211        let mut b = Builder::new(&mut func, entry);
1212        let args = b.func().push_values(&[v, p]);
1213        let extra = Extra::Mem(b.func().add_mem(info));
1214        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1215        b.ret(&[]);
1216
1217        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).judged, 1);
1218
1219        let printed = print_func(&module, &func, &names);
1220        // Four bytes, which the payload does not say and the type of the value stored does.
1221        assert!(printed.contains("%3 = iconst.i64 4\n"), "{printed}");
1222        // The entry for `int`, which is the node the plane made for the node the access named.
1223        let entry = plane.entry(Some(int));
1224        assert_eq!(module[entry], MetaNode::Plane(PlaneNode::Type(int)));
1225        let wanted = format!("meta_type %0, %3, tbaa !{}\n", entry.index());
1226        assert!(printed.contains(&wanted), "{printed}");
1227
1228        if let Err(errors) = verify_func(&module, &func, &names) {
1229            panic!("that was expected to be believed: {errors:#?}");
1230        }
1231    }
1232
1233    #[test]
1234    fn a_store_records_that_the_bytes_it_wrote_hold_something() {
1235        // The init plane's half of the same store. One bit per byte and nothing else, so the write
1236        // carries a range and no type, and the range is the width of the value stored rather than
1237        // anything the payload says. A store of eight bytes makes eight bytes readable however it
1238        // came to be written.
1239        let mut names = Interner::new();
1240        let (module, plane) = planed(&mut names, "wrote.c");
1241
1242        let i64_ = Type::int(64);
1243        let mut func =
1244            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
1245        let entry = func.create_block();
1246        let p = func.append_param(entry, Type::PTR);
1247        let v = func.append_param(entry, i64_);
1248        let info = MemInfo {
1249            size: 0,
1250            align: 8,
1251            order: MemOrder::NotAtomic,
1252            tbaa: None,
1253            owns: 0,
1254            restrict: Restrict::NONE,
1255        };
1256        let mut b = Builder::new(&mut func, entry);
1257        let args = b.func().push_values(&[v, p]);
1258        let extra = Extra::Mem(b.func().add_mem(info));
1259        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1260        b.ret(&[]);
1261
1262        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).wrote, 1);
1263
1264        let printed = print_func(&module, &func, &names);
1265        assert!(printed.contains("%4 = iconst.i64 8\n    meta_init %0, %4\n"), "{printed}");
1266
1267        if let Err(errors) = verify_func(&module, &func, &names) {
1268            panic!("that was expected to be believed: {errors:#?}");
1269        }
1270    }
1271
1272    #[test]
1273    fn a_store_that_owns_the_padding_after_it_records_that_too() {
1274        // `-fsafety-init=nopadding`, which by the time it gets here is a number on the store and
1275        // nothing else. A `char` member with three bytes of padding behind it owns four, so the
1276        // record it is in comes out whole once the other member is written and the ordinary reads
1277        // of one, which are a `memcmp` or a hash or a `write`, are not refused. Working out what
1278        // the padding is takes a record's layout, which the front end has and this pass does not,
1279        // and that is why the number arrives rather than the mode.
1280        let mut names = Interner::new();
1281        let (module, plane) = planed(&mut names, "owns.c");
1282
1283        let byte = Type::int(8);
1284        let mut func =
1285            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, byte]));
1286        let entry = func.create_block();
1287        let p = func.append_param(entry, Type::PTR);
1288        let v = func.append_param(entry, byte);
1289        let info = MemInfo {
1290            size: 0,
1291            align: 1,
1292            order: MemOrder::NotAtomic,
1293            tbaa: None,
1294            owns: 4,
1295            restrict: Restrict::NONE,
1296        };
1297        let mut b = Builder::new(&mut func, entry);
1298        let args = b.func().push_values(&[v, p]);
1299        let extra = Extra::Mem(b.func().add_mem(info));
1300        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1301        b.ret(&[]);
1302
1303        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).wrote, 1);
1304
1305        let printed = print_func(&module, &func, &names);
1306        // Four rather than the one byte the store wrote.
1307        assert!(printed.contains("%4 = iconst.i64 4\n    meta_init %0, %4\n"), "{printed}");
1308        // And the bounds check is still about the one byte the store touches, since the padding
1309        // is what a store records and not what it writes.
1310        assert!(printed.contains("check_bounds %2, %0, size 1"), "{printed}");
1311
1312        if let Err(errors) = verify_func(&module, &func, &names) {
1313            panic!("that was expected to be believed: {errors:#?}");
1314        }
1315    }
1316
1317    #[test]
1318    fn a_read_asks_whether_anything_ever_wrote_the_bytes_it_is_about_to_read() {
1319        // Document 03's Y6. The question carries the access's width and no type, because the plane
1320        // holds one bit per byte and the bit says whether anything was stored there at all.
1321        let mut names = Interner::new();
1322        let (module, plane) = planed(&mut names, "ask.c");
1323        let mut func = reading(&mut names, None);
1324
1325        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).filled, 1);
1326
1327        let printed = print_func(&module, &func, &names);
1328        assert!(printed.contains("check_init %1, %0, size 4, align 4\n"), "{printed}");
1329
1330        if let Err(errors) = verify_func(&module, &func, &names) {
1331            panic!("that was expected to be believed: {errors:#?}");
1332        }
1333    }
1334
1335    #[test]
1336    fn a_read_the_front_end_named_no_type_for_still_asks_the_init_plane() {
1337        // The one place the two questions a read asks come apart. A read with no type on it has
1338        // nothing to ask the type plane, because the question there is which type the bytes hold,
1339        // and it has the same thing to ask the init plane as any other read, because the question
1340        // there is about the bytes rather than about the access.
1341        let mut names = Interner::new();
1342        let (_module, plane) = planed(&mut names, "untyped.c");
1343        let mut func = reading(&mut names, None);
1344
1345        let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1346        assert_eq!(counts.asked, 0);
1347        assert_eq!(counts.filled, 1);
1348    }
1349
1350    #[test]
1351    fn a_store_asks_the_init_plane_nothing() {
1352        // A store writes the bytes it is about to write, so whether anything wrote them before is
1353        // not a question about it. Asking would refuse the first write to every fresh instance,
1354        // which is every program.
1355        let mut names = Interner::new();
1356        let (module, plane) = planed(&mut names, "store.c");
1357
1358        let i64_ = Type::int(64);
1359        let mut func =
1360            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
1361        let entry = func.create_block();
1362        let p = func.append_param(entry, Type::PTR);
1363        let v = func.append_param(entry, i64_);
1364        let info = MemInfo {
1365            size: 8,
1366            align: 8,
1367            order: MemOrder::NotAtomic,
1368            tbaa: None,
1369            owns: 0,
1370            restrict: Restrict::NONE,
1371        };
1372        let mut b = Builder::new(&mut func, entry);
1373        let args = b.func().push_values(&[v, p]);
1374        let extra = Extra::Mem(b.func().add_mem(info));
1375        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1376        b.ret(&[]);
1377
1378        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).filled, 0);
1379
1380        let printed = print_func(&module, &func, &names);
1381        assert!(!printed.contains("check_init"), "{printed}");
1382    }
1383
1384    #[test]
1385    fn a_read_tells_the_init_plane_nothing() {
1386        // A read is a question and not a judgement. Whether the bytes it read hold anything is
1387        // what the plane already says, and a read that wrote the plane would make every read of
1388        // storage nothing ever wrote look like a read of storage something did.
1389        let mut names = Interner::new();
1390        let (module, plane) = planed(&mut names, "read.c");
1391        let mut func = reading(&mut names, None);
1392
1393        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).wrote, 0);
1394
1395        let printed = print_func(&module, &func, &names);
1396        assert!(!printed.contains("meta_init"), "{printed}");
1397    }
1398
1399    /// A function that copies a fixed number of bytes from one of its parameters to the other.
1400    fn one_copy(names: &mut Interner, opcode: Opcode) -> Func {
1401        let mut func =
1402            Func::new(names.intern("move"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
1403        let entry = func.create_block();
1404        let to = func.append_param(entry, Type::PTR);
1405        let from = func.append_param(entry, Type::PTR);
1406
1407        let info = MemInfo {
1408            size: 24,
1409            align: 8,
1410            order: MemOrder::NotAtomic,
1411            tbaa: None,
1412            owns: 0,
1413            restrict: Restrict::NONE,
1414        };
1415        let mut b = Builder::new(&mut func, entry);
1416        let args = b.func().push_values(&[to, from]);
1417        let extra = Extra::Mem(b.func().add_mem(info));
1418        b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
1419        b.ret(&[]);
1420        func
1421    }
1422
1423    #[test]
1424    fn a_copy_carries_whatever_the_bytes_it_read_said() {
1425        // The other half of the judgement C 6.5 describes. A copy does not store through a type, so
1426        // there is nothing here for the compiler to name: what the copied bytes are is whatever the
1427        // bytes they came from were, and the plane over the source is the only place that is
1428        // written down. Without this the destination would go on saying whatever was there before.
1429        let mut names = Interner::new();
1430        let mut func = one_copy(&mut names, Opcode::Memcpy);
1431        let (module, plane) = planed(&mut names, "move.c");
1432        assert_eq!(
1433            insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
1434            Counts { carried: 1, moved: 1, ..Counts::default() }
1435        );
1436
1437        assert_eq!(
1438            print_func(&module, &func, &names),
1439            // After the copy, for the same reason a store's judgement is after the store.
1440            "func @move(ptr, ptr), linkage(external) {\n\
1441             block0(%0: ptr, %1: ptr):\n    \
1442             memcpy %0, %1, size 24, align 8\n    \
1443             %2 = iconst.i64 24\n    \
1444             meta_type_copy %0, %1, %2\n    \
1445             %3 = iconst.i64 24\n    \
1446             meta_init_copy %0, %1, %3\n    \
1447             return\n\
1448             }\n"
1449        );
1450
1451        if let Err(errors) = verify_func(&module, &func, &names) {
1452            panic!("that was expected to be believed: {errors:#?}");
1453        }
1454    }
1455
1456    #[test]
1457    fn a_copy_whose_ranges_may_overlap_is_carried_the_same_way() {
1458        // `memmove` is `memcpy` with the overlap allowed, and the overlap is the runtime's problem
1459        // rather than this pass's: a copy writes no plane entries of its own, so the entries over
1460        // the source are the same ones whichever end the bytes were moved from.
1461        let mut names = Interner::new();
1462        let mut func = one_copy(&mut names, Opcode::Memmove);
1463        let (module, plane) = planed(&mut names, "overlap.c");
1464        assert_eq!(
1465            insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
1466            Counts { carried: 1, moved: 1, ..Counts::default() }
1467        );
1468
1469        let printed = print_func(&module, &func, &names);
1470        assert!(printed.contains("meta_type_copy %0, %1, %2\n"), "{printed}");
1471        assert!(printed.contains("meta_init_copy %0, %1, %3\n"), "{printed}");
1472    }
1473
1474    #[test]
1475    fn a_walk_over_elements_hands_the_check_the_width_of_one() {
1476        // The low end of judgement J2's window is one element below the object, so the check has
1477        // to be told how wide an element is. C computed a byte offset before the arithmetic
1478        // happened, so the width is not in the `ptr_add`, and what is in it is the multiply the
1479        // frontend left behind. This pass runs before the optimizer, so that shape is still there.
1480        let mut names = Interner::new();
1481        let mut func = Func::new(
1482            names.intern("walk"),
1483            Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
1484        );
1485        let entry = func.create_block();
1486        let p = func.append_param(entry, Type::PTR);
1487        let n = func.append_param(entry, Type::int(64));
1488
1489        let mut b = Builder::new(&mut func, entry);
1490        let width = b.iconst(Type::int(64), 24);
1491        let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
1492        let args = b.func().push_values(&[p, bytes]);
1493        let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1494        b.ret(&[moved]);
1495
1496        let (module, plane) = planed(&mut names, "walk.c");
1497        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1498
1499        assert_eq!(
1500            print_func(&module, &func, &names),
1501            "func @walk(ptr, i64) -> ptr, linkage(external) {\n\
1502             block0(%0: ptr, %1: i64):\n    \
1503             %2 = iconst.i64 24\n    \
1504             %3 = mul.nsw %1, %2\n    \
1505             %4 = cap_of %0\n    \
1506             %5 = ptr_add %0, %3\n    \
1507             check_deriv %4, %0, %5, %2\n    \
1508             return %5\n\
1509             }\n"
1510        );
1511    }
1512
1513    #[test]
1514    fn a_walk_that_goes_backwards_is_still_a_walk_over_elements() {
1515        // Which is the case the whole widening is for. A walk backwards negates the byte offset
1516        // rather than the width, so the multiply is one instruction further down and the width is
1517        // the same one. Missing it here would mean `&a[-1]` getting a one byte window and being
1518        // refused, which is the report this change exists to stop.
1519        let mut names = Interner::new();
1520        let mut func = Func::new(
1521            names.intern("back"),
1522            Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
1523        );
1524        let entry = func.create_block();
1525        let p = func.append_param(entry, Type::PTR);
1526        let n = func.append_param(entry, Type::int(64));
1527
1528        let mut b = Builder::new(&mut func, entry);
1529        let width = b.iconst(Type::int(64), 24);
1530        let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
1531        let zero = b.iconst(Type::int(64), 0);
1532        let back = b.binary(Opcode::Sub, zero, bytes, Flags::NONE);
1533        let args = b.func().push_values(&[p, back]);
1534        let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1535        b.ret(&[moved]);
1536
1537        let (module, plane) = planed(&mut names, "back.c");
1538        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1539
1540        let printed = print_func(&module, &func, &names);
1541        assert!(printed.contains("check_deriv %6, %0, %7, %2\n"), "{printed}");
1542    }
1543
1544    #[test]
1545    fn a_pointer_computed_from_another_pointer_is_checked_where_it_is_computed() {
1546        // Judgement J2. The pointer that walked off its object is caught at the arithmetic, not
1547        // at whatever line eventually reads through it, which is what lets the report name the
1548        // loop that ran too far. Note where the check sits: after the ptr_add, because it is
1549        // handed the pointer the ptr_add produced.
1550        let mut names = Interner::new();
1551        let mut func = Func::new(
1552            names.intern("walk"),
1553            Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
1554        );
1555        let entry = func.create_block();
1556        let p = func.append_param(entry, Type::PTR);
1557        let n = func.append_param(entry, Type::int(64));
1558
1559        let mut b = Builder::new(&mut func, entry);
1560        let args = b.func().push_values(&[p, n]);
1561        let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1562        b.ret(&[moved]);
1563
1564        let (module, plane) = planed(&mut names, "walk.c");
1565        assert_eq!(
1566            insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
1567            Counts { derived: 1, ..Counts::default() }
1568        );
1569
1570        assert_eq!(
1571            print_func(&module, &func, &names),
1572            // The stride is one, because the offset here is a block parameter and nothing about
1573            // it says what it is a count of. That is the answer a shape this pass does not
1574            // recognise gets, and it is the strict reading of C.
1575            "func @walk(ptr, i64) -> ptr, linkage(external) {\n\
1576             block0(%0: ptr, %1: i64):\n    \
1577             %2 = iconst.i64 1\n    \
1578             %3 = cap_of %0\n    \
1579             %4 = ptr_add %0, %1\n    \
1580             check_deriv %3, %0, %4, %2\n    \
1581             return %4\n\
1582             }\n"
1583        );
1584
1585        if let Err(errors) = verify_func(&module, &func, &names) {
1586            panic!("that was expected to be believed: {errors:#?}");
1587        }
1588    }
1589
1590    #[test]
1591    fn what_it_produces_is_a_function_the_verifier_believes() {
1592        // The point of inserting checks as IR is that everything downstream may treat them as
1593        // IR, which is only true if the result is a module the verifier accepts.
1594        let mut names = Interner::new();
1595        let mut func = one_of_each(&mut names);
1596        let (module, plane) = planed(&mut names, "both.c");
1597        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1598
1599        if let Err(errors) = verify_func(&module, &func, &names) {
1600            panic!("that was expected to be believed: {errors:#?}");
1601        }
1602    }
1603
1604    #[test]
1605    fn every_definition_in_a_module_is_walked_and_the_declarations_are_not() {
1606        let mut names = Interner::new();
1607        let one = one_of_each(&mut names);
1608        let mut two = one_of_each(&mut names);
1609        two.name = names.intern("other");
1610        // A declaration of a function defined somewhere else. There is no body to put a check in
1611        // and reaching for one would be a crash rather than a wrong answer.
1612        let declared = Func::new(
1613            names.intern("elsewhere"),
1614            Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::int(32)]),
1615        );
1616
1617        let mut module = Module::new(names.intern("two.c"), &target());
1618        module.add_func(one);
1619        module.add_func(two);
1620        module.add_func(declared);
1621
1622        assert_eq!(
1623            run(&mut module, Subobject::Off, Promise::Off),
1624            Counts { checked: 4, live: 4, judged: 2, wrote: 2, filled: 2, ..Counts::default() }
1625        );
1626        if let Err(errors) = rucc_ir::verify(&module, &names) {
1627            panic!("that was expected to be believed: {errors:#?}");
1628        }
1629    }
1630
1631    #[test]
1632    fn a_function_with_no_accesses_is_left_alone() {
1633        let mut names = Interner::new();
1634        let i32_ = Type::int(32);
1635        let mut func = Func::new(names.intern("nothing"), Signature::new().with_returns(&[i32_]));
1636        let entry = func.create_block();
1637        let mut b = Builder::new(&mut func, entry);
1638        let zero = b.iconst(i32_, 0);
1639        b.ret(&[zero]);
1640
1641        let (_module, plane) = planed(&mut names, "nothing.c");
1642        let before = func.counts();
1643        assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off), Counts::default());
1644        assert_eq!(func.counts(), before);
1645    }
1646}