use rustc_hir::{Safety, def_id::DefId};
use rustc_middle::{
mir::{
BasicBlock, Body, Local, Operand, Place, ProjectionElem, Rvalue, StatementKind,
TerminatorKind,
},
ty::{self, Ty, TyCtxt, TyKind},
};
use rustc_span::Span;
use std::collections::{HashMap, HashSet};
use super::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 span: Span,
pub args: Vec<Operand<'tcx>>,
pub kind: CheckpointKind,
}
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 check_safety(tcx: TyCtxt<'_>, def_id: DefId) -> Safety {
let poly_fn_sig = tcx.fn_sig(def_id);
let fn_sig = poly_fn_sig.skip_binder();
fn_sig.safety()
}
pub fn place_has_raw_deref<'tcx>(
_tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
place: &Place<'tcx>,
) -> bool {
let local = place.local;
for proj in place.projection.iter() {
if let ProjectionElem::Deref = proj.kind() {
let ty = body.local_decls[local].ty;
if let TyKind::RawPtr(_, _) = ty.kind() {
return true;
}
}
}
false
}
pub fn get_rawptr_deref(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<Local> {
let mut raw_ptrs = HashSet::new();
if tcx.is_mir_available(def_id) {
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
if let StatementKind::Assign(assign) = &stmt.kind {
let (lhs, rhs) = &**assign;
if place_has_raw_deref(tcx, &body, lhs) {
raw_ptrs.insert(lhs.local);
}
if let Rvalue::Use(op, ..) = rhs {
match op {
Operand::Copy(place) | Operand::Move(place) => {
if place_has_raw_deref(tcx, &body, place) {
raw_ptrs.insert(place.local);
}
}
_ => {}
}
}
if let Rvalue::Ref(_, _, place) = rhs {
if place_has_raw_deref(tcx, &body, place) {
raw_ptrs.insert(place.local);
}
}
}
}
if let Some(terminator) = &bb.terminator {
match &terminator.kind {
rustc_middle::mir::TerminatorKind::Call { args, .. } => {
for arg in args {
match arg.node {
Operand::Copy(place) | Operand::Move(place) => {
if place_has_raw_deref(tcx, &body, &place) {
raw_ptrs.insert(place.local);
}
}
_ => {}
}
}
}
_ => {}
}
}
}
}
raw_ptrs
}
pub fn collect_global_local_pairs(tcx: TyCtxt<'_>, def_id: DefId) -> HashMap<DefId, Vec<Local>> {
let mut globals: HashMap<DefId, Vec<Local>> = HashMap::new();
if !tcx.is_mir_available(def_id) {
return globals;
}
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
if let StatementKind::Assign(assign) = &stmt.kind {
let (lhs, rhs) = &**assign;
if let Rvalue::Use(Operand::Constant(c), ..) = rhs {
if let Some(static_def_id) = c.check_static_ptr(tcx) {
globals.entry(static_def_id).or_default().push(lhs.local);
}
}
}
}
}
globals
}
pub fn get_unsafe_callees(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<DefId> {
let mut unsafe_callees = HashSet::new();
if tcx.is_mir_available(def_id) {
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
if let TerminatorKind::Call { func, .. } = &bb.terminator().kind {
if let Operand::Constant(func_constant) = func {
if let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() {
if check_safety(tcx, *callee_def_id) == Safety::Unsafe {
unsafe_callees.insert(*callee_def_id);
}
}
}
}
}
}
unsafe_callees
}
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,
fn_span,
..
} = &data.terminator().kind
else {
continue;
};
let Operand::Constant(func_constant) = func else {
continue;
};
let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() else {
continue;
};
if check_safety(tcx, *callee_def_id) != Safety::Unsafe {
continue;
}
checkpoints.push(Checkpoint {
caller: def_id,
callee: Some(*callee_def_id),
block: bb,
span: *fn_span,
args: args.iter().map(|arg| arg.node.clone()).collect(),
kind: CheckpointKind::UnsafeCall,
});
}
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 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);
for (bb, data) in body.basic_blocks.iter_enumerated() {
for stmt in &data.statements {
let StatementKind::Assign(assign) = &stmt.kind else {
continue;
};
let (lhs, rhs) = &**assign;
let is_write = place_has_raw_deref(tcx, &body, lhs);
let is_read = match rhs {
Rvalue::Use(Operand::Copy(place) | Operand::Move(place), ..) => {
place_has_raw_deref(tcx, &body, place)
}
_ => 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), ..) => place,
_ => continue,
}
};
let Some(ptr_operand) = ptr_operand_for_deref_place(deref_place) else {
continue;
};
let Some(pointee_ty) = deref_place_pointee_ty(&body, deref_place) else {
continue;
};
infos.push(RawPtrDerefInfo {
block: bb,
ptr_operand,
pointee_ty,
is_read,
});
}
}
infos
}
fn deref_place_pointee_ty<'tcx>(body: &Body<'tcx>, place: &Place<'tcx>) -> Option<Ty<'tcx>> {
let ty = body.local_decls[place.local].ty;
match ty.kind() {
TyKind::RawPtr(inner, _) => Some(*inner),
_ => None,
}
}
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 first_deref_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 static_def_id: DefId,
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 {
if let Some(static_id) = c.check_static_ptr(tcx) {
if 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,
static_def_id: static_id,
ty,
ptr_operand: op.clone(),
});
}
}
}
}
}
if let Some(terminator) = &data.terminator {
if let TerminatorKind::Call { args, .. } = &terminator.kind {
for arg in args {
match &arg.node {
op @ Operand::Constant(c) => {
if let Some(static_id) = c.check_static_ptr(tcx) {
if 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,
static_def_id: static_id,
ty,
ptr_operand: op.clone(),
});
}
}
}
_ => {}
}
}
}
}
}
infos
}