Skip to content

Commit 0ed9a62

Browse files
Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration (#3438)
This PR is a follow-up to #3374. Its main purpose is to properly handle a corner-case when multiple instrumentation instructions are added to the same instruction and not all of them are skipped during subsequent instrumentation. For example, if instrumentation is added after a terminator, a new basic block will be created, containing the added instrumentation. However, we currently only mark the first statement (or the terminator, if there are none) of the new basic block as skipped for subsequent instrumentation. That means that if instrumentation in this case contains some instrumentation targets itself, it will never terminate. Coincidentally, this is not currently the case, but could lead to subtle bugs if we decide to change instrumentation. In fact, this bug was only surfaced when I experimented with checking all memory accesses, which introduced significantly more checks. Ultimately, this shows that the current way to avoiding instrumentation is limited and needs to be changed. The PR introduces the following changes: - Group instrumentation into separate basic blocks instead of adding instructions one-by-one. - Use backward iteration to avoid having to reason about which instructions need to be skipped. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent d2141e4 commit 0ed9a62

File tree

10 files changed

+697
-554
lines changed

10 files changed

+697
-554
lines changed

kani-compiler/src/kani_middle/transform/body.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -310,16 +310,16 @@ impl MutableBody {
310310
// Update the source to point at the terminator.
311311
*source = SourceInstruction::Terminator { bb: orig_bb };
312312
}
313-
// Make the terminator at `source` point at the new block,
314-
// the terminator of which is a simple Goto instruction.
313+
// Make the terminator at `source` point at the new block, the terminator of which is
314+
// provided by the caller.
315315
SourceInstruction::Terminator { bb } => {
316316
let current_term = &mut self.blocks.get_mut(*bb).unwrap().terminator;
317317
let target_bb = get_mut_target_ref(current_term);
318318
let new_target_bb = get_mut_target_ref(&mut new_term);
319-
// Set the new terminator to point where previous terminator pointed.
320-
*new_target_bb = *target_bb;
321-
// Point the current terminator to the new terminator's basic block.
322-
*target_bb = new_bb_idx;
319+
// Swap the targets of the newly inserted terminator and the original one. This is
320+
// an easy way to make the original terminator point to the new basic block with the
321+
// new terminator.
322+
std::mem::swap(new_target_bb, target_bb);
323323
// Update the source to point at the terminator.
324324
*bb = new_bb_idx;
325325
self.blocks.push(BasicBlock { statements: vec![], terminator: new_term });
@@ -484,7 +484,7 @@ impl CheckType {
484484
}
485485

486486
/// We store the index of an instruction to avoid borrow checker issues and unnecessary copies.
487-
#[derive(Copy, Clone, Debug)]
487+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
488488
pub enum SourceInstruction {
489489
Statement { idx: usize, bb: BasicBlockIdx },
490490
Terminator { bb: BasicBlockIdx },

kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs

+47-41
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,15 @@ use rustc_middle::ty::TyCtxt;
1818
use stable_mir::mir::{
1919
mono::Instance,
2020
visit::{Location, PlaceContext},
21-
BasicBlockIdx, MirVisitor, Operand, Place, Rvalue, Statement, Terminator,
21+
MirVisitor, Operand, Place, Rvalue, Statement, Terminator,
2222
};
2323
use std::collections::HashSet;
2424

2525
pub struct InstrumentationVisitor<'a, 'tcx> {
26-
/// Whether we should skip the next instruction, since it might've been instrumented already.
27-
/// When we instrument an instruction, we partition the basic block, and the instruction that
28-
/// may trigger UB becomes the first instruction of the basic block, which we need to skip
29-
/// later.
30-
skip_next: bool,
31-
/// The instruction being visited at a given point.
32-
current: SourceInstruction,
33-
/// The target instruction that should be verified.
34-
pub target: Option<InitRelevantInstruction>,
26+
/// All target instructions in the body.
27+
targets: Vec<InitRelevantInstruction>,
28+
/// Current analysis target, eventually needs to be added to a list of all targets.
29+
current_target: InitRelevantInstruction,
3530
/// Aliasing analysis data.
3631
points_to: &'a PointsToGraph<'tcx>,
3732
/// The list of places we should be looking for, ignoring others
@@ -41,17 +36,16 @@ pub struct InstrumentationVisitor<'a, 'tcx> {
4136
}
4237

4338
impl<'a, 'tcx> TargetFinder for InstrumentationVisitor<'a, 'tcx> {
44-
fn find_next(
45-
&mut self,
46-
body: &MutableBody,
47-
bb: BasicBlockIdx,
48-
skip_first: bool,
49-
) -> Option<InitRelevantInstruction> {
50-
self.skip_next = skip_first;
51-
self.current = SourceInstruction::Statement { idx: 0, bb };
52-
self.target = None;
53-
self.visit_basic_block(&body.blocks()[bb]);
54-
self.target.clone()
39+
fn find_all(mut self, body: &MutableBody) -> Vec<InitRelevantInstruction> {
40+
for (bb_idx, bb) in body.blocks().iter().enumerate() {
41+
self.current_target = InitRelevantInstruction {
42+
source: SourceInstruction::Statement { idx: 0, bb: bb_idx },
43+
before_instruction: vec![],
44+
after_instruction: vec![],
45+
};
46+
self.visit_basic_block(bb);
47+
}
48+
self.targets
5549
}
5650
}
5751

@@ -63,43 +57,55 @@ impl<'a, 'tcx> InstrumentationVisitor<'a, 'tcx> {
6357
tcx: TyCtxt<'tcx>,
6458
) -> Self {
6559
Self {
66-
skip_next: false,
67-
current: SourceInstruction::Statement { idx: 0, bb: 0 },
68-
target: None,
60+
targets: vec![],
61+
current_target: InitRelevantInstruction {
62+
source: SourceInstruction::Statement { idx: 0, bb: 0 },
63+
before_instruction: vec![],
64+
after_instruction: vec![],
65+
},
6966
points_to,
7067
analysis_targets,
7168
current_instance,
7269
tcx,
7370
}
7471
}
72+
7573
fn push_target(&mut self, source_op: MemoryInitOp) {
76-
let target = self.target.get_or_insert_with(|| InitRelevantInstruction {
77-
source: self.current,
78-
after_instruction: vec![],
79-
before_instruction: vec![],
80-
});
81-
target.push_operation(source_op);
74+
self.current_target.push_operation(source_op);
8275
}
8376
}
8477

8578
impl<'a, 'tcx> MirVisitor for InstrumentationVisitor<'a, 'tcx> {
8679
fn visit_statement(&mut self, stmt: &Statement, location: Location) {
87-
if self.skip_next {
88-
self.skip_next = false;
89-
} else if self.target.is_none() {
90-
// Check all inner places.
91-
self.super_statement(stmt, location);
92-
}
80+
self.super_statement(stmt, location);
9381
// Switch to the next statement.
94-
let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() };
95-
self.current = SourceInstruction::Statement { idx: idx + 1, bb };
82+
if let SourceInstruction::Statement { idx, bb } = self.current_target.source {
83+
self.targets.push(self.current_target.clone());
84+
self.current_target = InitRelevantInstruction {
85+
source: SourceInstruction::Statement { idx: idx + 1, bb },
86+
after_instruction: vec![],
87+
before_instruction: vec![],
88+
};
89+
} else {
90+
unreachable!()
91+
}
9692
}
9793

9894
fn visit_terminator(&mut self, term: &Terminator, location: Location) {
99-
if !(self.skip_next || self.target.is_some()) {
100-
self.current = SourceInstruction::Terminator { bb: self.current.bb() };
101-
self.super_terminator(term, location);
95+
if let SourceInstruction::Statement { bb, .. } = self.current_target.source {
96+
// We don't have to push the previous target, since it already happened in the statement
97+
// handling code.
98+
self.current_target = InitRelevantInstruction {
99+
source: SourceInstruction::Terminator { bb },
100+
after_instruction: vec![],
101+
before_instruction: vec![],
102+
};
103+
} else {
104+
unreachable!()
102105
}
106+
self.super_terminator(term, location);
107+
// Push the current target from the terminator onto the list.
108+
self.targets.push(self.current_target.clone());
103109
}
104110

105111
fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) {

kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ use crate::kani_middle::{
1111
points_to::{run_points_to_analysis, MemLoc, PointsToGraph},
1212
reachability::CallGraph,
1313
transform::{
14-
body::{CheckType, MutableBody},
15-
check_uninit::UninitInstrumenter,
16-
BodyTransformation, GlobalPass, TransformationResult,
14+
body::CheckType, check_uninit::UninitInstrumenter, BodyTransformation, GlobalPass,
15+
TransformationResult,
1716
},
1817
};
1918
use crate::kani_queries::QueryDb;
@@ -112,25 +111,27 @@ impl GlobalPass for DelayedUbPass {
112111

113112
// Instrument each instance based on the final targets we found.
114113
for instance in instances {
115-
let mut instrumenter = UninitInstrumenter {
116-
check_type: self.check_type.clone(),
117-
mem_init_fn_cache: &mut self.mem_init_fn_cache,
118-
};
119114
// Retrieve the body with all local instrumentation passes applied.
120-
let body = MutableBody::from(transformer.body(tcx, instance));
115+
let body = transformer.body(tcx, instance);
121116
// Instrument for delayed UB.
122117
let target_finder = InstrumentationVisitor::new(
123118
&global_points_to_graph,
124119
&analysis_targets,
125120
instance,
126121
tcx,
127122
);
128-
let (instrumentation_added, body) =
129-
instrumenter.instrument(tcx, body, instance, target_finder);
123+
let (instrumentation_added, body) = UninitInstrumenter::run(
124+
body,
125+
tcx,
126+
instance,
127+
self.check_type.clone(),
128+
&mut self.mem_init_fn_cache,
129+
target_finder,
130+
);
130131
// If some instrumentation has been performed, update the cached body in the local transformer.
131132
if instrumentation_added {
132133
transformer.cache.entry(instance).and_modify(|transformation_result| {
133-
*transformation_result = TransformationResult::Modified(body.into());
134+
*transformation_result = TransformationResult::Modified(body);
134135
});
135136
}
136137
}

0 commit comments

Comments
 (0)