use rustc_hir::def_id::DefId;
use rustc_middle::mir::Body;
use rustc_middle::mir::{BasicBlock, Local};
use rustc_middle::ty::TyCtxt;
use std::collections::{HashMap, HashSet};
use crate::analysis::dataflow::graph::build_dataflow_graph;
use crate::analysis::dataflow::types::DataflowGraph;
use super::super::{
contract,
path_extractor::{Path, PathStep},
relevance::bind_callsite_roots,
};
use crate::helpers::def_use::RelevantPlaces;
use crate::verify::checkpoint::{Checkpoint, CheckpointKind, CheckpointLocation};
use crate::analysis::path::{PathNode, PathTree};
use super::types::{ProofGoal, RelevantItem};
type LeafItem<'tcx> = (
Vec<usize>,
Vec<RelevantItem<'tcx>>,
RelevantPlaces,
Vec<(DefId, Vec<usize>, RelevantPlaces)>,
);
pub(crate) struct BackwardSlicer<'tcx> {
pub(in crate::verify::slicer) tcx: TyCtxt<'tcx>,
}
impl<'tcx> BackwardSlicer<'tcx> {
pub(crate) fn new(tcx: TyCtxt<'tcx>) -> Self {
Self { tcx }
}
pub(crate) fn visit_path_tree(
&self,
tree: &PathTree,
target_block: usize,
checkpoint: &Checkpoint<'tcx>,
property: &contract::Property<'tcx>,
) -> Vec<ProofGoal<'tcx>> {
self.visit_path_tree_impl(
tree,
target_block,
checkpoint.caller,
checkpoint.block,
Some(checkpoint),
property,
)
}
pub(crate) fn visit_path_tree_for_checkpoint(
&self,
tree: &PathTree,
target_block: usize,
caller: DefId,
checkpoint_loc: CheckpointLocation,
property: &contract::Property<'tcx>,
) -> Vec<ProofGoal<'tcx>> {
self.visit_path_tree_impl(
tree,
target_block,
caller,
checkpoint_loc.block,
None,
property,
)
}
fn visit_path_tree_impl(
&self,
tree: &PathTree,
target_block: usize,
caller: DefId,
checkpoint_block: BasicBlock,
bind_checkpoint: Option<&Checkpoint<'tcx>>,
property: &contract::Property<'tcx>,
) -> Vec<ProofGoal<'tcx>> {
let Some(root) = tree.root() else {
return Vec::new();
};
let checkpoint_loc = CheckpointLocation {
caller,
block: checkpoint_block,
};
let mut bodies: HashMap<DefId, &'tcx Body<'tcx>> = HashMap::new();
let mut flows: HashMap<DefId, DataflowGraph> = HashMap::new();
let mut def_ids: HashSet<DefId> = tree.block_fns().iter().map(|(d, _)| *d).collect();
def_ids.insert(caller);
for d in def_ids {
bodies.insert(d, self.tcx.optimized_mir(d));
flows.insert(d, build_dataflow_graph(self.tcx, d));
}
let leaf_results = Self::build_leaf_items(
self,
tree,
root,
target_block,
checkpoint_block,
bind_checkpoint,
property,
caller,
&bodies,
&flows,
);
let mut results = Vec::new();
for (block_path, backward_items, _relevant, _) in leaf_results {
let mut items = backward_items;
items.reverse();
let steps: Vec<PathStep> = block_path
.iter()
.map(|&b| PathStep::Block(BasicBlock::from(b)))
.chain(std::iter::once(PathStep::Checkpoint(checkpoint_loc)))
.collect();
results.push(ProofGoal {
path: Path {
target: checkpoint_loc,
steps,
},
items,
});
}
results
}
#[allow(clippy::too_many_arguments)]
fn build_leaf_items(
visitor: &Self,
tree: &PathTree,
node: &PathNode,
target_block: usize,
checkpoint_block: BasicBlock,
bind_checkpoint: Option<&Checkpoint<'tcx>>,
property: &contract::Property<'tcx>,
caller: DefId,
bodies: &HashMap<DefId, &'tcx Body<'tcx>>,
flows: &HashMap<DefId, DataflowGraph>,
) -> Vec<LeafItem<'tcx>> {
let (def_id, local_index) = tree.block_fn_of(node.block).unwrap_or((caller, node.block));
let body = &bodies[&def_id];
let flow = &flows[&def_id];
let block = BasicBlock::from(local_index);
let keep_inv = property
.kind()
.is_some_and(|k| needs_invalidation_tracking(&k));
let keep_owner = matches!(property.kind(), Some(contract::PropertyKind::Owning));
let block_data = &body.basic_blocks[block];
let mut results = Vec::new();
let (checkpoint_items, checkpoint_relevant) = if node.block == target_block {
let mut relevant = RelevantPlaces::from_property(property);
if let Some(cs) = bind_checkpoint {
bind_callsite_roots(visitor.tcx, &mut relevant, cs);
}
let mut items = Vec::new();
let skip_terminator = matches!(
bind_checkpoint.map(|c| c.kind),
Some(CheckpointKind::RawPtrDeref)
| Some(CheckpointKind::StaticMutAccess)
| Some(CheckpointKind::UnsafeCall)
) || crate::verify::api_classify::is_manually_drop_drop(
bind_checkpoint.and_then(|c| c.callee),
);
if !skip_terminator {
items.push(RelevantItem::Terminator {
def_id: caller,
block: checkpoint_block,
switch_succ: None,
});
}
for (si, stmt) in block_data.statements.iter().enumerate().rev() {
visitor.visit_statement(
def_id,
checkpoint_block,
si,
stmt,
flow,
&mut relevant,
&mut items,
keep_inv,
keep_owner,
);
}
Self::re_visit_newly_added(
visitor,
def_id,
checkpoint_block,
block_data,
flow,
&mut relevant,
&mut items,
keep_inv,
keep_owner,
);
(items, relevant)
} else {
(Vec::new(), RelevantPlaces::new())
};
for child in &node.children {
let child_results = Self::build_leaf_items(
visitor,
tree,
child,
target_block,
checkpoint_block,
bind_checkpoint,
property,
caller,
bodies,
flows,
);
for (mut child_path, child_items, child_relevant, mut frames) in child_results {
let mut relevant = child_relevant;
let mut items = child_items;
if def_id != caller
&& frames.last().map(|f| f.0) != Some(def_id)
&& let Some(binding) = tree.inline_binding(node.block - local_index)
{
let dest = Local::from_usize(binding.dest_local);
let mut callee_relevant = RelevantPlaces::new();
if relevant.locals.contains(&dest) {
relevant.locals.remove(&dest);
relevant.places.retain(|p| p.local() != Some(dest));
callee_relevant.insert_local(Local::from_usize(0));
}
frames.push((
def_id,
binding.arg_locals.clone(),
std::mem::replace(&mut relevant, callee_relevant),
));
}
if !tree.is_inlined_call(node.block) {
let successor = tree
.block_fn_of(child.block)
.map(|(_, li)| BasicBlock::from(li))
.or(Some(BasicBlock::from(child.block)));
visitor.visit_terminator(
def_id,
block,
block_data.terminator(),
flow,
body,
&mut relevant,
&mut items,
keep_inv,
keep_owner,
successor,
);
}
let block_stmt_count = block_data.statements.len();
for (si, stmt) in block_data.statements.iter().enumerate().rev() {
visitor.visit_statement(
def_id,
block,
si,
stmt,
flow,
&mut relevant,
&mut items,
keep_inv,
keep_owner,
);
}
let dist_to_target = child_path.iter().position(|&b| b == target_block);
if block_stmt_count > 0 && dist_to_target.is_some_and(|d| d <= 2) {
Self::re_visit_newly_added(
visitor,
def_id,
block,
block_data,
flow,
&mut relevant,
&mut items,
keep_inv,
keep_owner,
);
}
if let Some(binding) = tree.inline_binding(node.block) {
let (_, frame_arg_locals, parked) = frames
.pop()
.unwrap_or((def_id, binding.arg_locals.clone(), RelevantPlaces::new()));
let mut caller_relevant = parked;
for (i, arg_local) in frame_arg_locals.iter().enumerate() {
if relevant.locals.contains(&Local::from_usize(i + 1)) {
caller_relevant.insert_local(Local::from_usize(*arg_local));
}
}
relevant = caller_relevant;
}
child_path.insert(0, node.block);
results.push((child_path, items, relevant, frames));
}
}
if node.block == target_block {
results.push((
vec![node.block],
checkpoint_items,
checkpoint_relevant,
Vec::new(),
));
}
results
}
#[allow(clippy::too_many_arguments)]
fn re_visit_newly_added(
visitor: &Self,
def_id: DefId,
block: BasicBlock,
block_data: &'tcx rustc_middle::mir::BasicBlockData<'tcx>,
flow: &DataflowGraph,
relevant: &mut RelevantPlaces,
items: &mut Vec<RelevantItem<'tcx>>,
keep_inv: bool,
keep_owner: bool,
) {
let newly_added = std::mem::take(&mut relevant.just_added);
if newly_added.is_empty() {
return;
}
for (si, stmt) in block_data.statements.iter().enumerate().rev() {
let defs = match &stmt.kind {
rustc_middle::mir::StatementKind::Assign(assign) => {
let mut d = crate::helpers::def_use::RelevantPlaces::new();
d.insert_mir_place(&assign.0);
d
}
_ => continue,
};
let any_new = defs
.places
.iter()
.any(|dp| newly_added.iter().any(|np| dp.local() == np.local()));
if any_new {
visitor.visit_statement(
def_id, block, si, stmt, flow, relevant, items, keep_inv, keep_owner,
);
}
}
}
}
fn needs_invalidation_tracking(kind: &contract::PropertyKind) -> bool {
matches!(
kind,
contract::PropertyKind::Allocated
| contract::PropertyKind::Init
| contract::PropertyKind::Alive
| contract::PropertyKind::ValidString
| contract::PropertyKind::ValidCStr
| contract::PropertyKind::Owning
)
}