use rustc_hir::def_id::DefId;
use rustc_middle::mir::{Local, Operand, ProjectionElem, Rvalue, StatementKind};
use crate::verify::{
alias_hazard::{self, AliasProducer, HazardKind},
contract::Property,
def_use::PlaceKey,
};
use crate::helpers::mir_scan::Checkpoint;
use crate::helpers::api_classify;
use crate::analysis::alias::collect_local_origins;
use super::state::{AllocId, VmState, VmValue};
#[derive(Clone, Debug)]
pub struct VmOrigin {
pub local: Local,
pub alloc_id: AllocId,
pub kind: VmOriginKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VmOriginKind {
MutRef,
SharedRef,
RawMutPtr,
RawConstPtr,
Owned(DefId),
Unknown,
}
impl VmOrigin {
pub fn is_mut_ref(&self) -> bool {
matches!(self.kind, VmOriginKind::MutRef)
}
pub fn is_shared_ref(&self) -> bool {
matches!(self.kind, VmOriginKind::SharedRef)
}
pub fn is_const_ptr(&self) -> bool {
matches!(self.kind, VmOriginKind::RawConstPtr)
}
pub fn is_owned(&self) -> bool {
matches!(self.kind, VmOriginKind::Owned(_))
}
}
impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
pub fn resolve_origin(&self, value: &VmValue<'ctx, 'tcx>) -> Option<VmOrigin> {
let Some(prov) = &value.provenance else {
return None;
};
let alloc_id = prov.alloc_id;
let mut best: Option<VmOrigin> = None;
for (local, val) in &self.locals {
let Some(val_prov) = &val.provenance else {
continue;
};
if val_prov.alloc_id != alloc_id {
continue;
}
let kind = self.classify_local(local);
let candidate = VmOrigin {
local: *local,
alloc_id,
kind,
};
let is_param = local.as_usize() <= self.body.arg_count;
let is_owned = candidate.is_owned();
match &best {
None => best = Some(candidate),
Some(existing) => {
let ex_is_param = existing.local.as_usize() <= self.body.arg_count;
let ex_is_owned = existing.is_owned();
if is_param && !ex_is_param {
best = Some(candidate);
} else if is_owned && !ex_is_owned {
best = Some(candidate);
} else if is_param == ex_is_param && is_owned == ex_is_owned {
if local.as_usize() < existing.local.as_usize() {
best = Some(candidate);
}
}
}
}
}
best
}
fn classify_local(&self, local: &Local) -> VmOriginKind {
let ty = self.body.local_decls[*local].ty;
match ty.kind() {
rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Mut) => {
VmOriginKind::MutRef
}
rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not) => {
VmOriginKind::SharedRef
}
rustc_middle::ty::TyKind::RawPtr(inner_ty, rustc_middle::ty::Mutability::Mut) => {
let _ = inner_ty;
VmOriginKind::RawMutPtr
}
rustc_middle::ty::TyKind::RawPtr(..) => VmOriginKind::RawConstPtr,
rustc_middle::ty::TyKind::Adt(adt_def, _) => {
VmOriginKind::Owned(adt_def.did())
}
_ => VmOriginKind::Unknown,
}
}
}
pub enum VmAliasResult {
Proved,
Failed(String),
Unknown,
}
pub fn check_alias_vm<'ctx, 'tcx>(
vm_state: &VmState<'ctx, 'tcx>,
checkpoint: &Checkpoint<'tcx>,
_property: &Property<'tcx>,
) -> VmAliasResult {
let callee = match checkpoint.callee {
Some(c) => c,
None => {
let Some(origin_arg) = checkpoint.args.first() else {
return VmAliasResult::Unknown;
};
let origin_val = vm_state.value_of_operand(origin_arg);
if let Some(origin) = vm_state.resolve_origin(&origin_val) {
if origin.is_mut_ref() || origin.is_shared_ref() {
return VmAliasResult::Proved;
}
if origin.is_owned() {
return VmAliasResult::Proved;
}
}
if let Some(prov) = &origin_val.provenance {
let is_external = vm_state.allocations.iter()
.any(|a| a.id == prov.alloc_id && a.is_external);
if !is_external {
return VmAliasResult::Proved;
}
let has_shared_ref = vm_state.body.local_decls.iter().any(|d| {
matches!(d.ty.kind(), rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not))
});
if has_shared_ref {
return VmAliasResult::Proved;
}
}
if origin_val.provenance.is_none() {
for decl in &vm_state.body.local_decls {
if matches!(decl.ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
return VmAliasResult::Proved;
}
}
}
let tcx = vm_state.tcx;
let caller = checkpoint.caller;
let arg_place = alias_hazard::operand_mir_place(origin_arg)
.map(|p| PlaceKey::from_mir_place(p));
if let Some(mir_place) = arg_place {
let origin_map = collect_local_origins(tcx, caller);
let (root, fields) = alias_hazard::deep_resolve_place(
mir_place.local().map(|l| l.as_usize()).unwrap_or(1),
&origin_map,
);
if !fields.is_empty() {
let resolved = PlaceKey::from_origin(root, fields);
let sfo = alias_hazard::self_field_origin(tcx, caller, &resolved);
if let Some(sfo) = sfo {
if let Some(is_shared) = is_self_field_shared_ref(tcx, caller, &sfo) {
if is_shared {
return VmAliasResult::Proved;
}
}
}
}
}
return VmAliasResult::Unknown;
}
};
let callee_name = vm_state.tcx.def_path_str(callee);
let Some(producer) = alias_hazard::alias_producer(&callee_name) else {
return VmAliasResult::Unknown;
};
match producer {
AliasProducer::View(kind) => {
check_view_alias(vm_state, checkpoint, callee_name, kind)
}
AliasProducer::OwnershipTransfer => {
check_ownership_transfer_alias(vm_state, checkpoint)
}
AliasProducer::ReadMemory => {
check_read_memory_alias(vm_state, checkpoint)
}
}
}
fn check_view_alias<'ctx, 'tcx>(
vm_state: &VmState<'ctx, 'tcx>,
checkpoint: &Checkpoint<'tcx>,
_callee_name: String,
kind: HazardKind,
) -> VmAliasResult {
let Some(origin_arg) = checkpoint.args.first() else {
return VmAliasResult::Unknown;
};
let origin_val = vm_state.value_of_operand(origin_arg);
let tcx = vm_state.tcx;
let caller = checkpoint.caller;
let call_block = checkpoint.block;
let destination = alias_hazard::call_destination(tcx, checkpoint);
let origin_place = alias_hazard::operand_place(origin_arg)
.or_else(|| alias_hazard::operand_mir_place(origin_arg)
.map(|p| PlaceKey::from_mir_place(p)))
.unwrap_or_else(|| {
PlaceKey::from_origin(
adjust_operand_local(origin_arg).unwrap_or(1),
vec![],
)
});
let resolved_origin = resolve_origin_place_mir(tcx, caller, &origin_place);
let mut origins = vec![origin_place.clone()];
if resolved_origin != origin_place {
origins.push(resolved_origin.clone());
}
let mir_place_from_arg = checkpoint.args.first()
.and_then(|a| alias_hazard::operand_mir_place(a));
if let Some(place) = mir_place_from_arg {
if !place.projection.is_empty() && place.local == Local::from_usize(1) {
let field_key = PlaceKey::from_mir_place(place);
if !field_key.fields.is_empty() && !origins.contains(&field_key) {
origins.push(field_key);
}
}
}
if let Some(origin) = vm_state.resolve_origin(&origin_val) {
match (kind, origin.kind) {
(HazardKind::UniqueView, VmOriginKind::MutRef) => return VmAliasResult::Proved,
(HazardKind::SharedView, VmOriginKind::SharedRef) => return VmAliasResult::Proved,
(HazardKind::SharedView, VmOriginKind::RawConstPtr) => return VmAliasResult::Proved,
(HazardKind::UniqueView, VmOriginKind::RawConstPtr) => {
return VmAliasResult::Failed(
"const raw pointer cannot safely create a unique mutable view".into(),
);
}
(HazardKind::UniqueView, VmOriginKind::SharedRef) => {
}
_ => {}
}
if origin.is_owned() {
let check = alias_hazard::alias_proved_for_param_local(
tcx, caller, origin.local.as_usize(), kind,
);
let is_reallocatable = match &origin.kind {
VmOriginKind::Owned(def_id) => {
let def_path = tcx.def_path_str(*def_id);
api_classify::is_std_vec(&def_path)
|| api_classify::is_std_cstring(&def_path)
}
_ => false,
};
if matches!(check, alias_hazard::HazardCheck::Safe(_)) && !is_reallocatable {
return VmAliasResult::Proved;
}
}
}
let view_len_place =
checkpoint.args.get(1).and_then(|a| alias_hazard::operand_place(a));
if let Some(reason) = alias_hazard::local_hazard_violation(
tcx, caller, call_block, destination, &origins, kind, view_len_place,
) {
return VmAliasResult::Failed(reason);
}
let origin_pk = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
if let Some(local_index) = origin_pk {
match alias_hazard::alias_proved_for_param_local(tcx, caller, local_index, kind) {
alias_hazard::HazardCheck::Safe(_) => return VmAliasResult::Proved,
alias_hazard::HazardCheck::Violation(_) => {
}
alias_hazard::HazardCheck::Inconclusive => {}
}
}
let origin_local_place = if origin_place.fields.is_empty() {
PlaceKey::from_origin(
origin_place.local().map(|l| l.as_usize()).unwrap_or(1),
vec![],
)
} else {
origin_place.clone()
};
match alias_hazard::alias_proved_for_param_local_from_origin(
tcx, caller, &origin_local_place, kind,
) {
alias_hazard::HazardCheck::Violation(_) => {} alias_hazard::HazardCheck::Safe(_) => {}
alias_hazard::HazardCheck::Inconclusive => {}
}
let dest_escapes = alias_hazard::destination_flows_to_return(tcx, caller, destination);
if dest_escapes {
let field_origin = alias_hazard::self_field_origin(tcx, caller, &resolved_origin)
.or_else(|| alias_hazard::self_field_origin(tcx, caller, &origin_place))
.or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint));
if let Some(sfo) = field_origin {
if let Some(reason) = alias_hazard::escaped_self_field_violation(tcx, caller, &sfo) {
return VmAliasResult::Failed(reason);
}
return VmAliasResult::Proved;
}
let any_field = alias_hazard::any_struct_field_origin(tcx, caller, &resolved_origin)
.or_else(|| alias_hazard::any_struct_field_origin(tcx, caller, &origin_place));
if let Some(sfo) = any_field {
if let Some(reason) = alias_hazard::escaped_self_field_violation(tcx, caller, &sfo) {
return VmAliasResult::Failed(reason);
}
return VmAliasResult::Proved;
}
if let Some(reason) = alias_hazard::private_fn_callsite_delegation(
tcx, caller, &origin_place, kind,
) {
return VmAliasResult::Failed(reason);
}
if kind == HazardKind::SharedView {
let param_origin = alias_hazard::resolve_param_origin(tcx, caller, &origin_place);
if let Some(local) = param_origin
&& alias_hazard::is_origin_a_reference(tcx, caller, &PlaceKey::from_origin(local, vec![]))
{
return VmAliasResult::Proved;
}
}
}
if !dest_escapes {
return VmAliasResult::Proved;
}
if kind == HazardKind::UniqueView {
if let Some(sfo) = infer_self_field_from_type(tcx, caller, checkpoint)
.or_else(|| find_struct_field_origin_for_param(tcx, caller, checkpoint))
{
if alias_hazard::escaped_self_field_violation(tcx, caller, &sfo).is_none() {
return VmAliasResult::Proved;
}
}
let body = tcx.optimized_mir(caller);
if body.arg_count >= 1 {
let self_ty = body.local_decls[Local::from_usize(1)].ty;
if matches!(self_ty.kind(), rustc_middle::ty::TyKind::Ref(..)) {
return VmAliasResult::Proved;
}
}
return VmAliasResult::Failed(format!(
"returned unique view escapes while the original pointer is not owned by a private self field [origin={:?}]",
origin_place
));
}
VmAliasResult::Proved
}
fn find_struct_field_origin_for_param<'tcx>(
tcx: rustc_middle::ty::TyCtxt<'tcx>,
caller: DefId,
checkpoint: &Checkpoint<'tcx>,
) -> Option<alias_hazard::SelfFieldOrigin> {
let body = tcx.optimized_mir(caller);
let self_ty = body.local_decls[Local::from_usize(1)].ty;
let inner_adt = match self_ty.kind() {
rustc_middle::ty::TyKind::Ref(_, inner, _)
if matches!(inner.kind(), rustc_middle::ty::TyKind::Adt(..)) => *inner,
_ => return None,
};
let (adt_def, _) = crate::analysis::alias::adt_from_ty(inner_adt)?;
let Some(arg0) = checkpoint.args.first() else { return None; };
let arg_place = match arg0 {
Operand::Copy(p) | Operand::Move(p) => p,
_ => return None,
};
if !arg_place.projection.is_empty() && arg_place.local == Local::from_usize(1) {
let fields: Vec<usize> = arg_place.projection.iter()
.filter_map(|p| match p {
ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
_ => None,
})
.collect();
if !fields.is_empty() {
let field_index = fields[0];
let adt = tcx.adt_def(adt_def);
let field = adt.all_fields().nth(field_index)?;
return Some(alias_hazard::SelfFieldOrigin {
struct_def_id: adt_def,
field_index,
field_name: field.name.to_string(),
});
}
}
let arg_local = arg_place.local;
if arg_place.projection.is_empty() && arg_local != Local::from_usize(1) {
for block in body.basic_blocks.iter() {
for stmt in &block.statements {
let StatementKind::Assign(assign) = &stmt.kind else { continue };
let (target, rvalue) = assign.as_ref();
if target.local != arg_local { continue; }
let source = match rvalue {
#[cfg(rapx_rvalue_use_with_retag)]
Rvalue::Use(operand, _) => match operand {
Operand::Copy(p) | Operand::Move(p) => p,
_ => continue,
},
#[cfg(not(rapx_rvalue_use_with_retag))]
Rvalue::Use(operand) => match operand {
Operand::Copy(p) | Operand::Move(p) => p,
_ => continue,
},
Rvalue::CopyForDeref(p) => p,
_ => continue,
};
if source.local != Local::from_usize(1) { continue; }
let fields: Vec<usize> = source.projection.iter()
.filter_map(|p| match p {
ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
_ => None,
})
.collect();
if fields.is_empty() { continue; }
let field_index = fields[0];
let adt = tcx.adt_def(adt_def);
let field = adt.all_fields().nth(field_index)?;
return Some(alias_hazard::SelfFieldOrigin {
struct_def_id: adt_def,
field_index,
field_name: field.name.to_string(),
});
}
}
}
None
}
fn adjust_operand_local<'tcx>(op: &rustc_middle::mir::Operand<'tcx>) -> Option<usize> {
match op {
rustc_middle::mir::Operand::Copy(p) | rustc_middle::mir::Operand::Move(p)
if p.projection.is_empty() =>
{
Some(p.local.as_usize())
}
_ => None,
}
}
fn infer_self_field_from_type<'tcx>(
tcx: rustc_middle::ty::TyCtxt<'tcx>,
caller: DefId,
checkpoint: &Checkpoint<'tcx>,
) -> Option<alias_hazard::SelfFieldOrigin> {
let body = tcx.optimized_mir(caller);
if body.arg_count == 0 {
return None;
}
let self_ty = body.local_decls[Local::from_usize(1)].ty;
let inner = match self_ty.kind() {
rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
_ => return None,
};
let Some((adt_def, _)) = crate::analysis::alias::adt_from_ty(inner) else {
return None;
};
let adt = tcx.adt_def(adt_def);
let mut raw_ptr_fields: Vec<(usize, String)> = Vec::new();
let variant = adt.non_enum_variant();
for (idx, field) in variant.fields.iter().enumerate() {
#[cfg(not(rapx_rustc_ge_198))]
let field_ty = field.ty(tcx, rustc_middle::ty::GenericArgs::identity_for_item(tcx, adt_def));
#[cfg(rapx_rustc_ge_198)]
let field_ty = field.ty(tcx, rustc_middle::ty::GenericArgs::identity_for_item(tcx, adt_def)).skip_norm_wip();
if matches!(field_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)) {
raw_ptr_fields.push((idx, field.name.to_string()));
}
}
if raw_ptr_fields.len() == 1 {
let (field_index, field_name) = raw_ptr_fields.into_iter().next().unwrap();
return Some(alias_hazard::SelfFieldOrigin {
struct_def_id: adt_def,
field_index,
field_name,
});
}
if let Some(arg0) = checkpoint.args.first()
&& let Some(place) = alias_hazard::operand_mir_place(arg0)
{
let fields: Vec<usize> = place.projection.iter()
.filter_map(|p| match p {
ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
_ => None,
})
.collect();
if let Some(&idx) = fields.first() {
if let Some(field) = adt.all_fields().nth(idx) {
return Some(alias_hazard::SelfFieldOrigin {
struct_def_id: adt_def,
field_index: idx,
field_name: field.name.to_string(),
});
}
}
}
None
}
fn is_self_field_shared_ref(
tcx: rustc_middle::ty::TyCtxt<'_>,
caller: DefId,
origin: &alias_hazard::SelfFieldOrigin,
) -> Option<bool> {
let body = tcx.optimized_mir(caller);
let self_ty = body.local_decls[Local::from_usize(1)].ty;
let ((adt_def, args), _) = match self_ty.kind() {
rustc_middle::ty::TyKind::Ref(_, inner, _)
if matches!(inner.kind(), rustc_middle::ty::TyKind::Adt(..)) =>
{
let (did, a) = crate::analysis::alias::adt_from_ty(*inner)?;
((did, a), Some(inner))
}
_ => return None,
};
if adt_def != origin.struct_def_id {
return Some(false);
}
let adt = tcx.adt_def(adt_def);
let field = adt.all_fields().nth(origin.field_index)?;
#[cfg(not(rapx_rustc_ge_198))]
let field_ty = field.ty(tcx, args);
#[cfg(rapx_rustc_ge_198)]
let field_ty = field.ty(tcx, args).skip_norm_wip();
Some(matches!(
field_ty.kind(),
rustc_middle::ty::TyKind::Ref(_, _, rustc_middle::ty::Mutability::Not)
))
}
fn resolve_origin_place_mir(tcx: rustc_middle::ty::TyCtxt<'_>, caller: DefId, place: &PlaceKey) -> PlaceKey {
let Some(local) = place.local() else {
return place.clone();
};
let origins = crate::analysis::alias::collect_local_origins(tcx, caller);
let (root_local, mut root_fields) =
crate::verify::alias_hazard::deep_resolve_place(local.as_usize(), &origins);
if root_local == local.as_usize() && root_fields.is_empty() && !place.fields.is_empty() {
root_fields = place.fields.clone();
}
if root_fields.is_empty() && !place.fields.is_empty() {
return place.clone();
}
PlaceKey::from_origin(root_local, root_fields)
}
fn check_ownership_transfer_alias<'ctx, 'tcx>(
vm_state: &VmState<'ctx, 'tcx>,
checkpoint: &Checkpoint<'tcx>,
) -> VmAliasResult {
let Some(origin_arg) = checkpoint.args.first() else {
return VmAliasResult::Unknown;
};
let tcx = vm_state.tcx;
let caller = checkpoint.caller;
let call_block = checkpoint.block;
let destination = alias_hazard::call_destination(tcx, checkpoint);
let origin_place = alias_hazard::operand_place(origin_arg);
let Some(origin_place) = origin_place else {
return VmAliasResult::Unknown;
};
if let Some(reason) = alias_hazard::ownership_transfer_violation(
tcx, caller, call_block, destination, &origin_place,
) {
return VmAliasResult::Failed(reason);
}
VmAliasResult::Proved
}
fn check_read_memory_alias<'ctx, 'tcx>(
vm_state: &VmState<'ctx, 'tcx>,
checkpoint: &Checkpoint<'tcx>,
) -> VmAliasResult {
let Some(origin_arg) = checkpoint.args.first() else {
return VmAliasResult::Unknown;
};
let origin_val = vm_state.value_of_operand(origin_arg);
if let rustc_middle::ty::TyKind::RawPtr(pointee, _) = origin_val.ty.kind() {
let tcx = vm_state.tcx;
let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, checkpoint.caller);
if tcx.type_is_copy_modulo_regions(typing_env, *pointee) {
return VmAliasResult::Proved;
}
}
let tcx = vm_state.tcx;
let destination = alias_hazard::call_destination(tcx, checkpoint);
if !alias_hazard::destination_flows_to_return(tcx, checkpoint.caller, destination) {
return VmAliasResult::Proved;
}
VmAliasResult::Failed(
"read API value escapes while the source pointer persists — structural alias hazard"
.into(),
)
}