pub(crate) mod builtin_models;
pub(crate) mod interprocedural;
use rustc_hir::def_id::DefId;
#[cfg(not(rapx_ge_100))]
use rustc_hir::LangItem;
#[cfg(rapx_ge_100)]
use rustc_hir::attrs::lang_items::LangItem;
use rustc_middle::{
mir::{Local, Operand},
ty::{Ty, TyCtxt, TyKind},
};
use crate::helpers::mir_utils;
use crate::verify::api_classify::is_std_vec;
#[derive(Clone, Debug)]
pub(crate) struct CallDependencySummary {
pub return_depends_on_args: Vec<usize>,
pub may_write_args: Vec<usize>,
pub unsupported: bool,
}
impl CallDependencySummary {
fn unknown(arg_count: usize) -> Self {
Self {
return_depends_on_args: (0..arg_count).collect(),
may_write_args: Vec::new(),
unsupported: true,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct CallEffectSummary {
pub name: String,
pub effects: Vec<CallEffect>,
pub unsupported: bool,
}
impl CallEffectSummary {
fn unknown(name: String) -> Self {
Self {
name,
effects: Vec::new(),
unsupported: true,
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum CallEffect {
ReturnAliasArg { arg: usize },
ReturnPointerFromArg { arg: usize },
ReturnPointerAdd {
base_arg: usize,
offset_arg: usize,
stride: Option<u64>,
},
ReturnPointerSub {
base_arg: usize,
offset_arg: usize,
stride: Option<u64>,
},
ReturnNonZero,
ReturnAligned,
ReturnConst { value: u64 },
WriteMemory { pointer_arg: usize },
ReturnFreshAllocation {
pointer_arg: usize,
size_arg: usize,
elem_size: u64,
},
ReturnLengthOfArg { arg: usize },
ReturnFieldOfArg { arg: usize, field: usize },
ReturnFieldOfArgSub { arg: usize, field: usize, offset: u64 },
ReturnMin { lhs_arg: usize, rhs_arg: usize },
ReturnMax { lhs_arg: usize, rhs_arg: usize },
ReturnClamp {
value_arg: usize,
min_arg: usize,
max_arg: usize,
},
ReturnAbs { arg: usize },
ReturnNeg { arg: usize },
ReturnAdd { lhs_arg: usize, rhs_arg: usize },
ReturnMul { lhs_arg: usize, rhs_arg: usize },
ReturnOptionSomeAdd { lhs_arg: usize, rhs_arg: usize },
ReturnOptionSomeMul { lhs_arg: usize, rhs_arg: usize },
ReturnNonZeroIff { arg: usize },
ReturnOptionSomeNonZeroIff { arg: usize },
ReturnOptionSomeNonZero,
ReturnTupleFieldNonZero { field: usize },
ReturnTupleFieldLength { field: usize, from_arg: usize },
ReturnNewAllocation { size_arg: usize, elem_size: u64 },
ReturnNewAllocationFromBox,
ReturnNewAllocationFromCap { cap_arg: usize, elem_size: u64 },
ReturnPowerOfTwo,
ReturnBoxFromVec { arg: usize },
OwnsInitMemory { arg: usize },
ChecksIndexBoundsDisjoint { indices_arg: usize, len_arg: usize },
ReturnOptionSomeScanIndex { self_arg: usize },
ReturnOptionSomeIndexLtArgLen { arg: usize },
ReturnOptionSomeTupleFieldLeArgLen { field: usize, arg: usize },
ReturnBranchPayload { arg: usize },
ReturnScanLength,
ReturnAlignOffset { ptr_arg: usize, align_arg: usize },
ReturnAlignTo { receiver_arg: usize },
ReturnIter { receiver_arg: usize },
ReturnTransparentDeref { arg: usize, peel: usize },
ReturnRange { bounds_arg: usize },
ReturnDerefArg { arg: usize },
}
pub(crate) fn dependency_summary<'tcx>(
tcx: TyCtxt<'tcx>,
func: &Operand<'tcx>,
arg_count: usize,
) -> CallDependencySummary {
let callee = mir_utils::dep_callee_def_id(func);
if let Some(callee) = callee {
if tcx.intrinsic(callee).is_some() || mir_utils::is_drop_in_place(callee) {
return CallDependencySummary::unknown(arg_count);
}
if let Some(must_write_args) = interprocedural::local_must_write_args(tcx, callee) {
if !must_write_args.is_empty() {
return CallDependencySummary {
return_depends_on_args: Vec::new(),
may_write_args: must_write_args
.into_iter()
.filter(|index| *index < arg_count)
.collect(),
unsupported: false,
};
}
}
if let Some(effect) = interprocedural::try_slice_bounded_return_effect(tcx, callee) {
if let CallEffect::ReturnOptionSomeIndexLtArgLen { arg } = effect {
if arg < arg_count {
return CallDependencySummary {
return_depends_on_args: vec![arg],
may_write_args: Vec::new(),
unsupported: false,
};
}
}
}
if let Some(effect) = interprocedural::try_decode_length_return_effect(tcx, callee) {
if let CallEffect::ReturnOptionSomeTupleFieldLeArgLen { arg, .. } = effect {
if arg < arg_count {
return CallDependencySummary {
return_depends_on_args: vec![arg],
may_write_args: Vec::new(),
unsupported: false,
};
}
}
}
if mir_utils::call_name(tcx, func).ends_with("::branch") {
return CallDependencySummary {
return_depends_on_args: vec![0],
may_write_args: Vec::new(),
unsupported: false,
};
}
if let Some(return_deps) = interprocedural::local_return_dependencies(tcx, callee) {
return CallDependencySummary {
return_depends_on_args: return_deps
.into_iter()
.filter(|index| *index < arg_count)
.collect(),
may_write_args: Vec::new(),
unsupported: false,
};
}
}
CallDependencySummary::unknown(arg_count)
}
pub(crate) fn effect_summary<'tcx>(
tcx: TyCtxt<'tcx>,
caller: DefId,
func: &Operand<'tcx>,
destination: Local,
) -> CallEffectSummary {
let callee = mir_utils::dep_callee_def_id(func);
let name = mir_utils::call_name(tcx, func);
if let Some(summary) =
builtin_models::lookup_effect(tcx, caller, callee, &name, func, destination)
{
return summary;
}
if let Some(peel) = transparent_deref_peel(tcx, func) {
return CallEffectSummary {
name,
effects: vec![CallEffect::ReturnTransparentDeref { arg: 0, peel }],
unsupported: false,
};
}
if let Some(callee) = callee {
if tcx.intrinsic(callee).is_some() || mir_utils::is_drop_in_place(callee) {
return CallEffectSummary::unknown(name);
}
if let Some(must_write_args) = interprocedural::local_must_write_args(tcx, callee) {
let effects: Vec<_> = must_write_args
.into_iter()
.map(|arg| CallEffect::WriteMemory { pointer_arg: arg })
.collect();
if !effects.is_empty() {
return CallEffectSummary {
name,
effects,
unsupported: false,
};
}
}
if let Some(effect) =
interprocedural::try_pointer_arith_wrapper_effect(tcx, callee, Some(destination))
{
return CallEffectSummary {
name,
effects: vec![effect],
unsupported: false,
};
}
if let Some(effect) =
interprocedural::try_from_raw_parts_wrapper_effect(tcx, callee, Some(destination))
{
return CallEffectSummary {
name,
effects: vec![effect],
unsupported: false,
};
}
if let Some(effect) = interprocedural::try_iter_constructor_effect(tcx, callee) {
return CallEffectSummary {
name,
effects: vec![effect],
unsupported: false,
};
}
if let Some((indices_arg, len_arg)) =
interprocedural::detect_index_disjoint_validator(tcx, callee)
.or_else(|| interprocedural::named_index_disjoint_validator(&name))
{
return CallEffectSummary {
name,
effects: vec![CallEffect::ChecksIndexBoundsDisjoint {
indices_arg,
len_arg,
}],
unsupported: false,
};
}
if let Some(return_deps) = interprocedural::local_return_dependencies(tcx, callee) {
if !interprocedural::callee_contains_pointer_arithmetic(tcx, callee) {
let has_nested_calls = interprocedural::callee_calls_other_local(tcx, callee);
return CallEffectSummary {
name,
effects: return_deps
.into_iter()
.map(|arg| CallEffect::ReturnAliasArg { arg })
.collect(),
unsupported: has_nested_calls,
};
}
}
}
CallEffectSummary::unknown(name)
}
fn transparent_deref_peel<'tcx>(tcx: TyCtxt<'tcx>, func: &Operand<'tcx>) -> Option<usize> {
let self_ty = crate::helpers::mir_utils::fn_def_first_type_arg(func)?;
let TyKind::Adt(adt_def, _) = self_ty.kind() else {
return None;
};
let did = adt_def.did();
if tcx.is_lang_item(did, LangItem::ManuallyDrop) {
return Some(2);
}
if is_maybe_dangling(tcx, did) {
return Some(1);
}
None
}
fn is_maybe_dangling(tcx: TyCtxt<'_>, did: DefId) -> bool {
#[cfg(rapx_has_maybe_dangling_lang_item)]
{
return tcx.is_lang_item(did, LangItem::MaybeDangling);
}
#[cfg(not(rapx_has_maybe_dangling_lang_item))]
{
tcx.def_path_str(did).contains("MaybeDangling")
}
}
pub(crate) fn vec_elem_ty<'tcx>(_tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if let TyKind::Adt(adt_def, substs) = ty.kind() {
if is_std_vec(adt_def.did()) {
return substs.first().and_then(|s| s.as_type());
}
}
None
}
pub(crate) fn from_raw_parts_elem_ty<'tcx>(
tcx: TyCtxt<'tcx>,
caller: DefId,
dest: Option<Local>,
) -> Option<Ty<'tcx>> {
let d = dest?;
let ty = tcx.optimized_mir(caller).local_decls[d].ty;
match ty.kind() {
TyKind::Ref(_, inner, _) => match inner.kind() {
TyKind::Slice(e) => Some(*e),
_ => None,
},
TyKind::RawPtr(inner, _) => match inner.kind() {
TyKind::Slice(e) => Some(*e),
_ => None,
},
TyKind::Adt(..) => vec_elem_ty(tcx, ty),
_ => None,
}
}
pub(crate) fn from_raw_parts_elem_size<'tcx>(
tcx: TyCtxt<'tcx>,
caller: DefId,
dest: Option<Local>,
) -> u64 {
from_raw_parts_elem_ty(tcx, caller, dest)
.and_then(|e| mir_utils::type_layout(tcx, caller, e).map(|(_, s)| s))
.unwrap_or(1)
}