1use rustc_hir::def_id::DefId;
9use rustc_middle::mir::Body;
10use rustc_middle::mir::{BasicBlock, Local, Operand, Rvalue, StatementKind, TerminatorKind};
11use rustc_middle::ty::TyCtxt;
12
13use std::collections::{HashMap, HashSet};
14
15use crate::analysis::dataflow::graph::build_dataflow_graph;
16use crate::analysis::dataflow::types::DataflowGraph;
17
18use super::super::{
19 contract,
20 def_use::{
21 RelevantPlaces, bind_callsite_roots, call_args_uses_at, operand_uses, terminator_use_def,
22 },
23 path_extractor::{Path, PathStep},
24};
25use crate::helpers::mir_scan::{Checkpoint, CheckpointKind, CheckpointLocation};
26
27use crate::analysis::path::{PathNode, PathTree};
28
29use super::{
30 call_visit,
31 types::{ProofGoal, RelevantItem},
32};
33
34pub(crate) struct BackwardSlicer<'tcx> {
36 tcx: TyCtxt<'tcx>,
37}
38
39impl<'tcx> BackwardSlicer<'tcx> {
40 pub(crate) fn new(tcx: TyCtxt<'tcx>) -> Self {
42 Self { tcx }
43 }
44
45 pub(crate) fn visit_path_tree(
51 &self,
52 tree: &PathTree,
53 target_block: usize,
54 checkpoint: &Checkpoint<'tcx>,
55 property: &contract::Property<'tcx>,
56 ) -> Vec<ProofGoal<'tcx>> {
57 self.visit_path_tree_impl(
58 tree,
59 target_block,
60 checkpoint.caller,
61 checkpoint.block,
62 Some(checkpoint),
63 property,
64 )
65 }
66
67 pub(crate) fn visit_path_tree_for_checkpoint(
71 &self,
72 tree: &PathTree,
73 target_block: usize,
74 caller: DefId,
75 checkpoint_loc: CheckpointLocation,
76 property: &contract::Property<'tcx>,
77 ) -> Vec<ProofGoal<'tcx>> {
78 self.visit_path_tree_impl(
79 tree,
80 target_block,
81 caller,
82 checkpoint_loc.block,
83 None,
84 property,
85 )
86 }
87
88 fn visit_path_tree_impl(
91 &self,
92 tree: &PathTree,
93 target_block: usize,
94 caller: DefId,
95 checkpoint_block: BasicBlock,
96 bind_checkpoint: Option<&Checkpoint<'tcx>>,
97 property: &contract::Property<'tcx>,
98 ) -> Vec<ProofGoal<'tcx>> {
99 let Some(root) = tree.root() else {
100 return Vec::new();
101 };
102 let checkpoint_loc = CheckpointLocation {
103 caller,
104 block: checkpoint_block,
105 };
106
107 let mut bodies: HashMap<DefId, &'tcx Body<'tcx>> = HashMap::new();
111 let mut flows: HashMap<DefId, DataflowGraph> = HashMap::new();
112 let mut def_ids: HashSet<DefId> = tree.block_fns().iter().map(|(d, _)| *d).collect();
113 def_ids.insert(caller);
114 for d in def_ids {
115 bodies.insert(d, self.tcx.optimized_mir(d));
116 flows.insert(d, build_dataflow_graph(self.tcx, d));
117 }
118
119 let leaf_results = Self::build_leaf_items(
120 self,
121 tree,
122 root,
123 target_block,
124 checkpoint_block,
125 bind_checkpoint,
126 property,
127 caller,
128 &bodies,
129 &flows,
130 );
131
132 let mut results = Vec::new();
133 for (block_path, backward_items, _relevant, _) in leaf_results {
134 let mut items = backward_items;
135 items.reverse();
136 let steps: Vec<PathStep> = block_path
137 .iter()
138 .map(|&b| PathStep::Block(BasicBlock::from(b)))
139 .chain(std::iter::once(PathStep::Checkpoint(checkpoint_loc)))
140 .collect();
141 results.push(ProofGoal {
142 path: Path {
143 target: checkpoint_loc,
144 steps,
145 },
146 items,
147 block_fn: tree.block_fns().to_vec(),
148 });
149 }
150 results
151 }
152
153 fn build_leaf_items(
157 visitor: &Self,
158 tree: &PathTree,
159 node: &PathNode,
160 target_block: usize,
161 checkpoint_block: BasicBlock,
162 bind_checkpoint: Option<&Checkpoint<'tcx>>,
163 property: &contract::Property<'tcx>,
164 caller: DefId,
165 bodies: &HashMap<DefId, &'tcx Body<'tcx>>,
166 flows: &HashMap<DefId, DataflowGraph>,
167 ) -> Vec<(
168 Vec<usize>,
169 Vec<RelevantItem<'tcx>>,
170 RelevantPlaces,
171 Vec<(DefId, Vec<usize>, RelevantPlaces)>,
172 )> {
173 let (def_id, local_index) = tree.block_fn_of(node.block).unwrap_or((caller, node.block));
174 let body = &bodies[&def_id];
175 let flow = &flows[&def_id];
176 let block = BasicBlock::from(local_index);
177 let keep_inv = property
178 .kind()
179 .is_some_and(|k| needs_invalidation_tracking(&k));
180 let keep_owner = matches!(property.kind(), Some(contract::PropertyKind::Owning));
183 let block_data = &body.basic_blocks[block];
184 let mut results = Vec::new();
185
186 let (checkpoint_items, checkpoint_relevant) = if node.block == target_block {
188 let mut relevant = RelevantPlaces::from_property(property);
189 if let Some(cs) = bind_checkpoint {
190 bind_callsite_roots(visitor.tcx, &mut relevant, cs);
191 }
192 let mut items = Vec::new();
193 let is_statement_checkpoint = matches!(
199 bind_checkpoint.map(|c| c.kind),
200 Some(CheckpointKind::RawPtrDeref) | Some(CheckpointKind::StaticMutAccess)
201 );
202 if !is_statement_checkpoint {
203 items.push(RelevantItem::Terminator {
204 def_id: caller,
205 block: checkpoint_block,
206 switch_succ: None,
207 });
208 }
209 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
211 visitor.visit_statement(
212 def_id,
213 checkpoint_block,
214 si,
215 stmt,
216 flow,
217 &mut relevant,
218 &mut items,
219 keep_inv,
220 keep_owner,
221 );
222 }
223 Self::re_visit_newly_added(
226 visitor,
227 def_id,
228 checkpoint_block,
229 block_data,
230 flow,
231 &mut relevant,
232 &mut items,
233 keep_inv,
234 keep_owner,
235 );
236 (items, relevant)
237 } else {
238 (Vec::new(), RelevantPlaces::new())
239 };
240
241 for child in &node.children {
244 let child_results = Self::build_leaf_items(
245 visitor,
246 tree,
247 child,
248 target_block,
249 checkpoint_block,
250 bind_checkpoint,
251 property,
252 caller,
253 bodies,
254 flows,
255 );
256 for (mut child_path, child_items, child_relevant, mut frames) in child_results {
257 let mut relevant = child_relevant;
258 let mut items = child_items;
259 if def_id != caller
266 && frames.last().map(|f| f.0) != Some(def_id)
267 && let Some(binding) = tree.inline_binding(node.block - local_index)
268 {
269 let dest = Local::from_usize(binding.dest_local);
270 let mut callee_relevant = RelevantPlaces::new();
271 if relevant.locals.contains(&dest) {
272 relevant.locals.remove(&dest);
273 relevant.places.retain(|p| p.local() != Some(dest));
274 callee_relevant.insert_local(Local::from_usize(0));
275 }
276 frames.push((
277 def_id,
278 binding.arg_locals.clone(),
279 std::mem::replace(&mut relevant, callee_relevant),
280 ));
281 }
282 if !tree.is_inlined_call(node.block) {
286 let successor = tree
291 .block_fn_of(child.block)
292 .map(|(_, li)| BasicBlock::from(li))
293 .or(Some(BasicBlock::from(child.block)));
294 visitor.visit_terminator(
295 def_id,
296 block,
297 block_data.terminator(),
298 flow,
299 body,
300 &mut relevant,
301 &mut items,
302 keep_inv,
303 keep_owner,
304 successor,
305 );
306 }
307 let block_stmt_count = block_data.statements.len();
308 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
309 visitor.visit_statement(
310 def_id,
311 block,
312 si,
313 stmt,
314 flow,
315 &mut relevant,
316 &mut items,
317 keep_inv,
318 keep_owner,
319 );
320 }
321 let dist_to_target = child_path.iter().position(|&b| b == target_block);
322 if block_stmt_count > 0 && dist_to_target.is_some_and(|d| d <= 2) {
323 Self::re_visit_newly_added(
324 visitor,
325 def_id,
326 block,
327 block_data,
328 flow,
329 &mut relevant,
330 &mut items,
331 keep_inv,
332 keep_owner,
333 );
334 }
335 if let Some(binding) = tree.inline_binding(node.block) {
339 let (_, frame_arg_locals, parked) = frames
340 .pop()
341 .unwrap_or((def_id, binding.arg_locals.clone(), RelevantPlaces::new()));
342 let mut caller_relevant = parked;
343 for (i, arg_local) in frame_arg_locals.iter().enumerate() {
344 if relevant.locals.contains(&Local::from_usize(i + 1)) {
345 caller_relevant.insert_local(Local::from_usize(*arg_local));
346 }
347 }
348 relevant = caller_relevant;
349 }
350 child_path.insert(0, node.block);
351 results.push((child_path, items, relevant, frames));
352 }
353 }
354
355 if !checkpoint_items.is_empty() {
362 results.push((
363 vec![node.block],
364 checkpoint_items,
365 checkpoint_relevant,
366 Vec::new(),
367 ));
368 }
369
370 results
371 }
372
373 fn re_visit_newly_added(
377 visitor: &Self,
378 def_id: DefId,
379 block: BasicBlock,
380 block_data: &'tcx rustc_middle::mir::BasicBlockData<'tcx>,
381 flow: &DataflowGraph,
382 relevant: &mut RelevantPlaces,
383 items: &mut Vec<RelevantItem<'tcx>>,
384 keep_inv: bool,
385 keep_owner: bool,
386 ) {
387 let newly_added = std::mem::take(&mut relevant.just_added);
388 if newly_added.is_empty() {
389 return;
390 }
391 for (si, stmt) in block_data.statements.iter().enumerate().rev() {
392 let defs = match &stmt.kind {
393 rustc_middle::mir::StatementKind::Assign(assign) => {
394 let mut d = crate::verify::def_use::RelevantPlaces::new();
395 d.insert_mir_place(&assign.0);
396 d
397 }
398 _ => continue,
399 };
400 let any_new = defs
401 .places
402 .iter()
403 .any(|dp| newly_added.iter().any(|np| dp.local() == np.local()));
404 if any_new {
405 visitor.visit_statement(
406 def_id, block, si, stmt, flow, relevant, items, keep_inv, keep_owner,
407 );
408 }
409 }
410 }
411
412 fn visit_statement(
414 &self,
415 def_id: DefId,
416 block: BasicBlock,
417 statement_index: usize,
418 statement: &'tcx rustc_middle::mir::Statement<'tcx>,
419 flow: &DataflowGraph,
420 relevant: &mut RelevantPlaces,
421 items: &mut Vec<RelevantItem<'tcx>>,
422 keep_invalidations: bool,
423 keep_owner: bool,
424 ) {
425 if keep_invalidations
426 && matches!(
427 statement.kind,
428 StatementKind::StorageDead(_) | StatementKind::StorageLive(_)
429 )
430 {
431 items.push(RelevantItem::Statement {
432 def_id,
433 block,
434 statement_index,
435 });
436 return;
437 }
438
439 if keep_owner {
442 if let StatementKind::Assign(assign) = &statement.kind {
443 let (place, _) = &**assign;
444 let body = self.tcx.optimized_mir(def_id);
445 let ty = body.local_decls[place.local].ty;
446 let typing_env = rustc_middle::ty::TypingEnv::non_body_analysis(self.tcx, def_id);
447 if ty.needs_drop(self.tcx, typing_env) {
448 let mut defs = RelevantPlaces::new();
449 defs.insert_mir_place(place);
450 let uses =
451 collect_statement_uses(statement, block, statement_index, flow, &defs);
452 items.push(RelevantItem::Statement {
453 def_id,
454 block,
455 statement_index,
456 });
457 relevant.remove_all(&defs);
458 relevant.extend(uses);
459 return;
460 }
461 }
462 }
463
464 let mut defs = RelevantPlaces::new();
465 match &statement.kind {
466 StatementKind::Assign(assign) => {
467 let (place, _) = &**assign;
468 defs.insert_mir_place(place);
469 }
470 StatementKind::StorageDead(local) => {
471 defs.insert_local(*local);
472 }
473 _ => {}
474 }
475
476 let is_provenance_carrier = match &statement.kind {
484 StatementKind::Assign(assign) => {
485 let (place, rvalue) = &**assign;
486 let dest_is_ptr = matches!(
487 self.tcx.optimized_mir(def_id).local_decls[place.local].ty.kind(),
488 rustc_middle::ty::TyKind::RawPtr(..) | rustc_middle::ty::TyKind::Ref(..)
489 );
490 match rvalue {
491 Rvalue::Cast(..) => dest_is_ptr,
492 Rvalue::Ref(_, _, src_place) | Rvalue::RawPtr(_, src_place) => src_place
493 .projection
494 .iter()
495 .any(|p| {
496 matches!(
497 p.kind(),
498 rustc_middle::mir::ProjectionElem::Deref
499 )
500 }),
501 #[cfg(rapx_rvalue_use_with_retag)]
502 Rvalue::Use(operand, _) => {
503 let is_projected = match operand {
504 Operand::Copy(p) | Operand::Move(p) => p.projection.iter().any(|e| {
505 matches!(e.kind(), rustc_middle::mir::ProjectionElem::Deref)
506 }),
507 _ => false,
508 };
509 dest_is_ptr || is_projected
510 }
511 #[cfg(not(rapx_rvalue_use_with_retag))]
512 Rvalue::Use(operand) => {
513 let is_projected = match operand {
514 Operand::Copy(p) | Operand::Move(p) => p.projection.iter().any(|e| {
515 matches!(e.kind(), rustc_middle::mir::ProjectionElem::Deref)
516 }),
517 _ => false,
518 };
519 dest_is_ptr || is_projected
520 }
521 Rvalue::CopyForDeref(p) => dest_is_ptr || !p.projection.is_empty(),
522 _ => false,
523 }
524 }
525 _ => false,
526 };
527
528 let is_iter_ptr_write = match &statement.kind {
534 StatementKind::Assign(assign) => {
535 let (place, _) = &**assign;
536 let mut proj = place.projection.iter();
537 if !matches!(
538 proj.next().map(|p| p.kind()),
539 Some(rustc_middle::mir::ProjectionElem::Deref)
540 ) {
541 false
542 } else {
543 let is_field0 = matches!(
544 (proj.next().map(|p| p.kind()), proj.next()),
545 (Some(rustc_middle::mir::ProjectionElem::Field(f, _)), None)
546 if f.as_usize() == 0
547 );
548 if !is_field0 {
549 false
550 } else {
551 let base_ty = self.tcx.optimized_mir(def_id).local_decls[place.local].ty;
552 match base_ty.kind() {
553 rustc_middle::ty::TyKind::Ref(_, pointee, _) => {
554 match pointee.kind() {
555 rustc_middle::ty::TyKind::Adt(adt_def, _) => {
556 crate::verify::api_classify::is_std_iter_or_itermut(
557 adt_def.did(),
558 )
559 }
560 _ => false,
561 }
562 }
563 _ => false,
564 }
565 }
566 }
567 }
568 _ => false,
569 };
570
571 let is_byte_write = match &statement.kind {
579 StatementKind::Assign(assign) => {
580 let (place, _) = &**assign;
581 let ty = place.ty(self.tcx.optimized_mir(def_id), self.tcx).ty;
582 crate::helpers::mir_utils::is_u8_array_or_slice(ty)
583 }
584 _ => false,
585 };
586
587 if defs.intersects(relevant) || is_provenance_carrier || is_iter_ptr_write || is_byte_write
588 {
589 let mut uses = collect_statement_uses(statement, block, statement_index, flow, &defs);
590 items.push(RelevantItem::Statement {
591 def_id,
592 block,
593 statement_index,
594 });
595 let already_seen: crate::compat::FxHashSet<crate::verify::def_use::PlaceKey> =
600 relevant.places.clone();
601 relevant.remove_all(&defs);
602 uses.places.retain(|p| !already_seen.contains(p));
603 relevant.extend(uses);
604 return;
605 }
606
607 if statement_can_refine(statement) {
608 let uses = collect_flow_uses(flow, block, statement_index, &defs);
609 if uses.intersects(relevant) {
610 items.push(RelevantItem::Statement {
611 def_id,
612 block,
613 statement_index,
614 });
615 }
616 }
617 }
618
619 fn visit_terminator(
621 &self,
622 def_id: DefId,
623 block: BasicBlock,
624 terminator: &rustc_middle::mir::Terminator<'tcx>,
625 flow: &DataflowGraph,
626 body: &Body<'tcx>,
627 relevant: &mut RelevantPlaces,
628 items: &mut Vec<RelevantItem<'tcx>>,
629 keep_invalidations: bool,
630 keep_owner: bool,
631 successor: Option<BasicBlock>,
632 ) {
633 if keep_invalidations {
634 if matches!(terminator.kind, TerminatorKind::Drop { .. }) {
635 items.push(RelevantItem::Terminator { def_id, block, switch_succ: None });
636 return;
637 }
638 if let TerminatorKind::Call { func, args, .. } = &terminator.kind {
643 let is_drop_call =
644 crate::helpers::mir_utils::dep_callee_def_id(func).is_some_and(|c| {
645 crate::verify::api_classify::is_manually_drop_drop(Some(c))
646 || crate::verify::api_classify::is_std_drop(Some(c))
647 });
648 if is_drop_call {
649 items.push(RelevantItem::Terminator { def_id, block, switch_succ: None });
650 relevant.extend(call_args_uses_at(args, &[0]));
651 return;
652 }
653 }
654 }
655
656 if let TerminatorKind::Call {
657 func,
658 args,
659 destination,
660 ..
661 } = &terminator.kind
662 {
663 if keep_owner {
667 let dest_ty = body.local_decls[destination.local].ty;
668 let typing_env = rustc_middle::ty::TypingEnv::non_body_analysis(self.tcx, def_id);
669 if dest_ty.needs_drop(self.tcx, typing_env) {
670 let use_def = terminator_use_def(terminator);
671 items.push(RelevantItem::Terminator { def_id, block, switch_succ: None });
672 relevant.remove_all(&use_def.defs);
673 relevant.extend(use_def.uses);
674 return;
675 }
676 }
677 call_visit::visit(
678 self.tcx,
679 def_id,
680 block,
681 func,
682 args,
683 destination,
684 flow,
685 body,
686 relevant,
687 items,
688 );
689 return;
690 }
691
692 let use_def = terminator_use_def(terminator);
693 if terminator_is_path_condition(terminator) {
694 let switch_succ = match terminator.kind {
695 TerminatorKind::SwitchInt { .. } => successor,
696 _ => None,
697 };
698 items.push(RelevantItem::Terminator { def_id, block, switch_succ });
699 relevant.extend(use_def.uses.clone());
700 return;
701 }
702
703 if use_def.defs.intersects(relevant) {
704 items.push(RelevantItem::Terminator { def_id, block, switch_succ: None });
705 relevant.remove_all(&use_def.defs);
706 relevant.extend(use_def.uses);
707 return;
708 }
709
710 if use_def.uses.intersects(relevant) {
711 items.push(RelevantItem::Terminator { def_id, block, switch_succ: None });
712 }
713 }
714}
715
716fn needs_invalidation_tracking(kind: &contract::PropertyKind) -> bool {
723 matches!(
724 kind,
725 contract::PropertyKind::Allocated
726 | contract::PropertyKind::Init
727 | contract::PropertyKind::Alive
728 | contract::PropertyKind::ValidString
729 | contract::PropertyKind::ValidCStr
730 | contract::PropertyKind::Owning
731 )
732}
733
734fn statement_can_refine(statement: &rustc_middle::mir::Statement<'_>) -> bool {
737 matches!(&statement.kind, StatementKind::Assign(assign) if matches!(
738 &**assign,
739 (
740 _,
741 rustc_middle::mir::Rvalue::BinaryOp(_, _)
742 | rustc_middle::mir::Rvalue::UnaryOp(_, _)
743 | rustc_middle::mir::Rvalue::Cast(_, _, _),
744 )
745 ))
746}
747
748fn terminator_is_path_condition(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
749 matches!(
750 terminator.kind,
751 TerminatorKind::SwitchInt { .. } | TerminatorKind::Assert { .. }
752 )
753}
754
755fn collect_statement_uses<'tcx>(
757 statement: &'tcx rustc_middle::mir::Statement<'tcx>,
758 block: BasicBlock,
759 statement_index: usize,
760 flow: &DataflowGraph,
761 defs: &RelevantPlaces,
762) -> RelevantPlaces {
763 let mut uses = collect_flow_uses(flow, block, statement_index, defs);
764
765 if let StatementKind::Assign(assign) = &statement.kind {
769 let (_, rvalue) = &**assign;
770 for operand in super::super::def_use::rvalue_operands(rvalue) {
771 uses.extend(operand_uses(operand));
772 }
773 if let rustc_middle::mir::Rvalue::Ref(_, _, place)
782 | rustc_middle::mir::Rvalue::RawPtr(_, place) = rvalue
783 {
784 uses.insert_local(place.local);
785 }
786 }
787
788 uses
789}
790
791fn collect_flow_uses(
794 flow: &DataflowGraph,
795 block: BasicBlock,
796 statement_index: usize,
797 defs: &RelevantPlaces,
798) -> RelevantPlaces {
799 let mut uses = RelevantPlaces::new();
800 for &local in &defs.locals {
801 for &edge_idx in &flow.node(local).in_edges {
802 let edge = &flow.edges[edge_idx];
803 if edge.block == block.as_usize() && edge.statement_index == statement_index {
804 uses.insert_local(edge.src);
805 }
806 }
807 }
808 uses
809}