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}