use crate::analysis::Analysis;
use crate::analysis::path_analysis::{
PathTree,
graph::{PathEnumerator, PathGraph},
};
use crate::cli::VerifyMode;
use crate::helpers::fn_info::{FnKind, get_cons, get_mutated_fields, get_muts, get_type};
use crate::verify::contract::PropertyKind;
use crate::verify::target::get_contract_from_annotation;
use crate::compat::{FxHashMap, FxHashSet};
use indexmap::IndexMap;
use rustc_middle::mir::BasicBlock;
use rustc_middle::ty::TyCtxt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use super::{
contract::Property,
engine::VerifyEngine,
helpers::{Checkpoint, CheckpointKind, CheckpointLocation, collect_return_block_indices},
path_extractor::{CallGroup, PATH_LIMIT, PathExtractor},
report::{PropertyCheckResult, VerificationReport, VisitDiagnostics},
slicer::BackwardItem,
target::{FunctionTarget, VerifyTargetCollector},
};
pub struct VerifyDriver<'target, 'tcx> {
tcx: TyCtxt<'tcx>,
target: &'target FunctionTarget<'tcx>,
path_info: Vec<CallGroup<'tcx>>,
engine: VerifyEngine<'tcx>,
allow_repeat: usize,
}
impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, target: &'target FunctionTarget<'tcx>) -> Self {
Self::new_with_repeat(tcx, target, 0)
}
pub fn new_with_repeat(
tcx: TyCtxt<'tcx>,
target: &'target FunctionTarget<'tcx>,
allow_repeat: usize,
) -> Self {
let mut all_checkpoints = target.checkpoints.clone();
for (checkpoint, _) in &target.raw_ptr_deref_checks {
all_checkpoints.push(checkpoint.clone());
}
for (checkpoint, _) in &target.static_mut_checks {
all_checkpoints.push(checkpoint.clone());
}
let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
let engine = VerifyEngine::new(tcx);
Self {
tcx,
target,
path_info,
engine,
allow_repeat,
}
}
pub fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
pub fn target(&self) -> &'target FunctionTarget<'tcx> {
self.target
}
pub fn path_info(&self) -> &[CallGroup<'tcx>] {
&self.path_info
}
pub fn verify_function(&self) -> VerificationReport<'tcx> {
let mut report = VerificationReport::new(self.target.def_id);
for view in self.iter_callsite_checks() {
for (property_index, property) in view.properties.iter().enumerate() {
let bulk = self.engine.check_callsite_from_tree(
view.tree,
view.checkpoint,
property,
&self.target.caller_requires,
);
for (path_index, (forward, smt_check)) in bulk.iter().enumerate() {
let check_diagnostics =
format!("{}\n{}", forward.describe(), smt_check.describe());
report.push(PropertyCheckResult {
checkpoint: view.checkpoint.location(),
checkpoint_index: view.checkpoint_index,
path_index,
property_index,
property: property.clone(),
result: smt_check.result.clone(),
diagnostics: Some(VisitDiagnostics::new(String::new(), check_diagnostics)),
path_description: forward.path.describe_indices(),
callee_name: view.checkpoint.callee_name(self.tcx),
});
}
}
}
report
}
pub fn properties_for_callsite(
&self,
checkpoint: &Checkpoint<'tcx>,
) -> &'target [Property<'tcx>] {
let loc = checkpoint.location();
match checkpoint.kind {
CheckpointKind::RawPtrDeref => {
for (cs, props) in &self.target.raw_ptr_deref_checks {
if cs.location() == loc {
return props.as_slice();
}
}
&[]
}
CheckpointKind::StaticMutAccess => {
for (cs, props) in &self.target.static_mut_checks {
if cs.location() == loc {
return props.as_slice();
}
}
&[]
}
CheckpointKind::UnsafeCall => {
if let Some(callee) = checkpoint.callee {
self.target
.callee_requires
.get(&callee)
.map(Vec::as_slice)
.unwrap_or(&[])
} else {
&[]
}
}
}
}
pub fn iter_callsite_checks(
&self,
) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
let mut checkpoint_index = 0usize;
self.path_info.iter().flat_map(move |group| {
group.checkpoints.iter().filter_map(move |checkpoint| {
let properties = self.properties_for_callsite(checkpoint);
if properties.is_empty() {
return None;
}
let view = CheckpointCheckView {
checkpoint_index,
checkpoint,
tree: &group.tree,
properties,
};
checkpoint_index += 1;
Some(view)
})
})
}
pub fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
let mut report = VerificationReport::new(self.target.def_id);
let invariants = &self.target.struct_invariants;
if invariants.is_empty() {
return report;
}
let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
let caller_contracts = &self.target.caller_requires;
let entry_facts: Vec<BackwardItem<'tcx>> = if is_constructor {
caller_contracts
.iter()
.filter(|c| !matches!(c.kind, PropertyKind::Unknown))
.map(|c| BackwardItem::ContractFact {
property: c.clone(),
})
.collect()
} else {
invariants
.iter()
.map(|inv| BackwardItem::ContractFact {
property: inv.clone(),
})
.collect()
};
for (checkpoint, tree) in self.build_invariant_trees(is_constructor) {
rap_debug!(
"[rapx::verify] struct invariant checkpoint bb{}: {} tree node(s)",
checkpoint.block.as_usize(),
tree.len()
);
for (property_index, invariant) in invariants.iter().enumerate() {
let results = self.engine.check_invariant_from_tree(
self.target.def_id,
&tree,
checkpoint,
invariant,
&entry_facts,
);
for (path_index, check) in results.iter().enumerate() {
report.push(PropertyCheckResult {
checkpoint: checkpoint,
checkpoint_index: checkpoint.block.as_usize(),
path_index,
property_index,
property: invariant.clone(),
result: check.result.clone(),
diagnostics: Some(VisitDiagnostics::new(
check.slicing_diag.clone(),
check.verification_diag.clone(),
)),
path_description: String::new(),
callee_name: format!("struct-invariant(bb{})", checkpoint.block.as_usize()),
});
}
}
}
report
}
fn build_invariant_trees(
&self,
is_constructor: bool,
) -> FxHashMap<CheckpointLocation, PathTree> {
let mut pg = PathGraph::new(self.tcx, self.target.def_id);
pg.find_scc();
let mut enumerator = PathEnumerator::new(&pg);
let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
let kind_label = if is_constructor {
"constructor"
} else {
"method"
};
rap_debug!(
"[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
all_paths.len(),
self.tcx.def_path_str(self.target.def_id),
);
let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
if is_constructor {
let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
for &return_block in &return_blocks {
let checkpoint = CheckpointLocation {
caller: self.target.def_id,
block: return_block,
};
let mut tree = PathTree::new();
let _ = all_paths.walk_prefixes(
return_block.as_usize(),
&mut |prefix: &[usize]| -> bool {
if tree.len() >= PATH_LIMIT {
return false;
}
tree.insert(prefix);
true
},
);
if !tree.is_empty() {
trees_by_checkpoint.insert(checkpoint, tree);
}
}
} else {
let mut seen_paths = FxHashSet::default();
for path in all_paths.iter() {
if path.is_empty() {
continue;
}
if !seen_paths.insert(path.clone()) {
continue;
}
let last_block = BasicBlock::from(*path.last().unwrap());
let checkpoint = CheckpointLocation {
caller: self.target.def_id,
block: last_block,
};
trees_by_checkpoint
.entry(checkpoint)
.or_insert_with(PathTree::new)
.insert(path.as_slice());
}
}
trees_by_checkpoint
}
}
pub struct CheckpointCheckView<'view, 'target, 'tcx> {
pub checkpoint_index: usize,
pub checkpoint: &'view Checkpoint<'tcx>,
pub tree: &'view PathTree,
pub properties: &'target [Property<'tcx>],
}
pub struct VerifyRun<'tcx> {
tcx: TyCtxt<'tcx>,
postfix_repeat: usize,
mode: VerifyMode,
}
impl<'tcx> VerifyRun<'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, postfix_repeat: usize, mode: VerifyMode) -> Self {
Self {
tcx,
postfix_repeat,
mode,
}
}
fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
for target in targets {
let read_def_id = target.def_id;
let cons = get_cons(self.tcx, read_def_id);
if cons.is_empty() {
continue;
}
let muts = get_muts(self.tcx, read_def_id);
for &con_id in &cons {
let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
self.verify_and_emit_sequence(target, read_def_id, &con_target, con_id, &[], 0);
for (mut_idx, &mut_id) in muts.iter().enumerate() {
let con_target =
self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
self.verify_and_emit_sequence(
target,
read_def_id,
&con_target,
con_id,
&[mut_id],
1 + mut_idx,
);
}
}
}
}
fn build_virtual_target(
&self,
read_target: &FunctionTarget<'tcx>,
read_def_id: rustc_hir::def_id::DefId,
con_id: rustc_hir::def_id::DefId,
mut_ids: &[rustc_hir::def_id::DefId],
) -> FunctionTarget<'tcx> {
let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
let con_contracts = get_contract_from_annotation(self.tcx, con_id);
accumulated_requires.extend(con_contracts);
if !mut_ids.is_empty() {
let mut mutated_fields: Vec<usize> = Vec::new();
for &mut_id in mut_ids {
for field_idx in get_mutated_fields(self.tcx, mut_id) {
if !mutated_fields.contains(&field_idx) {
mutated_fields.push(field_idx);
}
}
}
if !mutated_fields.is_empty() {
accumulated_requires.retain(|prop| {
let prop_fields = property_field_indices(prop);
!prop_fields.iter().any(|f| mutated_fields.contains(f))
});
}
}
let own_requires = get_contract_from_annotation(self.tcx, read_def_id);
for req in own_requires {
if !accumulated_requires.iter().any(|p| same_property(p, &req)) {
accumulated_requires.push(req);
}
}
FunctionTarget {
def_id: read_def_id,
owner_struct_def_id: read_target.owner_struct_def_id,
checkpoints: read_target.checkpoints.clone(),
callee_requires: read_target.callee_requires.clone(),
caller_requires: accumulated_requires,
struct_invariants: Vec::new(),
raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
static_mut_checks: read_target.static_mut_checks.clone(),
}
}
fn verify_and_emit_sequence(
&self,
_read_target: &FunctionTarget<'tcx>,
read_def_id: rustc_hir::def_id::DefId,
con_target: &FunctionTarget<'tcx>,
con_id: rustc_hir::def_id::DefId,
mut_ids: &[rustc_hir::def_id::DefId],
_seq_index: usize,
) {
let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
for repeat in 0..=self.postfix_repeat {
let driver = VerifyDriver::new_with_repeat(self.tcx, con_target, repeat);
let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
match result {
Ok(report) => {
rap_debug!("{}", report.describe());
all_results.extend(report.results);
}
Err(e) => {
let msg = e.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| e.downcast_ref::<&str>().copied())
.unwrap_or("<rustc ICE>");
rap_warn!("Skipping invless constructor {} (repeat {}): {}",
self.tcx.def_path_str(con_id),
repeat,
msg
);
all_results.clear();
break;
}
}
}
let read_name = short_fn_name(self.tcx, read_def_id);
let con_name = short_fn_name(self.tcx, con_id);
let mut chain_parts: Vec<String> = vec![con_name];
for &mut_id in mut_ids {
chain_parts.push(short_fn_name(self.tcx, mut_id));
}
chain_parts.push(read_name);
let chain_label = chain_parts.join(" -> ");
rap_info!("============================================================");
rap_info!("[rapx::verify] sequence: {chain_label}");
rap_info!("============================================================");
let unproved = all_results
.iter()
.filter(|r| !matches!(r.result, super::report::CheckResult::Proved))
.count();
let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
IndexMap::new();
for r in &all_results {
groups
.entry((r.checkpoint, r.callee_name.clone()))
.or_default()
.push(r);
}
let checkpoint_groups: Vec<_> = groups
.iter()
.filter(|((_, name), _)| !name.starts_with("struct-invariant"))
.collect();
if !checkpoint_groups.is_empty() {
rap_info!(" --- unsafe checkpoints ---");
for ((checkpoint, callee_name), results) in &checkpoint_groups {
rap_info!(
" unsafe checkpoint: bb{} -> {callee_name}",
checkpoint.block.as_usize(),
);
emit_property_rows(results);
}
}
if unproved == 0 && !all_results.is_empty() {
rap_info!(" result: SOUND");
} else {
rap_warn!(" result: UNSOUND ({unproved} unproved)");
}
rap_info!("");
}
}
impl<'tcx> Analysis for VerifyRun<'tcx> {
fn name(&self) -> &'static str {
"Verify Driver"
}
fn run(&mut self) {
let mut collector = VerifyTargetCollector::new(self.tcx, self.mode);
self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
for target in &collector.function_targets {
let target_path = self.tcx.def_path_str(target.def_id);
let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
for repeat in 0..=self.postfix_repeat {
let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
match result {
Ok(report) => {
rap_debug!("{}", report.describe());
all_results.extend(report.results);
}
Err(e) => {
let msg = e.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| e.downcast_ref::<&str>().copied())
.unwrap_or("<rustc ICE>");
rap_warn!(
"Skipping function {} (repeat {}): {}",
target_path,
repeat,
msg
);
all_results.clear();
break;
}
}
}
if !target.struct_invariants.is_empty() && !matches!(self.mode, VerifyMode::Invless) {
let driver = VerifyDriver::new_with_repeat(self.tcx, target, self.postfix_repeat);
let struct_report = driver.verify_struct_invariants();
rap_debug!("{}", struct_report.describe());
all_results.extend(struct_report.results);
}
if all_results.is_empty() {
if target.checkpoints.is_empty()
&& target.raw_ptr_deref_checks.is_empty()
&& target.static_mut_checks.is_empty()
&& target.struct_invariants.is_empty()
{
rap_info!("============================================================");
rap_info!("[rapx::verify] function: {target_path}");
rap_info!("============================================================");
if matches!(self.mode, VerifyMode::Invless) {
let cons = get_cons(self.tcx, target.def_id);
for con in &cons {
rap_info!(" + constructor: {}", self.tcx.def_path_str(*con));
}
}
rap_info!(" --- unsafe checkpoints ---");
rap_info!(" <none>");
rap_info!(" Unknown | Unproved");
rap_warn!(" result: UNSOUND (no safety contracts found)");
rap_info!("");
}
continue;
}
if matches!(self.mode, VerifyMode::Invless)
&& !get_cons(self.tcx, target.def_id).is_empty()
{
continue;
}
emit_verify_summary(
self.tcx,
&target_path,
target.def_id,
&all_results,
self.mode,
);
}
if !collector.trait_targets.is_empty() {
let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
for trait_def_id in trait_ids {
let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
continue;
};
rap_info!("============================================================");
rap_info!(
"[rapx::verify] unsafe trait impl: {}",
self.tcx.def_path_str(trait_target.def_id)
);
rap_info!("============================================================");
if let Some(self_ty) = trait_target.self_ty_def_id {
rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
}
if trait_target.ensures.is_empty() {
rap_info!(" ensures: <none>");
} else {
rap_info!(" ensures (implementor must satisfy):");
for (method_name, contracts) in &trait_target.ensures {
rap_info!(" fn {}:", method_name);
for property in contracts {
rap_info!(" - {:?}, args={:?}", property.kind, property.args);
}
}
}
rap_info!(" verification: deferred");
rap_info!("");
}
}
if matches!(self.mode, VerifyMode::Invless) {
self.run_invless_sequences(&collector.function_targets);
}
}
fn reset(&mut self) {}
}
fn emit_verify_summary<'tcx>(
tcx: TyCtxt<'tcx>,
target_path: &str,
def_id: rustc_hir::def_id::DefId,
all_results: &[PropertyCheckResult<'tcx>],
mode: VerifyMode,
) {
let unproved = all_results
.iter()
.filter(|r| !matches!(r.result, super::report::CheckResult::Proved))
.count();
rap_info!("============================================================");
rap_info!("[rapx::verify] function: {target_path}");
rap_info!("============================================================");
if matches!(mode, VerifyMode::Invless) {
let cons = get_cons(tcx, def_id);
for con in &cons {
rap_info!(" + constructor: {}", tcx.def_path_str(*con));
}
}
let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
IndexMap::new();
for r in all_results {
groups
.entry((r.checkpoint, r.callee_name.clone()))
.or_default()
.push(r);
}
let checkpoint_groups: Vec<_> = groups
.iter()
.filter(|((_, name), _)| !name.starts_with("struct-invariant"))
.collect();
let invariant_groups: Vec<_> = groups
.iter()
.filter(|((_, name), _)| name.starts_with("struct-invariant"))
.collect();
if !checkpoint_groups.is_empty() {
rap_info!(" --- unsafe checkpoints ---");
for ((checkpoint, callee_name), results) in &checkpoint_groups {
rap_info!(
" unsafe checkpoint: bb{} -> {callee_name}",
checkpoint.block.as_usize(),
);
emit_property_rows(results);
}
}
if !invariant_groups.is_empty() {
rap_info!(" --- struct invariants ---");
for ((checkpoint, _), results) in &invariant_groups {
rap_info!(" checkpoint bb{}:", checkpoint.block.as_usize(),);
emit_property_rows(results);
}
}
if unproved == 0 {
rap_info!(" result: SOUND");
} else {
rap_warn!(" result: UNSOUND ({unproved} unproved)");
}
rap_info!("");
}
fn emit_property_rows(results: &[&PropertyCheckResult<'_>]) {
let mut path_groups: FxHashMap<&str, Vec<_>> = FxHashMap::default();
for r in results.iter() {
path_groups
.entry(r.path_description.as_str())
.or_default()
.push(r);
}
for (path_desc, props) in &path_groups {
rap_info!(" path {path_desc}:");
for r in props.iter() {
rap_info!(" {:?} | {:?}", r.property.kind, r.result);
}
}
}
pub struct VerifyVisitDump<'tcx> {
tcx: TyCtxt<'tcx>,
postfix_repeat: usize,
mode: VerifyMode,
}
fn short_fn_name(tcx: TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> String {
let path = tcx.def_path_str(def_id);
path.rsplit("::").next().unwrap_or(&path).to_string()
}
fn same_property(
a: &crate::verify::contract::Property<'_>,
b: &crate::verify::contract::Property<'_>,
) -> bool {
matches!(
(&a.kind, &b.kind),
(PropertyKind::Align, PropertyKind::Align)
| (PropertyKind::InBound, PropertyKind::InBound)
| (PropertyKind::Init, PropertyKind::Init)
| (PropertyKind::NonNull, PropertyKind::NonNull)
| (PropertyKind::ValidPtr, PropertyKind::ValidPtr)
)
}
fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
use crate::verify::contract::{ContractExpr, PropertyArg};
let mut indices = Vec::new();
for arg in &property.args {
let place = match arg {
PropertyArg::Place(p) => Some(p),
PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
_ => None,
};
if let Some(place) = place {
for proj in &place.projections {
let crate::verify::contract::ContractProjection::Field { index, .. } = proj;
let idx = *index;
if !indices.contains(&idx) {
indices.push(idx);
}
}
}
}
indices
}
impl<'tcx> VerifyVisitDump<'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, postfix_repeat: usize, mode: VerifyMode) -> Self {
Self {
tcx,
postfix_repeat,
mode,
}
}
}
impl<'tcx> Analysis for VerifyVisitDump<'tcx> {
fn name(&self) -> &'static str {
"Verify Visitor Diagnostic Dump"
}
fn run(&mut self) {
rap_debug!("======== #[rapx::verify] visitor diagnostics ========");
let mut collector = VerifyTargetCollector::new(self.tcx, self.mode);
self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
for target in &collector.function_targets {
let target_path = self.tcx.def_path_str(target.def_id);
rap_debug!(
"[rapx::verify::diagnostics] target: {} (DefId: {:?})",
target_path,
target.def_id
);
for repeat in 0..=self.postfix_repeat {
if self.postfix_repeat > 0 {
rap_debug!(
"[rapx::verify::diagnostics] round {}/{}: postfix-repeat={}",
repeat,
self.postfix_repeat,
repeat
);
}
let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
match result {
Ok(report) => {
rap_debug!("{}", report.describe());
}
Err(_) => {
rap_debug!("[rapx::verify::diagnostics] function {} skipped due to ICE",
self.tcx.def_path_str(target.def_id)
);
}
}
}
}
rap_debug!("=======================================");
}
fn reset(&mut self) {}
}