use rustc_hir::def_id::DefId;
use rustc_middle::mir::Operand;
use rustc_middle::ty::{GenericArgKind, Ty, TyCtxt, TyKind};
use super::{CallDependencySummary, CallEffect, CallEffectSummary};
use crate::helpers::api_classify;
use crate::helpers::mir_utils::{
type_layout, destination_stride, pointee_alignment,
nonnull_inner_ty, slice_element_size, vec_element_size,
};
pub struct EffCtx<'a, 'tcx> {
pub tcx: TyCtxt<'tcx>,
pub caller: DefId,
pub callee: Option<DefId>,
pub name: &'a str,
pub func: &'a Operand<'tcx>,
pub dest: Option<rustc_middle::mir::Local>,
}
struct Entry {
matches: fn(&str) -> bool,
dep_on: &'static [usize],
dep_on_all: bool,
writes: &'static [usize],
effects: fn(&EffCtx<'_, '_>) -> Vec<CallEffect>,
}
macro_rules! none { () => { &[] } }
macro_rules! dep0 { () => { &[0usize] } }
macro_rules! dep01 { () => { &[0usize, 1] } }
macro_rules! E {
($m:expr, $d:expr, $all:expr, $w:expr, $e:ident) => {
Entry { matches: $m, dep_on: $d, dep_on_all: $all, writes: $w, effects: $e }
};
}
const ALL: &[usize] = &[];
static REGISTRY: &[Entry] = &[
E!(mem_forget_capacity, dep0!(), false, none!(), eff_forget),
E!(transmute, dep0!(), false, none!(), eff_none),
E!(api_classify::is_maybe_uninit_uninit,none!(), false, none!(), eff_none),
E!(api_classify::is_maybe_uninit_assume_init,dep0!(), false, none!(), eff_none),
E!(api_classify::is_numeric_arith, ALL, true, none!(), eff_none),
E!(saturating_sub, ALL, true, none!(), eff_none),
E!(api_classify::is_offset_from_unsigned, dep01!(), false, none!(), eff_offset_from_unsigned),
E!(api_classify::is_option_unwrap, dep0!(), false, none!(), eff_alias_arg0),
E!(from_trait_call, dep0!(), false, none!(), eff_from_trait),
E!(nonnull_from, dep0!(), false, none!(), eff_alias_ptr),
E!(nonnull_new_unchecked, dep0!(), false, none!(), eff_none),
E!(nonnull_new, dep0!(), false, none!(), eff_alias_ptr),
E!(nonnull_as_ref, dep0!(), false, none!(), eff_alias_ptr),
E!(nonnull_as_mut, dep0!(), false, none!(), eff_alias_ptr),
E!(api_classify::is_as_ptr, dep0!(), false, none!(), eff_alias_ptr),
E!(api_classify::is_as_ptr_range, dep0!(), false, none!(), eff_alias_arg0),
E!(api_classify::is_as_mut_ptr_range, dep0!(), false, none!(), eff_alias_arg0),
E!(|n| api_classify::is_pointer_add(n) && !api_classify::is_byte_ptr_arith(n), dep01!(), false, none!(), eff_ptr_add),
E!(|n| api_classify::is_pointer_sub(n) && !api_classify::is_byte_ptr_arith(n), dep01!(), false, none!(), eff_ptr_sub),
E!(api_classify::is_byte_ptr_arith, dep01!(), false, none!(), eff_ptr_add),
E!(api_classify::is_byte_ptr_arith, dep01!(), false, none!(), eff_ptr_sub),
E!(ptr_read, dep0!(), false, none!(), eff_read_mem),
E!(api_classify::is_ptr_write, none!(), false, dep0!(), eff_write_mem),
E!(api_classify::is_maybe_uninit_write, none!(), false, dep0!(), eff_write_mem),
E!(api_classify::is_len, dep0!(), false, none!(), eff_len),
E!(is_empty, dep0!(), false, none!(), eff_is_empty),
E!(cmp_min, ALL, true, none!(), eff_cmp_min),
E!(is_slice_get_unchecked, dep0!(), false, none!(), eff_alias_ptr),
E!(api_classify::is_ownership_reconstruction, dep0!(), false, none!(), eff_ownership_recon),
E!(slice_range, dep01!(), false, none!(), eff_bounded_range),
E!(align_to_offsets, ALL, true, none!(), eff_lcm_split),
E!(split_at, dep01!(), false, none!(), eff_split_at),
E!(api_classify::is_from_raw_parts, dep01!(), false, none!(), eff_from_raw_parts),
E!(api_classify::is_vec_alloc_constructor, dep01!(), false, none!(), eff_new_allocation),
E!(api_classify::is_vec_from_box, dep0!(), false, none!(), eff_vec_from_box),
E!(api_classify::is_vec_with_capacity, dep0!(), false, none!(), eff_new_allocation_from_cap),
E!(api_classify::is_into_boxed_slice, dep0!(), false, none!(), eff_box_from_vec),
E!(api_classify::is_layout_constant, none!(), false, none!(), eff_layout_const),
E!(api_classify::is_cstr_from_ptr, dep0!(), false, none!(), eff_alias_arg0),
E!(api_classify::is_cstr_from_bytes_with_nul_unchecked, dep0!(), false, none!(), eff_alias_arg0),
E!(api_classify::is_vec_push, none!(), false, dep0!(), eff_write_mem),
];
pub fn lookup_dependency(
callee: Option<DefId>,
name: &str,
arg_count: usize,
) -> Option<CallDependencySummary> {
for e in REGISTRY {
if (e.matches)(name) {
let args = if e.dep_on_all { (0..arg_count).collect() } else { e.dep_on.to_vec() };
return Some(CallDependencySummary {
callee,
name: name.to_string(),
return_depends_on_args: args,
may_write_args: e.writes.to_vec(),
unsupported: false,
});
}
}
None
}
pub fn lookup_effect<'tcx>(
tcx: TyCtxt<'tcx>,
caller: DefId,
callee: Option<DefId>,
name: &str,
func: &Operand<'tcx>,
destination: rustc_middle::mir::Local,
) -> Option<CallEffectSummary> {
let dest = Some(destination);
for e in REGISTRY {
if (e.matches)(name) {
let ctx = EffCtx { tcx, caller, callee, name, func, dest };
return Some(CallEffectSummary {
callee,
name: name.to_string(),
destination: dest,
effects: (e.effects)(&ctx),
unsupported: false,
});
}
}
None
}
fn eff_none(_: &EffCtx<'_, '_>) -> Vec<CallEffect> { Vec::new() }
fn eff_alias_ptr(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let mut eff = vec![
CallEffect::ReturnPointerFromArg { arg: 0 },
CallEffect::ReturnNonZero,
];
if let Some((a, n)) = pointee_alignment(ctx.tcx, ctx.caller, ctx.dest) {
eff.push(CallEffect::ReturnAligned { align: a, ty_name: n });
}
eff
}
fn eff_alias_nonnull(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let mut eff = vec![
CallEffect::ReturnPointerFromArg { arg: 0 },
CallEffect::ReturnNonZero,
];
if let Some((a, n)) = nonnull_pointee_alignment(ctx.tcx, ctx.caller, ctx.dest) {
eff.push(CallEffect::ReturnAligned { align: a, ty_name: n });
}
eff
}
fn eff_from_trait(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
if is_nonnull_dest(ctx.tcx, ctx.caller, ctx.dest) {
eff_alias_nonnull(ctx)
} else {
Vec::new()
}
}
fn eff_alias_arg0(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnAliasArg { arg: 0 }]
}
fn eff_ptr_add(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let stride = if api_classify::is_byte_ptr_arith(ctx.name) {
Some(1)
} else {
destination_stride(ctx.tcx, ctx.caller, ctx.dest)
};
vec![CallEffect::ReturnPointerAdd { base_arg: 0, offset_arg: 1, stride }]
}
fn eff_ptr_sub(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let stride = if api_classify::is_byte_ptr_arith(ctx.name) {
Some(1)
} else {
destination_stride(ctx.tcx, ctx.caller, ctx.dest)
};
vec![CallEffect::ReturnPointerSub { base_arg: 0, offset_arg: 1, stride }]
}
fn eff_offset_from_unsigned(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnOffsetFromUnsigned { self_arg: 0, origin_arg: 1 }]
}
fn eff_read_mem(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReadMemory { arg: 0 }]
}
fn eff_write_mem(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::WriteMemory { pointer_arg: 0 }]
}
fn eff_len(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnLengthOfArg { arg: 0 }]
}
fn eff_is_empty(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnIsEmptyOfArg { arg: 0 }]
}
fn eff_cmp_min(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnMin { lhs_arg: 0, rhs_arg: 1 }]
}
fn eff_ownership_recon(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![
CallEffect::ReturnAliasArg { arg: 0 },
CallEffect::ReturnNonZero,
CallEffect::OwnsInitMemory { arg: 0 },
]
}
fn eff_bounded_range(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnBoundedRange { bounds_arg: 1 }]
}
fn eff_lcm_split(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnLcmSplit { receiver_arg: 0 }]
}
fn eff_split_at(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![
CallEffect::ReturnAliasArg { arg: 0 },
CallEffect::ReturnTupleFieldLength { field: 0, from_arg: 1 },
]
}
fn eff_from_raw_parts(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let elem = slice_element_size(ctx.tcx, ctx.caller, ctx.func, ctx.dest);
let mut eff = vec![
CallEffect::ReturnAliasArg { arg: 0 },
CallEffect::ReturnFreshAllocation {
pointer_arg: 0,
size_arg: 1,
elem_size: elem,
},
CallEffect::ReturnNonZero,
];
if let Some((a, n)) = pointee_alignment(ctx.tcx, ctx.caller, ctx.dest) {
eff.push(CallEffect::ReturnAligned { align: a, ty_name: n });
}
eff
}
fn eff_new_allocation(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let elem = vec_element_size(ctx.tcx, ctx.caller, ctx.dest);
vec![
CallEffect::ReturnNewAllocation {
size_arg: 1,
elem_size: elem,
},
]
}
fn eff_new_allocation_from_cap(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
let elem = vec_element_size(ctx.tcx, ctx.caller, ctx.dest);
vec![
CallEffect::ReturnNewAllocation {
size_arg: 0,
elem_size: elem,
},
]
}
fn eff_vec_from_box(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![
CallEffect::ReturnNewAllocationFromBox { box_arg: 0 },
]
}
fn eff_forget(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![
CallEffect::CleanSliceDataLinks { arg: 0 },
]
}
fn eff_layout_const(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
layout_constant_effect(ctx.tcx, ctx.caller, ctx.func, ctx.name)
.into_iter()
.collect()
}
fn mem_forget_capacity(n: &str) -> bool { n.ends_with("mem::forget") || n.ends_with("::capacity") }
fn transmute(n: &str) -> bool { n.contains("::transmute") || n.contains("intrinsics::transmute") }
fn slice_range(n: &str) -> bool { let b = n.split('<').next().unwrap_or(n); b.ends_with("slice::range") || b.contains("slice::index::range") }
fn align_to_offsets(n: &str) -> bool { n.contains("::align_to_offsets") }
fn from_trait_call(n: &str) -> bool { n == "std::convert::From::from" || n == "core::convert::From::from" }
fn nonnull_from(n: &str) -> bool { n.ends_with("::from") && api_classify::is_nonnull_api(n) }
fn nonnull_new_unchecked(n: &str) -> bool { n.ends_with("::new_unchecked") && api_classify::is_nonnull_api(n) }
fn nonnull_new(n: &str) -> bool { n.ends_with("::new") && api_classify::is_nonnull_api(n) && !n.ends_with("::new_unchecked") }
fn nonnull_as_ref(n: &str) -> bool { n.ends_with("::as_ref") && api_classify::is_nonnull_api(n) }
fn nonnull_as_mut(n: &str) -> bool { n.ends_with("::as_mut") && api_classify::is_nonnull_api(n) }
fn ptr_read(n: &str) -> bool { n.ends_with("::read") && n.contains("::ptr::") }
fn is_empty(n: &str) -> bool { n.ends_with("::is_empty") }
fn cmp_min(n: &str) -> bool { (n.contains("::cmp::min") || n.starts_with("core::cmp::min")) && !n.contains("min_by") }
fn saturating_sub(n: &str) -> bool { n.contains("::saturating_sub") }
fn split_at(n: &str) -> bool { n.contains("::split_at") }
fn is_slice_get_unchecked(n: &str) -> bool {
(n.contains("::get_unchecked") || n.contains("::get_unchecked_mut"))
&& (n.contains("::SliceIndex")
|| n.contains("::impl [T]>::get_unchecked")
|| n.contains("::mut_ptr::get_unchecked")
|| n.contains("::const_ptr::get_unchecked"))
}
fn nonnull_pointee_alignment<'tcx>(
tcx: TyCtxt<'tcx>, caller: DefId, dest: Option<rustc_middle::mir::Local>,
) -> Option<(u64, String)> {
let d = dest?;
let ty = tcx.optimized_mir(caller).local_decls[d].ty;
let pointee = nonnull_inner_ty(tcx, ty)?;
type_layout(tcx, caller, pointee).map(|(a, _)| (a, format!("{pointee:?}")))
}
fn is_nonnull_dest(tcx: TyCtxt<'_>, caller: DefId, dest: Option<rustc_middle::mir::Local>) -> bool {
let Some(d) = dest else { return false };
nonnull_inner_ty(tcx, tcx.optimized_mir(caller).local_decls[d].ty).is_some()
}
fn layout_call_ty<'tcx>(func: &Operand<'tcx>) -> Option<Ty<'tcx>> {
let Operand::Constant(c) = func else { return None };
let TyKind::FnDef(_, args) = c.const_.ty().kind() else { return None };
args.iter().find_map(|a| {
#[cfg(rapx_rustc_ge_199)] let a = a.skip_binder();
match a.kind() { GenericArgKind::Type(t) => Some(t), _ => None }
})
}
fn layout_constant_effect<'tcx>(
tcx: TyCtxt<'tcx>, caller: DefId, func: &Operand<'tcx>, name: &str,
) -> Option<CallEffect> {
let ty = layout_call_ty(func)?;
let (align, size) = type_layout(tcx, caller, ty)?;
if name.contains("align_of") {
Some(CallEffect::ReturnConst { value: align, label: format!("align_of::<{ty:?}>()") })
} else if name.contains("size_of") {
Some(CallEffect::ReturnConst { value: size, label: format!("size_of::<{ty:?}>()") })
} else {
None
}
}
fn eff_box_from_vec(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
vec![CallEffect::ReturnBoxFromVec { arg: 0 }]
}