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 path_extractor::{Path, PathStep},
20};
21use crate::helpers::mir_scan::{Checkpoint, CheckpointLocation};
22
23use crate::analysis::path::{PathNode, PathTree};
24
25use super::{
26 call_visit,
27 types::{RelevantItem, ProofGoal},
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<ProofGoal<'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<ProofGoal<'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<ProofGoal<'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
110 let leaf_results = Self::build_leaf_items(
111 self,
112 root,
113 target_block,
114 checkpoint_block,
115 bind_checkpoint,
116 property,
117 &body,
118 &flow,
119 );
120
121 let mut results = Vec::new();
122 for (block_path, backward_items, _relevant) in leaf_results {
123 let mut items = backward_items;
124 items.reverse();
125 let steps: Vec<PathStep> = block_path
126 .iter()
127 .map(|&b| PathStep::Block(BasicBlock::from(b)))
128 .chain(std::iter::once(PathStep::Checkpoint(checkpoint_loc)))
129 .collect();
130 results.push(ProofGoal {
131 path: Path {
132 target: checkpoint_loc,
133 steps,
134 },
135 items,
136 });
137 }
138 results
139 }
140
141 fn build_leaf_items(
145 visitor: &Self,
146 node: &PathNode,
147 target_block: usize,
148 checkpoint_block: BasicBlock,
149 bind_checkpoint: Option<&Checkpoint<'tcx>>,
150 property: &contract::Property<'tcx>,
151 body: &'tcx rustc_middle::mir::Body<'tcx>,
152 flow: &DataflowGraph,
153 ) -> Vec<(Vec<usize>, Vec<RelevantItem<'tcx>>, RelevantPlaces)> {
154 let block = BasicBlock::from(node.block);
155 let keep_inv = property.kind().is_some_and(|k| needs_invalidation_tracking(&k));
156 let block_data = &body.basic_blocks[block];
157 let mut results = Vec::new();
158
159 let (checkpoint_items, checkpoint_relevant) = if node.block == target_block {
161 let mut relevant = RelevantPlaces::from_property(property);
162 if let Some(cs) = bind_checkpoint {
163 bind_callsite_roots(visitor.tcx, &mut relevant, cs);
164 }
165 let mut items = Vec::new();
166 items.push(RelevantItem::Terminator {
167 block: checkpoint_block,
168 });
169 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
171 visitor.visit_statement(
172 checkpoint_block,
173 si,
174 stmt,
175 flow,
176 &mut relevant,
177 &mut items,
178 keep_inv,
179 );
180 }
181 Self::re_visit_newly_added(visitor, checkpoint_block, block_data, flow, &mut relevant, &mut items, keep_inv);
184 (items, relevant)
185 } else {
186 (Vec::new(), RelevantPlaces::new())
187 };
188
189 for child in &node.children {
192 let child_results = Self::build_leaf_items(
193 visitor,
194 child,
195 target_block,
196 checkpoint_block,
197 bind_checkpoint,
198 property,
199 body,
200 flow,
201 );
202 for (mut child_path, child_items, child_relevant) in child_results {
203 let mut relevant = child_relevant;
204 let mut items = child_items;
205 visitor.visit_terminator(
208 block,
209 block_data.terminator(),
210 flow,
211 body,
212 &mut relevant,
213 &mut items,
214 keep_inv,
215 );
216 let block_stmt_count = block_data.statements.len();
217 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
218 visitor.visit_statement(
219 block,
220 si,
221 stmt,
222 flow,
223 &mut relevant,
224 &mut items,
225 keep_inv,
226 );
227 }
228 let dist_to_target = child_path.iter().position(|&b| b == target_block);
235 if block_stmt_count > 0 && dist_to_target.map_or(false, |d| d <= 2) {
236 Self::re_visit_newly_added(visitor, block, block_data, flow, &mut relevant, &mut items, keep_inv);
237 }
238 child_path.insert(0, node.block);
239 results.push((child_path, items, relevant));
240 }
241 }
242
243 if !checkpoint_items.is_empty() {
250 results.push((vec![node.block], checkpoint_items, checkpoint_relevant));
251 }
252
253 results
254 }
255
256 fn re_visit_newly_added(
260 visitor: &Self,
261 block: BasicBlock,
262 block_data: &'tcx rustc_middle::mir::BasicBlockData<'tcx>,
263 flow: &DataflowGraph,
264 relevant: &mut RelevantPlaces,
265 items: &mut Vec<RelevantItem<'tcx>>,
266 keep_inv: bool,
267 ) {
268 let newly_added = std::mem::take(&mut relevant.just_added);
269 if newly_added.is_empty() {
270 return;
271 }
272 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
273 let defs = match &stmt.kind {
274 rustc_middle::mir::StatementKind::Assign(assign) => {
275 let mut d = crate::verify::def_use::RelevantPlaces::new();
276 d.insert_mir_place(&assign.0);
277 d
278 }
279 _ => continue,
280 };
281 let any_new = defs.places.iter().any(|dp| {
282 newly_added.iter().any(|np| dp.local() == np.local())
283 });
284 if any_new {
285 visitor.visit_statement(
286 block,
287 si,
288 stmt,
289 flow,
290 relevant,
291 items,
292 keep_inv,
293 );
294 }
295 }
296 }
297
298 fn visit_statement(
300 &self,
301 block: BasicBlock,
302 statement_index: usize,
303 statement: &'tcx rustc_middle::mir::Statement<'tcx>,
304 flow: &DataflowGraph,
305 relevant: &mut RelevantPlaces,
306 items: &mut Vec<RelevantItem<'tcx>>,
307 keep_invalidations: bool,
308 ) {
309 if keep_invalidations && matches!(statement.kind, StatementKind::StorageDead(_) | StatementKind::StorageLive(_))
310 {
311 items.push(RelevantItem::Statement {
312 block,
313 statement_index,
314 });
315 return;
316 }
317
318 let mut defs = RelevantPlaces::new();
319 match &statement.kind {
320 StatementKind::Assign(assign) => {
321 let (place, _) = &**assign;
322 defs.insert_mir_place(place);
323 }
324 StatementKind::StorageDead(local) => {
325 defs.insert_local(*local);
326 }
327 _ => {}
328 }
329
330 if defs.intersects(relevant) {
331 let mut uses = collect_statement_uses(statement, block, statement_index, flow);
332 items.push(RelevantItem::Statement {
333 block,
334 statement_index,
335 });
336 let mut already_seen: crate::compat::FxHashSet<crate::verify::def_use::PlaceKey> =
342 relevant.places.clone();
343 let is_aggregate =
350 if let rustc_middle::mir::StatementKind::Assign(assign) = &statement.kind {
351 matches!(assign.1, rustc_middle::mir::Rvalue::Aggregate(..))
352 } else {
353 false
354 };
355 if is_aggregate {
356 already_seen.extend(relevant.saturated.iter().cloned());
357 }
358 relevant.remove_all(&defs);
359 uses.places.retain(|p| !already_seen.contains(p));
360 relevant.extend(uses);
361 return;
362 }
363
364 if statement_invalidates_relevant(statement, relevant) {
365 items.push(RelevantItem::Statement {
366 block,
367 statement_index,
368 });
369 } else if statement_can_refine(statement) {
370 let mut uses = RelevantPlaces::new();
371 for &local in &defs.locals {
372 for &edge_idx in &flow.node(local).in_edges {
373 let edge = &flow.edges[edge_idx];
374 if edge.block == block.as_usize() && edge.statement_index == statement_index {
375 uses.insert_local(edge.src);
376 }
377 }
378 }
379 if uses.intersects(relevant) {
380 items.push(RelevantItem::Statement {
381 block,
382 statement_index,
383 });
384 }
385 }
386 }
387
388 fn visit_terminator(
390 &self,
391 block: BasicBlock,
392 terminator: &rustc_middle::mir::Terminator<'tcx>,
393 flow: &DataflowGraph,
394 body: &Body<'tcx>,
395 relevant: &mut RelevantPlaces,
396 items: &mut Vec<RelevantItem<'tcx>>,
397 keep_invalidations: bool,
398 ) {
399 if keep_invalidations && matches!(terminator.kind, TerminatorKind::Drop { .. }) {
400 items.push(RelevantItem::Terminator { block });
401 return;
402 }
403
404 if let TerminatorKind::Call {
405 func,
406 args,
407 destination,
408 ..
409 } = &terminator.kind
410 {
411 call_visit::visit(
412 self.tcx,
413 block,
414 func,
415 args,
416 destination,
417 flow,
418 body,
419 relevant,
420 items,
421 );
422 return;
423 }
424
425 let use_def = terminator_use_def(terminator);
426 if terminator_is_path_condition(terminator) {
427 items.push(RelevantItem::Terminator { block });
428 relevant.extend(use_def.uses.clone());
429 return;
430 }
431
432 if use_def.defs.intersects(relevant) {
433 if terminator_may_havoc(terminator) {
434 items.push(RelevantItem::Forget);
435 }
436 items.push(RelevantItem::Terminator { block });
437 relevant.remove_all(&use_def.defs);
438 relevant.extend(use_def.uses);
439 return;
440 }
441
442 if use_def.uses.intersects(relevant) {
443 if terminator_may_havoc(terminator) {
444 items.push(RelevantItem::Forget);
445 }
446 items.push(RelevantItem::Terminator { block });
447 }
448 }
449}
450
451fn needs_invalidation_tracking(kind: &contract::PropertyKind) -> bool {
454 matches!(kind, contract::PropertyKind::Allocated)
455}
456
457fn statement_can_refine(statement: &rustc_middle::mir::Statement<'_>) -> bool {
460 matches!(&statement.kind, StatementKind::Assign(assign) if matches!(
461 &**assign,
462 (
463 _,
464 rustc_middle::mir::Rvalue::BinaryOp(_, _)
465 | rustc_middle::mir::Rvalue::UnaryOp(_, _)
466 | rustc_middle::mir::Rvalue::Cast(_, _, _),
467 )
468 ))
469}
470
471fn statement_invalidates_relevant(
472 statement: &rustc_middle::mir::Statement<'_>,
473 relevant: &RelevantPlaces,
474) -> bool {
475 match &statement.kind {
476 StatementKind::StorageDead(local) => relevant.locals.contains(local),
477 _ => false,
478 }
479}
480
481fn terminator_is_path_condition(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
482 matches!(
483 terminator.kind,
484 TerminatorKind::SwitchInt { .. } | TerminatorKind::Assert { .. }
485 )
486}
487
488fn terminator_may_havoc(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
489 matches!(terminator.kind, TerminatorKind::Call { .. })
490}
491
492fn collect_statement_uses<'tcx>(
494 statement: &'tcx rustc_middle::mir::Statement<'tcx>,
495 block: BasicBlock,
496 statement_index: usize,
497 flow: &DataflowGraph,
498) -> RelevantPlaces {
499 let mut uses = RelevantPlaces::new();
500
501 let def_locals = match &statement.kind {
503 StatementKind::Assign(assign) => {
504 let (place, _) = &**assign;
505 vec![place.local]
506 }
507 StatementKind::StorageDead(local) => vec![*local],
508 _ => Vec::new(),
509 };
510
511 for &local in &def_locals {
512 for &edge_idx in &flow.node(local).in_edges {
513 let edge = &flow.edges[edge_idx];
514 if edge.block == block.as_usize() && edge.statement_index == statement_index {
515 uses.insert_local(edge.src);
516 }
517 }
518 }
519
520 if let StatementKind::Assign(assign) = &statement.kind {
524 let (_, rvalue) = &**assign;
525 for operand in super::super::def_use::rvalue_operands(rvalue) {
526 uses.extend(operand_uses(operand));
527 }
528 if let rustc_middle::mir::Rvalue::Ref(_, _, place)
537 | rustc_middle::mir::Rvalue::RawPtr(_, place) = rvalue
538 {
539 uses.insert_local(place.local);
540 }
541 }
542
543 uses
544}