#[cfg(not(rapx_ge_100))]
use rustc_hir::LangItem;
#[cfg(rapx_ge_100)]
use rustc_hir::attrs::lang_items::LangItem;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{ClauseKind, GenericArgKind, ParamTy, Ty, TyCtxt, TyKind};
use z3::Solver;
use crate::compat::FxHashMap;
use crate::helpers::mir_scan::{Checkpoint, has_atomic_call, has_raw_ptr_write};
use crate::verify::vm::state::VmState;
use crate::verify::{
contract::{Property, PropertyArg, PropertyKind},
report::CheckResult,
target::get_struct_invariants_from_annotation,
};
use super::PropertyChecker;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Contains {
Yes,
No,
Maybe,
}
impl Contains {
fn join(self, other: Contains) -> Contains {
match (self, other) {
(Contains::Yes, _) | (_, Contains::Yes) => Contains::Yes,
(Contains::Maybe, _) | (_, Contains::Maybe) => Contains::Maybe,
(Contains::No, Contains::No) => Contains::No,
}
}
}
impl PropertyChecker {
pub(super) fn check_contain_no_type<'ctx, 'tcx>(
&self,
vm_state: &VmState<'ctx, 'tcx>,
_solver: &Solver<'ctx>,
checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
) -> CheckResult {
let Some(ty) = property.args().first().and_then(|a| match a {
PropertyArg::Ty(ty) => Some(*ty),
_ => None,
}) else {
return CheckResult::Unknown;
};
let negatives: Vec<String> = property.args()[1..]
.iter()
.filter_map(|a| match a {
PropertyArg::Ident(name) => Some(name.clone()),
_ => None,
})
.collect();
if negatives.is_empty() {
return CheckResult::Unknown;
}
contain_no_type_check(vm_state.tcx, ty, &negatives, checkpoint.caller, false)
}
pub(super) fn check_no_raw_ptr<'ctx, 'tcx>(
&self,
vm_state: &VmState<'ctx, 'tcx>,
_solver: &Solver<'ctx>,
checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
) -> CheckResult {
let Some(ty) = property.args().first().and_then(|a| match a {
PropertyArg::Ty(ty) => Some(*ty),
_ => None,
}) else {
return CheckResult::Unknown;
};
no_raw_ptr_check(vm_state.tcx, ty, checkpoint.caller, false)
}
pub(super) fn check_no_internal_mut<'ctx, 'tcx>(
&self,
vm_state: &VmState<'ctx, 'tcx>,
_solver: &Solver<'ctx>,
_checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
) -> CheckResult {
let Some(ty) = property.args().first().and_then(|a| match a {
PropertyArg::Ty(ty) => Some(*ty),
_ => None,
}) else {
return CheckResult::Unknown;
};
no_internal_mut_check(vm_state.tcx, ty)
}
pub(super) fn check_uni_internal_mut<'ctx, 'tcx>(
&self,
vm_state: &VmState<'ctx, 'tcx>,
_solver: &Solver<'ctx>,
_checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
) -> CheckResult {
let Some(ty) = property.args().first().and_then(|a| match a {
PropertyArg::Ty(ty) => Some(*ty),
_ => None,
}) else {
return CheckResult::Unknown;
};
uni_internal_mut_check(vm_state.tcx, ty)
}
pub(super) fn check_atomic_update<'ctx, 'tcx>(
&self,
vm_state: &VmState<'ctx, 'tcx>,
_solver: &Solver<'ctx>,
checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
) -> CheckResult {
let Some(ty) = property.args().first().and_then(|a| match a {
PropertyArg::Ty(ty) => Some(*ty),
_ => None,
}) else {
return CheckResult::Unknown;
};
atomic_update_check(vm_state.tcx, ty, checkpoint.caller, false)
}
pub(super) fn check_ref_send<'ctx, 'tcx>(
&self,
vm_state: &VmState<'ctx, 'tcx>,
_solver: &Solver<'ctx>,
checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
) -> CheckResult {
let Some(ty) = property.args().first().and_then(|a| match a {
PropertyArg::Ty(ty) => Some(*ty),
_ => None,
}) else {
return CheckResult::Unknown;
};
ref_send_check(vm_state.tcx, ty, checkpoint.caller, true)
}
}
pub(crate) fn contain_no_type_check<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
negatives: &[String],
impl_def_id: DefId,
is_sync: bool,
) -> CheckResult {
let mut defs: Vec<DefId> = Vec::new();
for name in negatives {
defs.extend_from_slice(crate::def_id::negative_type_defs(name));
}
match type_structurally_contains(tcx, ty, &defs, impl_def_id, is_sync) {
Contains::Yes => CheckResult::Failed,
Contains::Maybe => CheckResult::Unknown,
Contains::No => CheckResult::Proved,
}
}
pub(crate) fn no_raw_ptr_check<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
impl_def_id: DefId,
is_sync: bool,
) -> CheckResult {
match find_raw_ptr(tcx, ty, impl_def_id, is_sync) {
Contains::Yes => CheckResult::Failed,
Contains::Maybe => CheckResult::Unknown,
Contains::No => CheckResult::Proved,
}
}
pub(crate) fn no_internal_mut_check<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> CheckResult {
if has_raw_ptr_writes(tcx, ty) || has_atomic_ptr_updates(tcx, ty) {
CheckResult::Failed
} else {
CheckResult::Proved
}
}
pub(crate) fn uni_internal_mut_check<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> CheckResult {
if (has_raw_ptr_writes(tcx, ty) || has_atomic_ptr_updates(tcx, ty))
&& !type_implements_clone(tcx, ty)
{
CheckResult::Proved
} else {
CheckResult::Failed
}
}
pub(crate) fn atomic_update_check<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
impl_def_id: DefId,
is_sync: bool,
) -> CheckResult {
match find_unsynchronized_mutation(tcx, ty, impl_def_id, is_sync) {
Contains::No => CheckResult::Proved,
Contains::Maybe => CheckResult::Unknown,
Contains::Yes => {
if has_atomic_ptr_updates(tcx, ty) {
CheckResult::Proved
} else {
CheckResult::Failed
}
}
}
}
pub(crate) fn field_invariant_check<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
kind: PropertyKind,
field: Option<&str>,
invariant_results: &FxHashMap<DefId, CheckResult>,
) -> CheckResult {
let TyKind::Adt(adt_def, _) = ty.kind() else {
return CheckResult::Failed;
};
let adt_def_id = adt_def.did();
let invariants = get_struct_invariants_from_annotation(tcx, adt_def_id, adt_def_id);
let matched = invariants.iter().any(|p| {
p.kind() == Some(kind)
&& field.map_or(true, |f| {
p.args()
.first()
.and_then(|a| {
crate::verify::contract::place::field_name_from_arg(tcx, adt_def_id, a)
})
.as_deref()
== Some(f)
})
});
if !matched {
return CheckResult::Failed;
}
if let Some(result) = invariant_results.get(&adt_def_id) {
if *result != CheckResult::Proved {
return CheckResult::Failed;
}
}
CheckResult::Proved
}
pub(crate) fn ref_send_check<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
impl_def_id: DefId,
is_sync: bool,
) -> CheckResult {
match find_unsynchronized_mutation(tcx, ty, impl_def_id, is_sync) {
Contains::Yes => CheckResult::Failed,
Contains::Maybe => CheckResult::Unknown,
Contains::No => CheckResult::Proved,
}
}
fn type_implements_clone<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
let Some(clone_did) = tcx.lang_items().clone_trait() else {
return false;
};
tcx.all_impls(clone_did).any(|impl_did| {
tcx.impl_trait_ref(impl_did).skip_binder().self_ty() == ty
})
}
fn param_bound_is_satisfied(
tcx: TyCtxt<'_>,
impl_def_id: DefId,
param_ty: ParamTy,
is_sync: bool,
) -> bool {
let trait_did = if is_sync {
tcx.get_diagnostic_item(rustc_span::sym::Sync)
} else {
tcx.get_diagnostic_item(rustc_span::sym::Send)
};
let Some(trait_did) = trait_did else {
return false;
};
let predicates = crate::compat::predicates_of(tcx, impl_def_id);
#[cfg(not(rapx_ge_100))]
let iter = predicates.predicates.iter();
#[cfg(rapx_ge_100)]
let iter = predicates.clauses.iter();
for (pred, _) in iter {
if let ClauseKind::Trait(trait_ref) = pred.kind().skip_binder() {
if trait_ref.def_id() == trait_did {
if let TyKind::Param(p) = trait_ref.self_ty().kind() {
if p.index == param_ty.index {
return true;
}
}
}
}
}
false
}
fn has_raw_ptr_writes<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
let TyKind::Adt(adt_def, _) = ty.kind() else {
return false;
};
let adt_def_id = adt_def.did();
tcx.inherent_impls(adt_def_id).iter().any(|impl_id| {
tcx.associated_item_def_ids(*impl_id).iter().any(|item| {
matches!(tcx.def_kind(*item), DefKind::Fn | DefKind::AssocFn)
&& has_raw_ptr_write(tcx, *item)
})
})
}
fn has_atomic_ptr_updates<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
let TyKind::Adt(adt_def, _) = ty.kind() else {
return false;
};
let adt_def_id = adt_def.did();
tcx.inherent_impls(adt_def_id).iter().any(|impl_id| {
tcx.associated_item_def_ids(*impl_id).iter().any(|item| {
matches!(tcx.def_kind(*item), DefKind::Fn | DefKind::AssocFn)
&& has_atomic_call(tcx, *item)
})
})
}
fn find_raw_ptr<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
impl_def_id: DefId,
is_sync: bool,
) -> Contains {
match ty.kind() {
TyKind::RawPtr(..) => Contains::Yes,
TyKind::Pat(inner, _) => find_raw_ptr(tcx, *inner, impl_def_id, is_sync),
TyKind::Adt(adt_def, substs) => {
let mut result = Contains::No;
for field in adt_def.all_fields() {
let field_ty = crate::helpers::mir_utils::field_ty(tcx, field, substs);
result = result.join(find_raw_ptr(tcx, field_ty, impl_def_id, is_sync));
if result == Contains::Yes {
return Contains::Yes;
}
}
for subst in substs.iter() {
if let GenericArgKind::Type(subst_ty) = subst.kind() {
result = result.join(find_raw_ptr(tcx, subst_ty, impl_def_id, is_sync));
if result == Contains::Yes {
return Contains::Yes;
}
}
}
result
}
TyKind::Ref(_, inner, _) | TyKind::Slice(inner) | TyKind::Array(inner, _) => {
find_raw_ptr(tcx, *inner, impl_def_id, is_sync)
}
TyKind::Tuple(tys) => tys.iter().fold(Contains::No, |acc, t| {
acc.join(find_raw_ptr(tcx, t, impl_def_id, is_sync))
}),
TyKind::Param(param_ty) => {
if param_bound_is_satisfied(tcx, impl_def_id, *param_ty, is_sync) {
Contains::No
} else {
Contains::Maybe
}
}
_ => Contains::No,
}
}
fn type_structurally_contains<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
negative_defs: &[DefId],
impl_def_id: DefId,
is_sync: bool,
) -> Contains {
match ty.kind() {
TyKind::Adt(adt_def, substs) => {
if negative_defs.contains(&adt_def.did()) {
return Contains::Yes;
}
if crate::def_id::sync_primitive_types().contains(&adt_def.did()) {
return Contains::No;
}
let mut result = Contains::No;
for field in adt_def.all_fields() {
let field_ty = crate::helpers::mir_utils::field_ty(tcx, field, substs);
result = result.join(type_structurally_contains(
tcx,
field_ty,
negative_defs,
impl_def_id,
is_sync,
));
if result == Contains::Yes {
return Contains::Yes;
}
}
for subst in substs.iter() {
if let GenericArgKind::Type(subst_ty) = subst.kind() {
result = result.join(type_structurally_contains(
tcx,
subst_ty,
negative_defs,
impl_def_id,
is_sync,
));
if result == Contains::Yes {
return Contains::Yes;
}
}
}
result
}
TyKind::Ref(_, inner, _) | TyKind::Slice(inner) | TyKind::Array(inner, _) => {
type_structurally_contains(tcx, *inner, negative_defs, impl_def_id, is_sync)
}
TyKind::Tuple(tys) => tys.iter().fold(Contains::No, |acc, t| {
acc.join(type_structurally_contains(
tcx,
t,
negative_defs,
impl_def_id,
is_sync,
))
}),
TyKind::Param(param_ty) => {
if param_bound_is_satisfied(tcx, impl_def_id, *param_ty, is_sync) {
Contains::No
} else {
Contains::Maybe
}
}
_ => Contains::No,
}
}
fn find_unsynchronized_mutation<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
impl_def_id: DefId,
is_sync: bool,
) -> Contains {
match ty.kind() {
TyKind::RawPtr(..) => Contains::Yes,
TyKind::Pat(inner, _) => find_unsynchronized_mutation(tcx, *inner, impl_def_id, is_sync),
TyKind::Adt(adt_def, substs) => {
let did = adt_def.did();
if crate::def_id::sync_primitive_types().contains(&did) {
return Contains::No;
}
if tcx.is_lang_item(did, LangItem::UnsafeCell) {
return Contains::Yes;
}
let mut result = Contains::No;
for field in adt_def.all_fields() {
let field_ty = crate::helpers::mir_utils::field_ty(tcx, field, substs);
result = result.join(find_unsynchronized_mutation(
tcx,
field_ty,
impl_def_id,
is_sync,
));
if result == Contains::Yes {
return Contains::Yes;
}
}
for subst in substs.iter() {
if let GenericArgKind::Type(subst_ty) = subst.kind() {
result = result.join(find_unsynchronized_mutation(
tcx,
subst_ty,
impl_def_id,
is_sync,
));
if result == Contains::Yes {
return Contains::Yes;
}
}
}
result
}
TyKind::Ref(_, inner, _) | TyKind::Slice(inner) | TyKind::Array(inner, _) => {
find_unsynchronized_mutation(tcx, *inner, impl_def_id, is_sync)
}
TyKind::Tuple(tys) => tys.iter().fold(Contains::No, |acc, t| {
acc.join(find_unsynchronized_mutation(tcx, t, impl_def_id, is_sync))
}),
TyKind::Param(param_ty) => {
if param_bound_is_satisfied(tcx, impl_def_id, *param_ty, is_sync) {
Contains::No
} else {
Contains::Maybe
}
}
_ => Contains::No,
}
}