use rustc_hir::{Safety, def_id::DefId};
#[cfg(all(rapx_has_attr_ir, not(rapx_box_deref_transmute)))]
use rustc_attr_ir::LangItem;
#[cfg(all(not(rapx_has_attr_ir), not(rapx_ge_100), not(rapx_box_deref_transmute)))]
use rustc_hir::LangItem;
#[cfg(all(not(rapx_has_attr_ir), rapx_ge_100, not(rapx_box_deref_transmute)))]
use rustc_hir::attrs::lang_items::LangItem;
use rustc_middle::{
mir::{
BasicBlock, Body, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind,
TerminatorKind,
},
ty::{self, Ty, TyCtxt},
};
#[cfg(not(rapx_box_deref_transmute))]
use rustc_middle::ty::TyKind;
#[cfg(rapx_box_deref_transmute)]
use rustc_middle::mir::CastKind;
use std::collections::HashSet;
use crate::helpers::mir_scan::{get_fn_safety, place_has_raw_deref};
use crate::helpers::mir_utils::{dep_callee_def_id, pointee_ty, resolve_callee_impl};
use crate::helpers::name::get_cleaned_def_path_name;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct CheckpointLocation {
pub caller: DefId,
pub block: BasicBlock,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum CheckpointKind {
UnsafeCall,
RawPtrDeref,
StaticMutAccess,
}
#[derive(Clone, Debug)]
pub struct Checkpoint<'tcx> {
pub caller: DefId,
pub callee: Option<DefId>,
pub block: BasicBlock,
pub args: Vec<Operand<'tcx>>,
pub kind: CheckpointKind,
pub destination: Option<Local>,
pub is_mut_ref: bool,
pub statement_index: usize,
}
impl<'tcx> Checkpoint<'tcx> {
pub fn location(&self) -> CheckpointLocation {
CheckpointLocation {
caller: self.caller,
block: self.block,
}
}
pub fn callee_name(&self, tcx: TyCtxt<'tcx>) -> String {
match self.callee {
Some(def_id) => get_cleaned_def_path_name(tcx, def_id),
None => match self.kind {
CheckpointKind::RawPtrDeref => "raw-ptr-deref".to_string(),
CheckpointKind::StaticMutAccess => "static-mut-access".to_string(),
CheckpointKind::UnsafeCall => "unknown-callee".to_string(),
},
}
}
}
pub fn has_raw_ptr_write(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
if !tcx.is_mir_available(def_id) {
return false;
}
let body = tcx.optimized_mir(def_id);
body.basic_blocks.iter().any(|bb| {
bb.statements.iter().any(|stmt| {
if let StatementKind::Assign(assign) = &stmt.kind {
let (lhs, _) = &**assign;
place_has_raw_deref(body, lhs)
} else {
false
}
})
})
}
pub fn has_atomic_call(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
if !tcx.is_mir_available(def_id) {
return false;
}
let body = tcx.optimized_mir(def_id);
body.basic_blocks.iter().any(|bb| {
if let TerminatorKind::Call { func, .. } = &bb.terminator().kind {
let Some(callee) = dep_callee_def_id(func) else {
return false;
};
if tcx
.intrinsic(callee)
.is_some_and(|i| i.name.as_str().starts_with("atomic_"))
{
return true;
}
tcx.def_path_str(callee).contains("Atomic")
} else {
false
}
})
}
pub fn collect_unsafe_callsites<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Vec<Checkpoint<'tcx>> {
let mut checkpoints = Vec::new();
if !tcx.is_mir_available(def_id) {
return checkpoints;
}
let body = tcx.optimized_mir(def_id);
for (bb, data) in body.basic_blocks.iter_enumerated() {
let TerminatorKind::Call {
func,
args,
destination: call_dest,
..
} = &data.terminator().kind
else {
continue;
};
let Operand::Constant(func_constant) = func else {
continue;
};
let ty::FnDef(callee_def_id, callee_args) = func_constant.const_.ty().kind() else {
continue;
};
#[cfg(rapx_ge_99)]
let callee_args = callee_args.skip_binder();
if get_fn_safety(tcx, *callee_def_id) != Safety::Unsafe {
continue;
}
let resolved_callee = resolve_callee_impl(
tcx,
def_id,
*callee_def_id,
callee_args,
)
.unwrap_or(*callee_def_id);
checkpoints.push(Checkpoint {
caller: def_id,
callee: Some(resolved_callee),
block: bb,
args: args.iter().map(|arg| arg.node.clone()).collect(),
kind: CheckpointKind::UnsafeCall,
destination: Some(call_dest.local),
is_mut_ref: false,
statement_index: 0,
});
}
checkpoints
}
#[derive(Clone, Debug)]
pub struct RawPtrDerefInfo<'tcx> {
pub block: BasicBlock,
pub ptr_operand: Operand<'tcx>,
pub pointee_ty: Ty<'tcx>,
pub is_read: bool,
pub is_ptr2ref: bool,
pub is_mut_ref: bool,
pub destination: Local,
pub statement_index: usize,
}
fn box_deref_transmute_locals<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> HashSet<Local> {
let mut result = HashSet::new();
let mut changed = true;
while changed {
changed = false;
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (target, rhs) = &**assign;
if !target.projection.is_empty() {
continue;
}
let from_box = if is_box_deref_cast(tcx, body, rhs) {
true
} else if let Rvalue::Use(Operand::Copy(p) | Operand::Move(p), ..)
| Rvalue::Cast(_, Operand::Copy(p) | Operand::Move(p), _) = rhs
{
p.projection.is_empty() && result.contains(&p.local)
} else {
false
};
if from_box && result.insert(target.local) {
changed = true;
}
}
}
}
result
}
fn is_box_deref_cast(tcx: TyCtxt<'_>, body: &Body<'_>, rvalue: &Rvalue<'_>) -> bool {
#[cfg(rapx_box_deref_transmute)]
{
let _ = (tcx, body);
matches!(rvalue, Rvalue::Cast(CastKind::BoxDerefTransmute, _, _))
}
#[cfg(not(rapx_box_deref_transmute))]
{
let Rvalue::Cast(_, Operand::Copy(p) | Operand::Move(p), _) = rvalue else {
return false;
};
if p.projection.is_empty() {
return false;
}
let base_ty = body.local_decls[p.local].ty;
matches!(
base_ty.kind(),
TyKind::Adt(adt, _) if tcx.is_lang_item(adt.did(), LangItem::OwnedBox)
)
}
}
pub fn collect_raw_ptr_deref_info<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> Vec<RawPtrDerefInfo<'tcx>> {
let mut infos = Vec::new();
if !tcx.is_mir_available(def_id) {
return infos;
}
let body = tcx.optimized_mir(def_id);
let box_derefs = box_deref_transmute_locals(tcx, body);
let fn_span = tcx.def_span(def_id);
let local_file = tcx.sess.source_map().lookup_char_pos(fn_span.lo()).file;
for (bb, data) in body.basic_blocks.iter_enumerated() {
for (stmt_index, stmt) in data.statements.iter().enumerate() {
let stmt_file = tcx
.sess
.source_map()
.lookup_char_pos(stmt.source_info.span.lo())
.file;
if !std::ptr::addr_eq(
std::sync::Arc::as_ptr(&stmt_file),
std::sync::Arc::as_ptr(&local_file),
) {
continue;
}
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (lhs, rhs) = &**assign;
let is_write = place_has_raw_deref(body, lhs);
let (is_read, is_ptr2ref, is_mut_ref) = match rhs {
Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) => {
(place_has_raw_deref(body, place), false, false)
}
Rvalue::Ref(_, borrow_kind, place) => (
place_has_raw_deref(body, place),
true,
matches!(borrow_kind, rustc_middle::mir::BorrowKind::Mut { .. }),
),
_ => (false, false, false),
};
if !is_write && !is_read {
continue;
}
let deref_place = if is_write {
lhs
} else {
match rhs {
Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..)
| Rvalue::Ref(_, _, place) => place,
_ => continue,
}
};
if box_derefs.contains(&deref_place.local) {
continue;
}
let Some(ptr_operand) = ptr_operand_for_deref_place(deref_place) else {
continue;
};
let Some(pointee) = pointee_ty(body.local_decls[deref_place.local].ty) else {
continue;
};
infos.push(RawPtrDerefInfo {
block: bb,
ptr_operand,
pointee_ty: pointee,
is_read,
is_ptr2ref,
is_mut_ref,
destination: lhs.local,
statement_index: stmt_index,
});
}
}
infos
}
fn ptr_operand_for_deref_place<'tcx>(place: &Place<'tcx>) -> Option<Operand<'tcx>> {
use rustc_middle::ty::List;
let first_deref_idx = place
.projection
.iter()
.position(|p| matches!(p.kind(), ProjectionElem::Deref));
if let Some(idx) = first_deref_idx
&& idx > 0
{
return None;
}
Some(Operand::Copy(Place {
local: place.local,
projection: List::empty(),
}))
}
#[derive(Clone, Debug)]
pub struct StaticMutAccessInfo<'tcx> {
pub block: BasicBlock,
pub ty: Ty<'tcx>,
pub ptr_operand: Operand<'tcx>,
}
pub fn collect_static_mut_access_info<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> Vec<StaticMutAccessInfo<'tcx>> {
let mut infos = Vec::new();
if !tcx.is_mir_available(def_id) {
return infos;
}
let body = tcx.optimized_mir(def_id);
for (bb, data) in body.basic_blocks.iter_enumerated() {
for stmt in &data.statements {
if let StatementKind::Assign(assign) = &stmt.kind {
let (_lhs, rhs) = &**assign;
if let Rvalue::Use(op @ Operand::Constant(c), ..) = rhs
&& let Some(static_id) = c.check_static_ptr(tcx)
&& matches!(tcx.static_mutability(static_id), Some(m) if m.is_mut()) {
let ty = tcx.type_of(static_id).skip_binder();
infos.push(StaticMutAccessInfo {
block: bb,
ty,
ptr_operand: op.clone(),
});
}
}
}
if let Some(terminator) = &data.terminator
&& let TerminatorKind::Call { args, .. } = &terminator.kind {
for arg in args {
if let op @ Operand::Constant(c) = &arg.node
&& let Some(static_id) = c.check_static_ptr(tcx)
&& matches!(tcx.static_mutability(static_id), Some(m) if m.is_mut())
{
let ty = tcx.type_of(static_id).skip_binder();
infos.push(StaticMutAccessInfo {
block: bb,
ty,
ptr_operand: op.clone(),
});
}
}
}
}
infos
}
pub fn call_destination<'tcx>(tcx: TyCtxt<'tcx>, checkpoint: &Checkpoint<'tcx>) -> Option<Local> {
if checkpoint.kind == CheckpointKind::RawPtrDeref {
return checkpoint.destination;
}
let body = tcx.optimized_mir(checkpoint.caller);
let terminator = body.basic_blocks[checkpoint.block].terminator();
let TerminatorKind::Call { destination, .. } = &terminator.kind else {
return None;
};
Some(destination.local)
}