use crate::analysis::Analysis;
use crate::analysis::safety_flow::root::{
function_has_struct_invariant, function_has_trait_ensurance, hir_contains_unsafe,
};
use crate::cli::VerifyMode;
use crate::compat::FxHashMap;
use crate::helpers::mir_scan::{collect_raw_ptr_deref_info, collect_static_mut_access_info};
use crate::helpers::name::short_fn_name;
#[cfg(not(rapx_ge_100))]
use rustc_hir::LangItem;
#[cfg(rapx_ge_100)]
use rustc_hir::attrs::lang_items::LangItem;
use rustc_hir::{
Attribute, BodyId, FnDecl, ItemKind,
def_id::{DefId, LocalDefId},
intravisit::{FnKind, Visitor},
};
use rustc_middle::{hir::nested_filter, ty::TyCtxt};
use rustc_span::Span;
use std::collections::{HashMap, HashSet};
use super::{
contract::{
ContractExpr, ContractPlace, PlaceBase, Property, PropertyArg, PropertyKind,
attr::parse_rapx_attr,
},
path_extractor::PathExtractor,
type_invariants::build_type_invariants_from_params,
};
use crate::helpers::fn_info::get_adt_def_id_by_adt_method;
use crate::helpers::mir_scan::{Checkpoint, collect_unsafe_callsites};
use crate::helpers::mir_utils::{
collect_return_block_indices, has_rapx_verify_attr, is_std_crate_def_id, is_trait_unsafe,
resolve_impl_self_ty_def_id,
};
pub(crate) type FnContracts<'tcx> = Vec<Property<'tcx>>;
pub(crate) type StructInvariants<'tcx> = Vec<Property<'tcx>>;
#[derive(Clone, Debug)]
pub(crate) struct FunctionTarget<'tcx> {
pub def_id: DefId,
pub owner_struct_def_id: Option<DefId>,
pub checkpoints: Vec<Checkpoint<'tcx>>,
pub callee_requires: HashMap<DefId, FnContracts<'tcx>>,
pub caller_requires: FnContracts<'tcx>,
pub struct_invariants: Vec<Property<'tcx>>,
pub type_invariants: Vec<Property<'tcx>>,
pub raw_ptr_deref_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
pub static_mut_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
}
impl<'tcx> FunctionTarget<'tcx> {
pub(crate) fn all_checkpoints(&self) -> Vec<&Checkpoint<'tcx>> {
self.checkpoints
.iter()
.chain(
self.raw_ptr_deref_checks
.iter()
.map(|(checkpoint, _)| checkpoint),
)
.chain(
self.static_mut_checks
.iter()
.map(|(checkpoint, _)| checkpoint),
)
.collect()
}
pub(crate) fn properties_for_callsite(
&self,
checkpoint: &Checkpoint<'tcx>,
) -> &[Property<'tcx>] {
let loc = checkpoint.location();
match checkpoint.kind {
crate::helpers::mir_scan::CheckpointKind::RawPtrDeref => self
.raw_ptr_deref_checks
.iter()
.find(|(candidate, _)| candidate.location() == loc)
.map(|(_, properties)| properties.as_slice())
.unwrap_or(&[]),
crate::helpers::mir_scan::CheckpointKind::StaticMutAccess => self
.static_mut_checks
.iter()
.find(|(candidate, _)| candidate.location() == loc)
.map(|(_, properties)| properties.as_slice())
.unwrap_or(&[]),
crate::helpers::mir_scan::CheckpointKind::UnsafeCall => checkpoint
.callee
.and_then(|callee| self.callee_requires.get(&callee))
.map(Vec::as_slice)
.unwrap_or(&[]),
}
}
}
pub(crate) struct StructTarget<'tcx> {
pub def_id: DefId,
pub invariants: StructInvariants<'tcx>,
pub function_targets: Vec<FunctionTarget<'tcx>>,
}
pub(crate) struct TraitEnsurance<'tcx> {
pub def_id: DefId,
pub impl_def_id: DefId,
pub self_ty_def_id: Option<DefId>,
pub kind: TraitEnsuranceKind<'tcx>,
}
pub(crate) enum TraitEnsuranceKind<'tcx> {
Marker(MarkerTraitKind, Vec<Property<'tcx>>),
Unsafe(Vec<(String, FnContracts<'tcx>)>),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum MarkerTraitKind {
Send,
Sync,
}
fn resolve_chain_contracts<'tcx>(
tcx: TyCtxt<'tcx>,
callee_def_id: DefId,
visited: &mut HashSet<DefId>,
) -> FnContracts<'tcx> {
if !visited.insert(callee_def_id) {
return Vec::new();
}
if !tcx.is_mir_available(callee_def_id) {
return Vec::new();
}
let body = tcx.optimized_mir(callee_def_id);
let mut contracts = Vec::new();
for bb in body.basic_blocks.iter() {
let Some(terminator) = &bb.terminator else {
continue;
};
if let rustc_middle::mir::TerminatorKind::Call { func, .. } = &terminator.kind {
if let rustc_middle::mir::Operand::Constant(c) = func {
let rustc_middle::ty::TyKind::FnDef(sub_def_id, _) = c.const_.ty().kind() else {
continue;
};
let sub_def_id = *sub_def_id;
let fn_sig = tcx.fn_sig(sub_def_id).skip_binder();
if fn_sig.safety() != rustc_hir::Safety::Unsafe {
continue;
}
let mut reqs = get_contract_from_annotation(tcx, sub_def_id);
if reqs.is_empty() {
reqs = get_trait_method_requires(tcx, sub_def_id);
}
if reqs.is_empty() && is_std_crate_def_id(tcx, sub_def_id) {
reqs = super::contract::json::query_json_contracts(tcx, sub_def_id);
}
if reqs.is_empty() {
reqs = resolve_chain_contracts(tcx, sub_def_id, visited);
}
contracts.extend(reqs);
}
}
}
contracts
}
pub(crate) struct VerifyTargetCollector<'tcx> {
tcx: TyCtxt<'tcx>,
mode: VerifyMode,
skip_invariant: bool,
crate_filter: Option<String>,
crate_filter_matched: bool,
module_filter: Option<String>,
module_filter_matched: bool,
pub function_targets: Vec<FunctionTarget<'tcx>>,
pub struct_targets: HashMap<DefId, StructTarget<'tcx>>,
pub trait_targets: Vec<TraitEnsurance<'tcx>>,
fn_contract_cache: HashMap<DefId, FnContracts<'tcx>>,
}
impl<'tcx> VerifyTargetCollector<'tcx> {
pub(crate) fn collect_all(
tcx: TyCtxt<'tcx>,
mode: VerifyMode,
skip_invariant: bool,
crate_filter: Option<String>,
module_filter: Option<String>,
) -> Self {
let mut collector = Self::new(
tcx,
mode,
skip_invariant,
crate_filter.clone(),
module_filter,
);
tcx.hir_visit_all_item_likes_in_crate(&mut collector);
if crate_filter.is_some() {
collector.collect_extern_crate_targets();
}
collector.check_module_filter_result();
collector
}
pub(crate) fn new(
tcx: TyCtxt<'tcx>,
mode: VerifyMode,
skip_invariant: bool,
crate_filter: Option<String>,
module_filter: Option<String>,
) -> Self {
VerifyTargetCollector {
tcx,
mode,
skip_invariant,
crate_filter,
crate_filter_matched: false,
module_filter,
module_filter_matched: false,
function_targets: Vec::new(),
struct_targets: HashMap::new(),
trait_targets: Vec::new(),
fn_contract_cache: HashMap::new(),
}
}
fn get_fn_contracts(&mut self, callee_def_id: DefId) -> FnContracts<'tcx> {
let is_std = is_std_crate_def_id(self.tcx, callee_def_id);
let trait_requires = get_trait_method_requires(self.tcx, callee_def_id);
self.fn_contract_cache
.entry(callee_def_id)
.or_insert_with(|| {
let mut requires = get_contract_from_annotation(self.tcx, callee_def_id);
if requires.is_empty() && !trait_requires.is_empty() {
requires = trait_requires.clone();
}
if requires.is_empty() && is_std {
requires = super::contract::json::query_json_contracts(
self.tcx,
callee_def_id,
);
if requires.is_empty() {
let mut visited = HashSet::new();
requires = resolve_chain_contracts(
self.tcx,
callee_def_id,
&mut visited,
);
if requires.is_empty() {
let path = crate::helpers::name::get_cleaned_def_path_name(
self.tcx,
callee_def_id,
);
rap_warn!(
"no safety contracts found for callee \"{path}\""
);
} else {
let path = crate::helpers::name::get_cleaned_def_path_name(
self.tcx,
callee_def_id,
);
rap_debug!(
"resolved {} safety contract(s) for callee \"{path}\" via call chain",
requires.len()
);
}
}
}
if requires.is_empty() {
requires.push(Property::new(
self.tcx,
callee_def_id,
"Unknown",
&[],
));
}
requires
})
.clone()
}
fn build_function_target(&mut self, def_id: DefId) -> FunctionTarget<'tcx> {
let checkpoints = collect_unsafe_callsites(self.tcx, def_id);
let unsafe_callees: HashSet<_> = checkpoints
.iter()
.filter_map(|checkpoint| checkpoint.callee)
.collect();
let callee_requires = unsafe_callees
.iter()
.map(|callee_def_id| {
let mut contracts = self.get_fn_contracts(*callee_def_id);
contracts.retain(|p| {
!matches!(
p.kind(),
Some(crate::verify::contract::PropertyKind::Unknown)
)
});
(*callee_def_id, contracts)
})
.collect();
let mut caller_requires = self.get_fn_contracts(def_id);
let raw_ptr_deref_checks = build_raw_ptr_deref_checks(self.tcx, def_id);
let static_mut_checks = build_static_mut_checks(self.tcx, def_id);
let owner_struct_def_id = get_adt_def_id_by_adt_method(self.tcx, def_id);
let mut struct_invariants = owner_struct_def_id
.map(|struct_def_id| {
get_struct_invariants_from_annotation(self.tcx, struct_def_id, def_id)
})
.unwrap_or_default();
caller_requires.extend(struct_invariants.clone());
if is_drop_impl(self.tcx, def_id) {
struct_invariants.clear();
}
let type_invariants = build_type_invariants_from_params(self.tcx, def_id);
caller_requires.extend(type_invariants.clone());
FunctionTarget {
def_id,
owner_struct_def_id,
checkpoints,
callee_requires,
caller_requires,
struct_invariants,
type_invariants,
raw_ptr_deref_checks,
static_mut_checks,
}
}
fn push_function_target(&mut self, function_target: FunctionTarget<'tcx>) {
self.function_targets.push(function_target.clone());
if let Some(struct_def_id) = function_target.owner_struct_def_id {
self.struct_targets
.entry(struct_def_id)
.or_insert_with(|| StructTarget {
def_id: struct_def_id,
invariants: get_struct_invariants_from_annotation(
self.tcx,
struct_def_id,
function_target.def_id,
),
function_targets: Vec::new(),
})
.function_targets
.push(function_target);
}
}
fn collect_extern_crate_targets(&mut self) {
let local_crate = rustc_hir::def_id::LOCAL_CRATE;
for def_id in self.tcx.mir_keys(()) {
let def_id = def_id.to_def_id();
if def_id.krate == local_crate {
continue; }
if !self.crate_name_matches(def_id) {
continue;
}
let def_kind = self.tcx.def_kind(def_id);
if !matches!(
def_kind,
rustc_hir::def::DefKind::Fn | rustc_hir::def::DefKind::AssocFn
) {
continue;
}
if matches!(self.mode, VerifyMode::Targeted) {
continue;
}
self.crate_filter_matched = true;
if !self.module_path_matches(def_id) {
continue;
}
self.module_filter_matched = true;
let function_target = self.build_function_target(def_id);
self.push_function_target(function_target);
}
}
fn crate_name_matches(&self, def_id: DefId) -> bool {
match self.crate_filter {
None => true,
Some(ref filter) => {
let crate_name = self.tcx.crate_name(def_id.krate);
if crate_name.as_str() == *filter {
return true;
}
if let Ok(pkg_name) = std::env::var("CARGO_PKG_NAME") {
if pkg_name == *filter {
return true;
}
}
false
}
}
}
fn module_path_matches(&self, def_id: DefId) -> bool {
let Some(ref filter) = self.module_filter else {
return true;
};
let def_path = self.tcx.def_path_str(def_id);
if def_path == *filter || def_path.starts_with(&format!("{}::", filter)) {
return true;
}
let crate_name = self.tcx.crate_name(def_id.krate);
let crate_prefix = format!("{}::", crate_name.as_str());
if let Some(inner) = filter.strip_prefix(&crate_prefix) {
if def_path == inner || def_path.starts_with(&format!("{}::", inner)) {
return true;
}
}
if let Some(inner) = def_path.strip_prefix(&crate_prefix) {
if inner == *filter || inner.starts_with(&format!("{}::", filter)) {
return true;
}
}
false
}
pub(crate) fn check_module_filter_result(&self) {
if let Some(ref filter) = self.crate_filter {
if !self.crate_filter_matched {
rap_warn!("[rapx::verify] --crate \"{filter}\" matched no targets");
}
}
if let Some(ref filter) = self.module_filter {
if !self.module_filter_matched {
rap_warn!("[rapx::verify] --module \"{filter}\" matched no functions in the crate");
}
}
}
}
fn get_trait_method_requires<'tcx>(tcx: TyCtxt<'tcx>, callee_def_id: DefId) -> FnContracts<'tcx> {
let Some(assoc_item) = tcx.opt_associated_item(callee_def_id) else {
return Vec::new();
};
let Some(trait_item_def_id) = assoc_item.trait_item_def_id() else {
return Vec::new();
};
get_contract_from_annotation(tcx, trait_item_def_id)
}
impl<'tcx> Visitor<'tcx> for VerifyTargetCollector<'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}
fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) {
if let ItemKind::Impl(rustc_hir::Impl { of_trait, .. }) = &item.kind
&& of_trait.is_some()
{
if matches!(self.mode, VerifyMode::Targeted)
&& !has_rapx_verify_attr(self.tcx, item.owner_id.def_id)
{
rustc_hir::intravisit::walk_item(self, item);
return;
}
let impl_def_id = item.owner_id.to_def_id();
if !self.crate_name_matches(impl_def_id) {
rustc_hir::intravisit::walk_item(self, item);
return;
}
self.crate_filter_matched = true;
if !self.module_path_matches(impl_def_id) {
rustc_hir::intravisit::walk_item(self, item);
return;
}
self.module_filter_matched = true;
let trait_ref = { self.tcx.impl_opt_trait_ref(impl_def_id) };
if let Some(trait_ref) = trait_ref {
let trait_def_id = trait_ref.skip_binder().def_id;
let self_ty_def_id = resolve_impl_self_ty_def_id(&item);
if let Some(kind) = marker_trait_kind(self.tcx, trait_def_id) {
let obligations =
build_marker_trait_obligations(self.tcx, self_ty_def_id, kind);
self.trait_targets.push(TraitEnsurance {
def_id: trait_def_id,
impl_def_id,
self_ty_def_id,
kind: TraitEnsuranceKind::Marker(kind, obligations),
});
} else if is_trait_unsafe(self.tcx, trait_def_id) {
let ensures = get_trait_contracts_from_annotation(self.tcx, trait_def_id);
self.trait_targets.push(TraitEnsurance {
def_id: trait_def_id,
impl_def_id,
self_ty_def_id,
kind: TraitEnsuranceKind::Unsafe(ensures),
});
}
}
}
rustc_hir::intravisit::walk_item(self, item);
}
fn visit_fn(
&mut self,
_fk: FnKind<'tcx>,
_fd: &'tcx FnDecl<'tcx>,
body_id: BodyId,
_span: Span,
id: LocalDefId,
) -> Self::Result {
if matches!(self.mode, VerifyMode::Targeted) && !has_rapx_verify_attr(self.tcx, id) {
if !is_drop_impl(self.tcx, id.to_def_id()) {
return;
}
}
let def_id = id.to_def_id();
if let rustc_hir::def::DefKind::Fn = self.tcx.def_kind(def_id) {
let fn_sig = self.tcx.fn_sig(def_id).skip_binder();
if matches!(
fn_sig.output().skip_binder().kind(),
rustc_type_ir::TyKind::Never
) {
return;
}
}
if !matches!(self.mode, VerifyMode::Targeted) {
if !hir_contains_unsafe(self.tcx, body_id)
&& !function_has_struct_invariant(self.tcx, def_id)
&& !function_has_trait_ensurance(self.tcx, def_id)
{
return;
}
}
let function_target = self.build_function_target(def_id);
match self.mode {
VerifyMode::Targeted => {}
VerifyMode::Scan => {
if function_target.checkpoints.is_empty()
&& function_target.raw_ptr_deref_checks.is_empty()
&& function_target.static_mut_checks.is_empty()
{
if !function_target.struct_invariants.is_empty() {
if self.skip_invariant {
return;
}
} else {
let root = crate::analysis::safety_flow::root::scan_mir(self.tcx, def_id);
if root.is_none() {
return;
}
}
}
}
}
if !self.crate_name_matches(def_id) {
return;
}
self.crate_filter_matched = true;
if !self.module_path_matches(def_id) {
return;
}
self.module_filter_matched = true;
self.push_function_target(function_target);
}
}
pub(crate) struct PrepareTargets<'tcx> {
tcx: TyCtxt<'tcx>,
mode: VerifyMode,
skip_invariant: bool,
crate_filter: Option<String>,
module_filter: Option<String>,
}
impl<'tcx> Analysis for PrepareTargets<'tcx> {
fn run(&mut self) {
let collector = VerifyTargetCollector::collect_all(
self.tcx,
self.mode,
self.skip_invariant,
self.crate_filter.clone(),
self.module_filter.clone(),
);
let free_targets: Vec<_> = collector
.function_targets
.iter()
.filter(|target| target.owner_struct_def_id.is_none())
.collect();
for target in &free_targets {
let target_path = self.tcx.def_path_str(target.def_id);
rap_info!("============================================================");
rap_info!(
"[rapx::verify] prepare targets for free function: {}",
target_path
);
rap_info!("============================================================");
self.log_free_function_unsafe_callees(target);
rap_info!("");
}
let mut struct_ids: Vec<_> = collector.struct_targets.keys().copied().collect();
struct_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
for struct_def_id in struct_ids {
let Some(struct_target) = collector.struct_targets.get(&struct_def_id) else {
continue;
};
let struct_path = self.tcx.def_path_str(struct_target.def_id);
rap_info!("============================================================");
rap_info!("[rapx::verify] prepare targets for struct: {}", struct_path);
rap_info!("============================================================");
self.log_struct_invariants(struct_target);
for target in &struct_target.function_targets {
self.log_method_target(target);
}
}
let mut trait_targets: Vec<_> = collector.trait_targets.iter().collect();
trait_targets.sort_by_key(|t| self.tcx.def_path_str(t.def_id));
for trait_target in trait_targets {
let trait_path = self.tcx.def_path_str(trait_target.def_id);
match &trait_target.kind {
TraitEnsuranceKind::Unsafe(_) => {
rap_info!("============================================================");
rap_info!(
"[rapx::verify] prepare targets for unsafe trait: {}",
trait_path
);
rap_info!("============================================================");
self.log_trait_ensurance(trait_target);
}
TraitEnsuranceKind::Marker(kind, obligations) => {
let name = match kind {
MarkerTraitKind::Send => "Send",
MarkerTraitKind::Sync => "Sync",
};
rap_info!("============================================================");
rap_info!(
"[rapx::verify] prepare targets for marker trait: {}",
name
);
rap_info!("============================================================");
self.log_marker_trait(trait_target, obligations);
}
}
rap_info!("");
}
let total_free = free_targets.len();
let total_method = collector
.function_targets
.iter()
.filter(|target| target.owner_struct_def_id.is_some())
.count();
let total_struct = collector.struct_targets.len();
let total_trait = collector.trait_targets.len();
rap_info!("============================================================");
rap_info!(
"[rapx::verify] total: {} free function(s), {} method(s), {} struct(s), {} trait(s)",
total_free,
total_method,
total_struct,
total_trait
);
rap_info!("============================================================");
}
}
impl<'tcx> PrepareTargets<'tcx> {
pub(crate) fn new(
tcx: TyCtxt<'tcx>,
mode: VerifyMode,
skip_invariant: bool,
crate_filter: Option<String>,
module_filter: Option<String>,
) -> Self {
PrepareTargets {
tcx,
mode,
skip_invariant,
crate_filter,
module_filter,
}
}
fn log_struct_invariants(&self, struct_target: &StructTarget<'tcx>) {
if struct_target.invariants.is_empty() {
rap_info!(" struct invariants: <none>");
} else {
rap_info!(" struct invariants:");
for property in
crate::verify::display::dedup_compound_props(struct_target.invariants.iter())
{
rap_info!(
" - {}",
property.display_for_report(self.tcx, Some(struct_target.def_id), None,)
);
}
}
}
fn log_trait_ensurance(&self, trait_target: &TraitEnsurance<'tcx>) {
if let Some(self_ty) = trait_target.self_ty_def_id {
rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
}
let TraitEnsuranceKind::Unsafe(ensures) = &trait_target.kind else {
return;
};
if ensures.is_empty() {
rap_info!(" ensures: <none>");
} else {
rap_info!(" ensures (implementor must satisfy):");
for (method_name, contracts) in ensures {
rap_info!(" fn {}:", method_name);
for property in crate::verify::display::dedup_compound_props(contracts.iter()) {
let (call, _meaning) = crate::verify::display::fmt_contract_expanded(
self.tcx,
property,
trait_target.self_ty_def_id,
None,
);
rap_info!(" - {call}");
}
}
}
}
fn log_marker_trait(&self, trait_target: &TraitEnsurance<'tcx>, obligations: &[Property<'tcx>]) {
if let Some(self_ty) = trait_target.self_ty_def_id {
rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
}
if obligations.is_empty() {
rap_info!(" obligations: <none>");
} else {
rap_info!(" obligations:");
for property in obligations {
let (call, _meaning) = crate::verify::display::fmt_contract_expanded(
self.tcx,
property,
trait_target.self_ty_def_id,
None,
);
rap_info!(" - {call}");
}
}
}
fn log_method_target(&self, target: &FunctionTarget<'tcx>) {
let name = short_fn_name(self.tcx, target.def_id);
let dashes = 62usize.saturating_sub(10 + name.len());
rap_info!(" --- method: {name} {}", "-".repeat(dashes));
let return_blocks = collect_return_block_indices(self.tcx, target.def_id);
rap_info!(
" return checkpoints: {} block(s) {:?}",
return_blocks.len(),
return_blocks
.iter()
.map(|bb| bb.as_usize())
.collect::<Vec<_>>()
);
let path_map = self.build_checkpoint_path_map(target);
self.log_unsafe_callees_and_contracts(target, &path_map);
}
fn log_free_function_unsafe_callees(&self, target: &FunctionTarget<'tcx>) {
let path_map = self.build_checkpoint_path_map(target);
self.log_unsafe_callees_and_contracts(target, &path_map);
}
fn log_unsafe_callees_and_contracts(
&self,
target: &FunctionTarget<'tcx>,
path_map: &FxHashMap<DefId, Vec<(usize, Vec<String>)>>,
) {
if target.callee_requires.is_empty() {
rap_info!(" unsafe checkpoints: <none>");
return;
}
let mut unsafe_callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
unsafe_callee_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
for unsafe_callee_def_id in unsafe_callee_ids {
let fn_sig = self.tcx.fn_sig(unsafe_callee_def_id).skip_binder();
let unsafe_callee_path = self.tcx.def_path_str(unsafe_callee_def_id);
let inputs: Vec<String> = fn_sig
.inputs()
.skip_binder()
.iter()
.map(|ty| format!("{}", ty))
.collect();
let output = format!("{}", fn_sig.output().skip_binder());
rap_info!(
" unsafe callee: {}({}) -> {}",
unsafe_callee_path,
inputs.join(", "),
output,
);
if let Some(requires) = target.callee_requires.get(&unsafe_callee_def_id) {
if requires.is_empty() {
rap_info!(" safety contracts: <none>");
} else {
rap_info!(" safety contracts:");
for property in crate::verify::display::dedup_compound_props(requires.iter()) {
rap_info!(
" - {}",
property.display_for_report(
self.tcx,
target.owner_struct_def_id,
Some(unsafe_callee_def_id),
)
);
}
}
}
if let Some(path_entries) = path_map.get(&unsafe_callee_def_id) {
for (_block_idx, path_strings) in path_entries {
if path_strings.is_empty() {
rap_info!(" path: <none>");
} else {
for desc in path_strings {
rap_info!(" path: shortest path: {desc}");
}
}
}
}
}
}
fn build_checkpoint_path_map(
&self,
target: &FunctionTarget<'tcx>,
) -> FxHashMap<DefId, Vec<(usize, Vec<String>)>> {
let mut path_map: FxHashMap<DefId, Vec<(usize, Vec<String>)>> = FxHashMap::default();
if target.checkpoints.is_empty() {
return path_map;
}
let groups =
PathExtractor::new(self.tcx, target.def_id, target.checkpoints.clone(), 0).run();
for group in &groups {
for checkpoint in &group.checkpoints {
if let Some(callee_def_id) = checkpoint.callee {
let block_idx = checkpoint.block.as_usize();
let mut path_strings: Vec<String> = Vec::new();
let _ = group.tree.walk_prefixes(
checkpoint.block.as_usize(),
&mut |prefix: &[usize]| -> bool {
let desc = prefix
.iter()
.map(usize::to_string)
.collect::<Vec<_>>()
.join(" -> ");
path_strings.push(desc);
true
},
);
path_map
.entry(callee_def_id)
.or_insert_with(Vec::new)
.push((block_idx, path_strings));
}
}
}
path_map
}
}
fn is_rapx_named_attr(attr: &Attribute, name: &str) -> bool {
let path = attr.path();
if path.len() >= 2
&& path[path.len() - 2].as_str() == "rapx"
&& path[path.len() - 1].as_str() == name
{
return true;
}
path.len() == 1 && path[0].as_str() == name
}
fn marker_trait_kind(tcx: TyCtxt<'_>, trait_def_id: DefId) -> Option<MarkerTraitKind> {
if tcx.get_diagnostic_item(rustc_span::sym::Send) == Some(trait_def_id) {
Some(MarkerTraitKind::Send)
} else if tcx.get_diagnostic_item(rustc_span::sym::Sync) == Some(trait_def_id) {
Some(MarkerTraitKind::Sync)
} else {
None
}
}
fn build_marker_trait_obligations<'tcx>(
tcx: TyCtxt<'tcx>,
self_ty_def_id: Option<DefId>,
trait_kind: MarkerTraitKind,
) -> Vec<Property<'tcx>> {
let Some(self_ty_def_id) = self_ty_def_id else {
return Vec::new();
};
let self_ty = tcx.type_of(self_ty_def_id).skip_binder();
let trait_def_id = match trait_kind {
MarkerTraitKind::Send => tcx.get_diagnostic_item(rustc_span::sym::Send),
MarkerTraitKind::Sync => tcx.get_diagnostic_item(rustc_span::sym::Sync),
};
let Some(trait_def_id) = trait_def_id else {
return Vec::new();
};
let templates = super::contract::json::query_trait_ensures(tcx, trait_def_id);
let mut obligations = Vec::new();
for entry in templates {
if let Some(prop) = build_type_atom(tcx, self_ty_def_id, &entry, self_ty) {
obligations.push(prop);
}
}
obligations
}
fn build_type_atom<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
entry: &super::contract::json::JsonProperty,
self_ty: rustc_middle::ty::Ty<'tcx>,
) -> Option<Property<'tcx>> {
if let Some(items) = &entry.any {
let disjuncts: Vec<Property<'tcx>> = items
.iter()
.filter_map(|item| match item {
super::contract::json::AnyItem::Single(e) => {
build_type_atom(tcx, def_id, e, self_ty)
}
super::contract::json::AnyItem::And(es) => {
let conjuncts: Vec<Property<'tcx>> = es
.iter()
.filter_map(|e| build_type_atom(tcx, def_id, e, self_ty))
.collect();
if conjuncts.is_empty() {
None
} else {
Some(Property::new_and(conjuncts))
}
}
})
.collect();
return if disjuncts.is_empty() {
None
} else {
Some(Property::new_or(disjuncts))
};
}
match entry.tag.as_str() {
"ContainNoType" => {
let negatives: Vec<String> = entry.args[1..]
.iter()
.map(|s| s.strip_prefix("ty:").unwrap_or(s).to_string())
.collect();
let mut args = vec![PropertyArg::Ty(self_ty)];
args.extend(negatives.into_iter().map(PropertyArg::Ident));
Some(Property::new_atom(PropertyKind::ContainNoType, args))
}
"NoRawPtr" => Some(Property::new_atom(
PropertyKind::NoRawPtr,
vec![PropertyArg::Ty(self_ty)],
)),
"NoInternalMut" => Some(Property::new_atom(
PropertyKind::NoInternalMut,
vec![PropertyArg::Ty(self_ty)],
)),
"UniInternalMut" => Some(Property::new_atom(
PropertyKind::UniInternalMut,
vec![PropertyArg::Ty(self_ty)],
)),
"AtomicUpdate" => Some(Property::new_atom(
PropertyKind::AtomicUpdate,
vec![PropertyArg::Ty(self_ty)],
)),
"RefSend" => Some(Property::new_atom(
PropertyKind::RefSend,
vec![PropertyArg::Ty(self_ty)],
)),
_ => {
let mut exprs: Vec<syn::Expr> = entry
.args
.iter()
.filter_map(|s| {
let normalized = super::contract::json::normalize_json_contract_arg(s);
syn::parse_str::<syn::Expr>(&normalized).ok()
})
.collect();
if exprs.len() != entry.args.len() {
return None;
}
if let Some(spec) = super::contract::compound::find_compound(def_id.krate, &entry.tag)
{
for i in exprs.len()..spec.param_tys.len() {
if spec.param_tys.get(i).map(|s| s.as_str()) != Some("Ptr") {
return None;
}
let Some(field) = extract_tamed_field(tcx, def_id) else {
return None;
};
let Ok(e) = syn::parse_str::<syn::Expr>(&field) else {
return None;
};
exprs.push(e);
}
}
match super::contract::compound::expand_compound(tcx, def_id, &entry.tag, &exprs) {
Some(mut props) if !props.is_empty() => {
let origin = props.first().and_then(|p| p.origin()).cloned();
for p in &mut props {
p.clear_origin();
}
let mut combined = Property::conjunction(props);
if let Some(o) = origin {
combined.set_origin(o.name, o.args, o.meaning);
}
Some(combined)
}
_ => None,
}
}
}
}
fn extract_tamed_field<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<String> {
let invariants = get_struct_invariants_from_annotation(tcx, def_id, def_id);
invariants
.iter()
.find(|p| {
matches!(
p.kind(),
Some(PropertyKind::Allocated) | Some(PropertyKind::Owning)
)
})
.and_then(|p| p.args().first())
.and_then(|a| super::contract::place::field_name_from_arg(tcx, def_id, a))
}
fn collect_properties_from_named_attrs<'tcx>(
tcx: TyCtxt<'tcx>,
attrs: impl IntoIterator<Item = &'tcx Attribute>,
property_def_id: DefId,
parse_error_label: &str,
attr_name: &str,
) -> Vec<Property<'tcx>> {
let mut results = Vec::new();
for attr in attrs {
if !is_rapx_named_attr(attr, attr_name) {
continue;
}
let attr_str = crate::compat::attribute_to_string(tcx, attr);
let parsed = match parse_rapx_attr(attr_str.as_str(), attr_name) {
Ok(parsed) => parsed,
Err(err) => {
rap_error!(
"Failed to parse RAPx {} attr '{}': {}",
parse_error_label,
attr_str,
err
);
continue;
}
};
let Some(property) = parsed else { continue };
results.extend(
Property::parse_list(tcx, property_def_id, property.tag.as_str(), &property.args)
.into_iter()
.map(move |mut p| {
p.apply_kind(property.kind.as_deref());
p
}),
);
}
results
}
pub(crate) fn get_contract_from_annotation<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> FnContracts<'tcx> {
if let Some(local_def_id) = def_id.as_local() {
let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
let hir_attrs = tcx.hir_attrs(hir_id);
return collect_properties_from_named_attrs(tcx, hir_attrs, def_id, "requires", "requires");
}
let attrs = crate::compat::get_all_attrs(tcx, def_id);
collect_properties_from_named_attrs(tcx, attrs, def_id, "requires", "requires")
}
pub(crate) fn get_struct_invariants_from_annotation<'tcx>(
tcx: TyCtxt<'tcx>,
struct_def_id: DefId,
context_def_id: DefId,
) -> StructInvariants<'tcx> {
let Some(local_def_id) = struct_def_id.as_local() else {
return Vec::new();
};
let item = tcx.hir_expect_item(local_def_id);
if !matches!(item.kind, ItemKind::Struct(..)) {
return Vec::new();
}
let mut invariants = collect_properties_from_named_attrs(
tcx,
crate::compat::get_all_attrs(tcx, struct_def_id),
context_def_id,
"invariant",
"requires",
);
invariants.extend(collect_properties_from_named_attrs(
tcx,
crate::compat::get_all_attrs(tcx, struct_def_id),
context_def_id,
"invariant",
"invariant",
));
invariants
}
fn get_trait_contracts_from_annotation<'tcx>(
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
) -> Vec<(String, FnContracts<'tcx>)> {
let Some(local_id) = trait_def_id.as_local() else {
return Vec::new();
};
let item = tcx.hir_expect_item(local_id);
let trait_items = {
#[cfg(not(rapx_ge_99))]
if let ItemKind::Trait(.., items) = &item.kind {
items
} else {
return Vec::new();
}
#[cfg(rapx_ge_99)]
if let ItemKind::Trait { items, .. } = &item.kind {
items
} else {
return Vec::new();
}
};
let mut ensures: Vec<(String, FnContracts<'tcx>)> = Vec::new();
for trait_item_id in trait_items.iter() {
let trait_item_def_id = trait_item_id.owner_id.to_def_id();
let method_name = tcx.def_path_str(trait_item_def_id);
let attrs = crate::compat::get_all_attrs(tcx, trait_item_def_id);
let method_ensures = collect_properties_from_named_attrs(
tcx,
attrs,
trait_item_def_id,
"trait ensures",
"ensures",
);
if !method_ensures.is_empty() {
ensures.push((method_name, method_ensures));
}
}
ensures
}
fn build_raw_ptr_deref_checks<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
let infos = collect_raw_ptr_deref_info(tcx, def_id);
if infos.is_empty() {
return Vec::new();
}
infos
.into_iter()
.map(|info| {
let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
base: PlaceBase::Arg(0),
projections: vec![],
}));
let ty = PropertyArg::Ty(info.pointee_ty);
let count = PropertyArg::Expr(ContractExpr::Const(1));
let mut properties = if info.is_ptr2ref {
vec![
Property::new_atom(
PropertyKind::Init,
vec![target.clone(), ty.clone(), count.clone()],
),
Property::new_atom(PropertyKind::Align, vec![target.clone(), ty.clone()]),
{
let mut p = Property::new_atom(PropertyKind::Alias, vec![target.clone()]);
p.set_contract_kind(crate::verify::contract::ContractKind::Hazard);
p
},
]
} else {
vec![
Property::new_atom(
PropertyKind::Allocated,
vec![target.clone(), ty.clone(), count.clone()],
),
Property::new_atom(
PropertyKind::InBound,
vec![target.clone(), ty.clone(), count.clone()],
),
Property::new_atom(PropertyKind::Align, vec![target.clone(), ty.clone()]),
]
};
if info.is_read && !info.is_ptr2ref {
properties.push(Property::new_atom(PropertyKind::Typed, vec![target, ty]));
}
(
Checkpoint {
caller: def_id,
callee: None,
block: info.block,
args: vec![info.ptr_operand],
kind: crate::helpers::mir_scan::CheckpointKind::RawPtrDeref,
destination: Some(info.destination),
},
properties,
)
})
.collect()
}
fn build_static_mut_checks<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
let infos = collect_static_mut_access_info(tcx, def_id);
if infos.is_empty() {
return Vec::new();
}
infos
.into_iter()
.map(|info| {
let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
base: PlaceBase::Arg(0),
projections: vec![],
}));
let ty = PropertyArg::Ty(info.ty);
let count = PropertyArg::Expr(ContractExpr::Const(1));
let properties = vec![
Property::new_atom(
PropertyKind::Allocated,
vec![target.clone(), ty.clone(), count.clone()],
),
Property::new_atom(
PropertyKind::InBound,
vec![target.clone(), ty.clone(), count.clone()],
),
Property::new_atom(PropertyKind::Align, vec![target.clone(), ty.clone()]),
Property::new_atom(PropertyKind::Init, vec![target, ty, count]),
];
(
Checkpoint {
caller: def_id,
callee: None,
block: info.block,
args: vec![info.ptr_operand],
kind: crate::helpers::mir_scan::CheckpointKind::StaticMutAccess,
destination: None,
},
properties,
)
})
.collect()
}
fn is_drop_impl(tcx: TyCtxt<'_>, fn_did: DefId) -> bool {
let Some(impl_id) = tcx.trait_impl_of_assoc(fn_did) else {
return false;
};
let trait_did = tcx.impl_trait_id(impl_id);
tcx.is_lang_item(trait_did, LangItem::Drop)
}