1use rustc_hir::def_id::DefId;
9use rustc_middle::mir::Body;
10use rustc_middle::mir::{BasicBlock, StatementKind, TerminatorKind};
11use rustc_middle::ty::TyCtxt;
12
13use crate::analysis::dataflow::graph::build_dataflow_graph;
14use crate::analysis::dataflow::types::DataflowGraph;
15
16use super::super::{
17 contract,
18 def_use::{RelevantPlaces, bind_callsite_roots, operand_uses, terminator_use_def},
19 helpers::{Checkpoint, CheckpointLocation},
20 path_extractor::{Path, PathStep},
21};
22
23use crate::analysis::path_analysis::{PathNode, PathTree};
24
25use super::{
26 call_visit,
27 types::{BackwardItem, ForgetReason, KeepReason, RelevantMirItems},
28};
29
30pub struct BackwardSlicer<'tcx> {
32 tcx: TyCtxt<'tcx>,
33}
34
35impl<'tcx> BackwardSlicer<'tcx> {
36 pub fn new(tcx: TyCtxt<'tcx>) -> Self {
38 Self { tcx }
39 }
40
41 pub fn tcx(&self) -> TyCtxt<'tcx> {
43 self.tcx
44 }
45
46 pub fn visit_path_tree(
52 &self,
53 tree: &PathTree,
54 target_block: usize,
55 checkpoint: &Checkpoint<'tcx>,
56 property: &contract::Property<'tcx>,
57 ) -> Vec<RelevantMirItems<'tcx>> {
58 self.visit_path_tree_impl(
59 tree,
60 target_block,
61 checkpoint.caller,
62 checkpoint.block,
63 Some(checkpoint),
64 property,
65 )
66 }
67
68 pub fn visit_path_tree_for_checkpoint(
72 &self,
73 tree: &PathTree,
74 target_block: usize,
75 caller: DefId,
76 checkpoint_loc: CheckpointLocation,
77 property: &contract::Property<'tcx>,
78 ) -> Vec<RelevantMirItems<'tcx>> {
79 self.visit_path_tree_impl(
80 tree,
81 target_block,
82 caller,
83 checkpoint_loc.block,
84 None,
85 property,
86 )
87 }
88
89 fn visit_path_tree_impl(
92 &self,
93 tree: &PathTree,
94 target_block: usize,
95 caller: DefId,
96 checkpoint_block: BasicBlock,
97 bind_checkpoint: Option<&Checkpoint<'tcx>>,
98 property: &contract::Property<'tcx>,
99 ) -> Vec<RelevantMirItems<'tcx>> {
100 let Some(root) = tree.root() else {
101 return Vec::new();
102 };
103 let checkpoint_loc = CheckpointLocation {
104 caller,
105 block: checkpoint_block,
106 };
107 let body = self.tcx.optimized_mir(caller);
108 let flow = build_dataflow_graph(self.tcx, caller);
109 let keep_alloc = matches!(
110 property.kind,
111 contract::PropertyKind::Allocated
112 | contract::PropertyKind::Deref
113 | contract::PropertyKind::ValidPtr
114 );
115
116 let leaf_results = Self::build_leaf_items(
117 self,
118 root,
119 target_block,
120 checkpoint_block,
121 bind_checkpoint,
122 property,
123 &body,
124 &flow,
125 keep_alloc,
126 );
127
128 let mut results = Vec::new();
129 for (block_path, backward_items, _relevant) in leaf_results {
130 let mut items = backward_items;
131 items.reverse();
132 let steps: Vec<PathStep> = block_path
133 .iter()
134 .map(|&b| PathStep::Block(BasicBlock::from(b)))
135 .chain(std::iter::once(PathStep::Checkpoint(checkpoint_loc)))
136 .collect();
137 results.push(RelevantMirItems {
138 checkpoint: checkpoint_loc,
139 property: property.clone(),
140 path: Path {
141 target: checkpoint_loc,
142 steps,
143 },
144 items,
145 roots: RelevantPlaces::from_property(property),
146 });
147 }
148 results
149 }
150
151 fn build_leaf_items(
155 visitor: &Self,
156 node: &PathNode,
157 target_block: usize,
158 checkpoint_block: BasicBlock,
159 bind_checkpoint: Option<&Checkpoint<'tcx>>,
160 property: &contract::Property<'tcx>,
161 body: &'tcx rustc_middle::mir::Body<'tcx>,
162 flow: &DataflowGraph,
163 keep_alloc: bool,
164 ) -> Vec<(Vec<usize>, Vec<BackwardItem<'tcx>>, RelevantPlaces)> {
165 let block = BasicBlock::from(node.block);
166 let block_data = &body.basic_blocks[block];
167 let mut results = Vec::new();
168
169 let (checkpoint_items, checkpoint_relevant) = if node.block == target_block {
171 let mut relevant = RelevantPlaces::from_property(property);
172 if let Some(cs) = bind_checkpoint {
173 bind_callsite_roots(visitor.tcx, &mut relevant, cs);
174 }
175 let mut items = Vec::new();
176 items.push(BackwardItem::Terminator {
177 block: checkpoint_block,
178 kind: KeepReason::Checkpoint,
179 });
180 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
181 visitor.visit_statement(
182 checkpoint_block,
183 si,
184 stmt,
185 flow,
186 &mut relevant,
187 &mut items,
188 keep_alloc,
189 );
190 }
191 (items, relevant)
192 } else {
193 (Vec::new(), RelevantPlaces::new())
194 };
195
196 for child in &node.children {
199 let child_results = Self::build_leaf_items(
200 visitor,
201 child,
202 target_block,
203 checkpoint_block,
204 bind_checkpoint,
205 property,
206 body,
207 flow,
208 keep_alloc,
209 );
210 for (mut child_path, child_items, child_relevant) in child_results {
211 let mut relevant = child_relevant;
212 let mut items = child_items;
213 visitor.visit_terminator(
217 block,
218 block_data.terminator(),
219 flow,
220 body,
221 &mut relevant,
222 &mut items,
223 keep_alloc,
224 );
225 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
226 visitor.visit_statement(
227 block,
228 si,
229 stmt,
230 flow,
231 &mut relevant,
232 &mut items,
233 keep_alloc,
234 );
235 }
236 child_path.insert(0, node.block);
237 results.push((child_path, items, relevant));
238 }
239 }
240
241 if !checkpoint_items.is_empty() {
243 results.push((vec![node.block], checkpoint_items, checkpoint_relevant));
244 }
245
246 results
247 }
248
249 fn visit_statement(
251 &self,
252 block: BasicBlock,
253 statement_index: usize,
254 statement: &'tcx rustc_middle::mir::Statement<'tcx>,
255 flow: &DataflowGraph,
256 relevant: &mut RelevantPlaces,
257 items: &mut Vec<BackwardItem<'tcx>>,
258 keep_allocation_invalidations: bool,
259 ) {
260 if keep_allocation_invalidations && matches!(statement.kind, StatementKind::StorageDead(_))
261 {
262 items.push(BackwardItem::Statement {
263 block,
264 statement_index,
265 kind: KeepReason::Invalidation,
266 });
267 return;
268 }
269
270 let mut defs = RelevantPlaces::new();
271 match &statement.kind {
272 StatementKind::Assign(assign) => {
273 let (place, _) = &**assign;
274 defs.insert_mir_place(place);
275 }
276 StatementKind::StorageDead(local) => {
277 defs.insert_local(*local);
278 }
279 _ => {}
280 }
281
282 if defs.intersects(relevant) {
283 let uses = collect_statement_uses(statement, block, statement_index, flow);
284 items.push(BackwardItem::Statement {
285 block,
286 statement_index,
287 kind: statement_keep_reason(statement),
288 });
289 relevant.remove_all(&defs);
290 relevant.extend(uses);
291 return;
292 }
293
294 if statement_invalidates_relevant(statement, relevant) {
295 items.push(BackwardItem::Statement {
296 block,
297 statement_index,
298 kind: KeepReason::Invalidation,
299 });
300 } else if statement_can_refine(statement) {
301 let mut uses = RelevantPlaces::new();
302 for &local in &defs.locals {
303 for &edge_idx in &flow.node(local).in_edges {
304 let edge = &flow.edges[edge_idx];
305 if edge.block == block.as_usize() && edge.statement_index == statement_index {
306 uses.insert_local(edge.src);
307 }
308 }
309 }
310 if uses.intersects(relevant) {
311 items.push(BackwardItem::Statement {
312 block,
313 statement_index,
314 kind: KeepReason::RuntimeCheck,
315 });
316 }
317 }
318 }
319
320 fn visit_terminator(
322 &self,
323 block: BasicBlock,
324 terminator: &rustc_middle::mir::Terminator<'tcx>,
325 flow: &DataflowGraph,
326 body: &Body<'tcx>,
327 relevant: &mut RelevantPlaces,
328 items: &mut Vec<BackwardItem<'tcx>>,
329 keep_allocation_invalidations: bool,
330 ) {
331 if keep_allocation_invalidations && matches!(terminator.kind, TerminatorKind::Drop { .. }) {
332 items.push(BackwardItem::Terminator {
333 block,
334 kind: KeepReason::Invalidation,
335 });
336 return;
337 }
338
339 if let TerminatorKind::Call {
340 func,
341 args,
342 destination,
343 ..
344 } = &terminator.kind
345 {
346 call_visit::visit(
347 self.tcx,
348 block,
349 func,
350 args,
351 destination,
352 flow,
353 body,
354 relevant,
355 items,
356 );
357 return;
358 }
359
360 let use_def = terminator_use_def(terminator);
361 if terminator_is_path_condition(terminator) {
362 items.push(BackwardItem::Terminator {
363 block,
364 kind: KeepReason::PathCondition,
365 });
366 relevant.extend(use_def.uses.clone());
367 return;
368 }
369
370 if use_def.defs.intersects(relevant) {
371 if terminator_may_havoc(terminator) {
372 items.push(BackwardItem::Forget {
373 reason: ForgetReason::UnknownCall,
374 });
375 }
376 items.push(BackwardItem::Terminator {
377 block,
378 kind: terminator_definition_reason(terminator),
379 });
380 relevant.remove_all(&use_def.defs);
381 relevant.extend(use_def.uses);
382 return;
383 }
384
385 if use_def.uses.intersects(relevant) {
386 if terminator_may_havoc(terminator) {
387 items.push(BackwardItem::Forget {
388 reason: ForgetReason::UnknownCall,
389 });
390 }
391 items.push(BackwardItem::Terminator {
392 block,
393 kind: terminator_use_reason(terminator),
394 });
395 }
396 }
397}
398
399fn statement_keep_reason(statement: &rustc_middle::mir::Statement<'_>) -> KeepReason {
402 match &statement.kind {
403 StatementKind::Assign(assign) => {
404 let (_, rvalue) = &**assign;
405 match rvalue {
406 rustc_middle::mir::Rvalue::Ref(_, _, _)
407 | rustc_middle::mir::Rvalue::RawPtr(_, _)
408 | rustc_middle::mir::Rvalue::Cast(_, _, _)
409 | rustc_middle::mir::Rvalue::CopyForDeref(_)
410 | rustc_middle::mir::Rvalue::BinaryOp(_, _) => KeepReason::PointerFlow,
411 _ => KeepReason::Definition,
412 }
413 }
414 StatementKind::StorageDead(_) => KeepReason::Invalidation,
415 _ => KeepReason::Definition,
416 }
417}
418
419fn statement_can_refine(statement: &rustc_middle::mir::Statement<'_>) -> bool {
420 matches!(&statement.kind, StatementKind::Assign(assign) if matches!(
421 &**assign,
422 (
423 _,
424 rustc_middle::mir::Rvalue::BinaryOp(_, _)
425 | rustc_middle::mir::Rvalue::UnaryOp(_, _)
426 | rustc_middle::mir::Rvalue::Cast(_, _, _),
427 )
428 ))
429}
430
431fn statement_invalidates_relevant(
432 statement: &rustc_middle::mir::Statement<'_>,
433 relevant: &RelevantPlaces,
434) -> bool {
435 match &statement.kind {
436 StatementKind::StorageDead(local) => relevant.locals.contains(local),
437 _ => false,
438 }
439}
440
441fn terminator_is_path_condition(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
442 matches!(
443 terminator.kind,
444 TerminatorKind::SwitchInt { .. } | TerminatorKind::Assert { .. }
445 )
446}
447
448fn terminator_definition_reason(terminator: &rustc_middle::mir::Terminator<'_>) -> KeepReason {
449 match terminator.kind {
450 TerminatorKind::Call { .. } => KeepReason::UnknownEffect,
451 _ => KeepReason::Definition,
452 }
453}
454
455fn terminator_use_reason(terminator: &rustc_middle::mir::Terminator<'_>) -> KeepReason {
456 match terminator.kind {
457 TerminatorKind::SwitchInt { .. } | TerminatorKind::Assert { .. } => {
458 KeepReason::PathCondition
459 }
460 TerminatorKind::Drop { .. } => KeepReason::Invalidation,
461 TerminatorKind::Call { .. } => KeepReason::UnknownEffect,
462 _ => KeepReason::UnknownEffect,
463 }
464}
465
466fn terminator_may_havoc(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
467 matches!(terminator.kind, TerminatorKind::Call { .. })
468}
469
470fn collect_statement_uses<'tcx>(
472 statement: &'tcx rustc_middle::mir::Statement<'tcx>,
473 block: BasicBlock,
474 statement_index: usize,
475 flow: &DataflowGraph,
476) -> RelevantPlaces {
477 let mut uses = RelevantPlaces::new();
478
479 let def_locals = match &statement.kind {
481 StatementKind::Assign(assign) => {
482 let (place, _) = &**assign;
483 vec![place.local]
484 }
485 StatementKind::StorageDead(local) => vec![*local],
486 _ => Vec::new(),
487 };
488
489 for &local in &def_locals {
490 for &edge_idx in &flow.node(local).in_edges {
491 let edge = &flow.edges[edge_idx];
492 if edge.block == block.as_usize() && edge.statement_index == statement_index {
493 uses.insert_local(edge.src);
494 }
495 }
496 }
497
498 if let StatementKind::Assign(assign) = &statement.kind {
502 let (_, rvalue) = &**assign;
503 for operand in super::super::def_use::rvalue_operands(rvalue) {
504 uses.extend(operand_uses(operand));
505 }
506 }
507
508 uses
509}