use crate::compat::FxHashMap;
use crate::compat::Spanned;
use rustc_hir::def_id::DefId;
use rustc_middle::{
mir::{
Body, CallReturnPlaces, Location, Operand, Place, Rvalue, Statement, StatementKind,
Terminator, TerminatorEdges, TerminatorKind,
},
ty::{self, Ty, TyCtxt, TypingEnv},
};
use rustc_mir_dataflow::{Analysis, JoinSemiLattice, fmt::DebugWithContext};
use std::cell::RefCell;
use std::rc::Rc;
use super::super::{FnAliasMap, FnAliasPairs};
use super::transfer;
use crate::analysis::alias_analysis::default::types::is_not_drop;
fn apply_function_summary<'tcx>(
state: &mut AliasDomain,
destination: Place<'tcx>,
args: &[Operand<'tcx>],
summary: &FnAliasPairs,
place_info: &PlaceInfo<'tcx>,
) {
let dest_id = transfer::mir_place_to_place_id(destination);
let mut actual_places = vec![dest_id.clone()];
for arg in args {
if let Some(arg_id) = transfer::operand_to_place_id(arg) {
actual_places.push(arg_id);
} else {
actual_places.push(PlaceId::Local(usize::MAX));
}
}
for alias_pair in summary.aliases() {
let left_idx = alias_pair.left_local();
let right_idx = alias_pair.right_local();
if left_idx >= actual_places.len() || right_idx >= actual_places.len() {
continue;
}
if actual_places[left_idx] == PlaceId::Local(usize::MAX)
|| actual_places[right_idx] == PlaceId::Local(usize::MAX)
{
continue;
}
let mut left_place = actual_places[left_idx].clone();
for &field_idx in alias_pair.lhs_fields() {
left_place = left_place.project_field(field_idx);
}
let mut right_place = actual_places[right_idx].clone();
for &field_idx in alias_pair.rhs_fields() {
right_place = right_place.project_field(field_idx);
}
if let (Some(left_place_idx), Some(right_place_idx)) = (
place_info.get_index(&left_place),
place_info.get_index(&right_place),
) {
let left_may_drop = place_info.may_drop(left_place_idx);
let right_may_drop = place_info.may_drop(right_place_idx);
if left_may_drop && right_may_drop {
state.union(left_place_idx, right_place_idx);
}
}
}
}
fn apply_conservative_alias_for_call<'tcx>(
state: &mut AliasDomain,
destination: Place<'tcx>,
args: &[Spanned<rustc_middle::mir::Operand<'tcx>>],
place_info: &PlaceInfo<'tcx>,
) {
let dest_id = transfer::mir_place_to_place_id(destination);
let dest_idx = match place_info.get_index(&dest_id) {
Some(idx) => idx,
None => {
return;
}
};
if !place_info.may_drop(dest_idx) {
return;
}
for (_i, arg) in args.iter().enumerate() {
if let Some(arg_id) = transfer::operand_to_place_id(&arg.node) {
if let Some(arg_idx) = place_info.get_index(&arg_id) {
if place_info.may_drop(arg_idx) {
state.union(dest_idx, arg_idx);
transfer::sync_fields(state, &dest_id, &arg_id, place_info);
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PlaceId {
Local(usize),
Field {
base: Box<PlaceId>,
field_idx: usize,
},
}
impl PlaceId {
pub fn root_local(&self) -> usize {
match self {
PlaceId::Local(idx) => *idx,
PlaceId::Field { base, .. } => base.root_local(),
}
}
pub fn project_field(&self, field_idx: usize) -> PlaceId {
PlaceId::Field {
base: Box::new(self.clone()),
field_idx,
}
}
pub fn has_prefix(&self, prefix: &PlaceId) -> bool {
if self == prefix {
return true;
}
match self {
PlaceId::Local(_) => false,
PlaceId::Field { base, .. } => base.has_prefix(prefix),
}
}
}
#[derive(Clone)]
pub struct PlaceInfo<'tcx> {
place_to_index: FxHashMap<PlaceId, usize>,
index_to_place: Vec<PlaceId>,
may_drop: Vec<bool>,
need_drop: Vec<bool>,
num_places: usize,
_phantom: std::marker::PhantomData<&'tcx ()>,
}
impl<'tcx> PlaceInfo<'tcx> {
pub fn new() -> Self {
PlaceInfo {
place_to_index: FxHashMap::default(),
index_to_place: Vec::new(),
may_drop: Vec::new(),
need_drop: Vec::new(),
num_places: 0,
_phantom: std::marker::PhantomData,
}
}
pub fn build(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) -> Self {
let mut info = Self::new();
let ty_env = TypingEnv::post_analysis(tcx, def_id);
for (local, local_decl) in body.local_decls.iter_enumerated() {
let ty = local_decl.ty;
let need_drop = ty.needs_drop(tcx, ty_env);
let may_drop = !is_not_drop(tcx, ty);
let place_id = PlaceId::Local(local.as_usize());
info.register_place(place_id.clone(), may_drop, need_drop);
info.create_fields_for_type(tcx, ty, place_id, 0, 0, ty_env);
}
info
}
fn create_fields_for_type(
&mut self,
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
base_place: PlaceId,
field_depth: usize,
deref_depth: usize,
ty_env: TypingEnv<'tcx>,
) {
const MAX_FIELD_DEPTH: usize = 5;
const MAX_DEREF_DEPTH: usize = 3;
if field_depth >= MAX_FIELD_DEPTH || deref_depth >= MAX_DEREF_DEPTH {
return;
}
match ty.kind() {
ty::Ref(_, inner_ty, _) => {
self.create_fields_for_type(
tcx,
*inner_ty,
base_place,
field_depth,
deref_depth + 1,
ty_env,
);
}
ty::RawPtr(inner_ty, _) => {
self.create_fields_for_type(
tcx,
*inner_ty,
base_place,
field_depth,
deref_depth + 1,
ty_env,
);
}
ty::Adt(adt_def, substs) => {
for (field_idx, field) in adt_def.all_fields().enumerate() {
#[cfg(not(rapx_rustc_ge_198))]
let field_ty = field.ty(tcx, substs);
#[cfg(rapx_rustc_ge_198)]
let field_ty = field.ty(tcx, substs).skip_norm_wip();
let field_place = base_place.project_field(field_idx);
let need_drop = field_ty.needs_drop(tcx, ty_env);
let may_drop = if deref_depth > 0 {
true
} else {
!is_not_drop(tcx, field_ty)
};
self.register_place(field_place.clone(), may_drop, need_drop);
self.create_fields_for_type(
tcx,
field_ty,
field_place,
field_depth + 1,
deref_depth,
ty_env,
);
}
}
ty::Tuple(fields) => {
for (field_idx, field_ty) in fields.iter().enumerate() {
let field_place = base_place.project_field(field_idx);
let may_drop = if deref_depth > 0 {
true
} else {
!is_not_drop(tcx, field_ty)
};
let need_drop = field_ty.needs_drop(tcx, ty_env);
self.register_place(field_place.clone(), may_drop, need_drop);
self.create_fields_for_type(
tcx,
field_ty,
field_place,
field_depth + 1,
deref_depth,
ty_env,
);
}
}
_ => {
}
}
}
pub fn register_place(&mut self, place_id: PlaceId, may_drop: bool, need_drop: bool) -> usize {
if let Some(&idx) = self.place_to_index.get(&place_id) {
return idx;
}
let idx = self.num_places;
self.place_to_index.insert(place_id.clone(), idx);
self.index_to_place.push(place_id);
self.may_drop.push(may_drop);
self.need_drop.push(need_drop);
self.num_places += 1;
idx
}
pub fn get_index(&self, place_id: &PlaceId) -> Option<usize> {
self.place_to_index.get(place_id).copied()
}
pub fn get_place(&self, idx: usize) -> Option<&PlaceId> {
self.index_to_place.get(idx)
}
pub fn may_drop(&self, idx: usize) -> bool {
self.may_drop.get(idx).copied().unwrap_or(false)
}
pub fn need_drop(&self, idx: usize) -> bool {
self.need_drop.get(idx).copied().unwrap_or(false)
}
pub fn num_places(&self) -> usize {
self.num_places
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AliasDomain {
parent: Vec<usize>,
rank: Vec<usize>,
}
impl AliasDomain {
pub fn new(num_places: usize) -> Self {
AliasDomain {
parent: (0..num_places).collect(),
rank: vec![0; num_places],
}
}
pub fn find(&mut self, idx: usize) -> usize {
if self.parent[idx] != idx {
self.parent[idx] = self.find(self.parent[idx]);
}
self.parent[idx]
}
pub fn union(&mut self, idx1: usize, idx2: usize) -> bool {
let root1 = self.find(idx1);
let root2 = self.find(idx2);
if root1 == root2 {
return false;
}
if self.rank[root1] < self.rank[root2] {
self.parent[root1] = root2;
} else if self.rank[root1] > self.rank[root2] {
self.parent[root2] = root1;
} else {
self.parent[root2] = root1;
self.rank[root1] += 1;
}
true
}
pub fn are_aliased(&mut self, idx1: usize, idx2: usize) -> bool {
self.find(idx1) == self.find(idx2)
}
pub fn remove_aliases(&mut self, idx: usize) {
let root = self.find(idx);
let mut component_nodes = Vec::new();
for i in 0..self.parent.len() {
if self.find(i) == root {
component_nodes.push(i);
}
}
component_nodes.retain(|&i| i != idx);
self.parent[idx] = idx;
self.rank[idx] = 0;
if !component_nodes.is_empty() {
for &i in &component_nodes {
self.parent[i] = i;
self.rank[i] = 0;
}
let first = component_nodes[0];
for &i in &component_nodes[1..] {
self.union(first, i);
}
}
}
pub fn remove_aliases_with_prefix(&mut self, place_id: &PlaceId, place_info: &PlaceInfo) {
let mut indices_to_remove = Vec::new();
for idx in 0..self.parent.len() {
if let Some(pid) = place_info.get_place(idx) {
if pid.has_prefix(place_id) {
indices_to_remove.push(idx);
}
}
}
for idx in indices_to_remove {
self.remove_aliases(idx);
}
}
pub fn get_all_alias_pairs(&self) -> Vec<(usize, usize)> {
let mut pairs = Vec::new();
let mut domain_clone = self.clone();
for i in 0..self.parent.len() {
for j in (i + 1)..self.parent.len() {
if domain_clone.are_aliased(i, j) {
pairs.push((i, j));
}
}
}
pairs
}
}
impl JoinSemiLattice for AliasDomain {
fn join(&mut self, other: &Self) -> bool {
assert_eq!(
self.parent.len(),
other.parent.len(),
"AliasDomain::join: size mismatch (self: {}, other: {})",
self.parent.len(),
other.parent.len()
);
let mut changed = false;
let pairs = other.get_all_alias_pairs();
for (i, j) in pairs {
if self.union(i, j) {
changed = true;
}
}
changed
}
}
impl DebugWithContext<FnAliasAnalyzer<'_>> for AliasDomain {}
pub struct FnAliasAnalyzer<'tcx> {
pub tcx: TyCtxt<'tcx>,
_body: &'tcx Body<'tcx>,
_def_id: DefId,
place_info: PlaceInfo<'tcx>,
fn_summaries: Rc<RefCell<FnAliasMap>>,
pub bb_iter_cnt: RefCell<usize>,
}
impl<'tcx> FnAliasAnalyzer<'tcx> {
pub fn new(
tcx: TyCtxt<'tcx>,
def_id: DefId,
body: &'tcx Body<'tcx>,
fn_summaries: Rc<RefCell<FnAliasMap>>,
) -> Self {
let place_info = PlaceInfo::build(tcx, def_id, body);
FnAliasAnalyzer {
tcx,
_body: body,
_def_id: def_id,
place_info,
fn_summaries,
bb_iter_cnt: RefCell::new(0),
}
}
pub fn place_info(&self) -> &PlaceInfo<'tcx> {
&self.place_info
}
}
#[cfg(not(rapx_rustc_ge_193))]
impl<'tcx> Analysis<'tcx> for FnAliasAnalyzer<'tcx> {
type Domain = AliasDomain;
const NAME: &'static str = "FnAliasAnalyzer";
fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
AliasDomain::new(self.place_info.num_places())
}
fn initialize_start_block(&self, _body: &Body<'tcx>, _state: &mut Self::Domain) {}
fn apply_primary_statement_effect(
&mut self,
state: &mut Self::Domain,
statement: &Statement<'tcx>,
_location: Location,
) {
apply_statement_effect(self, state, statement)
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
state: &mut Self::Domain,
terminator: &'mir Terminator<'tcx>,
_location: Location,
) -> TerminatorEdges<'mir, 'tcx> {
apply_terminator_effect(self, state, terminator)
}
fn apply_call_return_effect(
&mut self,
_state: &mut Self::Domain,
_block: rustc_middle::mir::BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
}
}
#[cfg(rapx_rustc_ge_193)]
impl<'tcx> Analysis<'tcx> for FnAliasAnalyzer<'tcx> {
type Domain = AliasDomain;
const NAME: &'static str = "FnAliasAnalyzer";
fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
AliasDomain::new(self.place_info.num_places())
}
fn initialize_start_block(&self, _body: &Body<'tcx>, _state: &mut Self::Domain) {}
fn apply_primary_statement_effect(
&self,
state: &mut Self::Domain,
statement: &Statement<'tcx>,
_location: Location,
) {
apply_statement_effect(self, state, statement)
}
fn apply_primary_terminator_effect<'mir>(
&self,
state: &mut Self::Domain,
terminator: &'mir Terminator<'tcx>,
_location: Location,
) -> TerminatorEdges<'mir, 'tcx> {
apply_terminator_effect(self, state, terminator)
}
fn apply_call_return_effect(
&self,
_state: &mut Self::Domain,
_block: rustc_middle::mir::BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
}
}
fn apply_statement_effect<'tcx>(
analyzer: &FnAliasAnalyzer<'tcx>,
state: &mut AliasDomain,
statement: &Statement<'tcx>,
) {
match &statement.kind {
StatementKind::Assign(assign) => {
let (lv, rvalue) = &**assign;
match rvalue {
Rvalue::Use(operand, ..) => {
transfer::transfer_assign(state, *lv, operand, &analyzer.place_info);
}
Rvalue::Ref(_, _, rv) | Rvalue::RawPtr(_, rv) => {
transfer::transfer_ref(state, *lv, *rv, &analyzer.place_info);
}
Rvalue::CopyForDeref(rv) => {
transfer::transfer_ref(state, *lv, *rv, &analyzer.place_info);
}
Rvalue::Cast(_, operand, _) => {
transfer::transfer_assign(state, *lv, operand, &analyzer.place_info);
}
Rvalue::Aggregate(_, operands) => {
let operand_slice: Vec<_> = operands.iter().map(|op| op.clone()).collect();
transfer::transfer_aggregate(state, *lv, &operand_slice, &analyzer.place_info);
}
#[cfg(not(rapx_rustc_ge_196))]
Rvalue::ShallowInitBox(operand, _) => {
transfer::transfer_assign(state, *lv, operand, &analyzer.place_info);
}
_ => {}
}
}
_ => {}
}
}
fn apply_terminator_effect<'tcx, 'mir>(
analyzer: &FnAliasAnalyzer<'tcx>,
state: &mut AliasDomain,
terminator: &'mir Terminator<'tcx>,
) -> TerminatorEdges<'mir, 'tcx> {
{
*analyzer.bb_iter_cnt.borrow_mut() += 1;
}
match &terminator.kind {
TerminatorKind::Call {
target,
destination,
args,
func,
..
} => {
let operand_slice: Vec<_> = args
.iter()
.map(|spanned_arg| spanned_arg.node.clone())
.collect();
transfer::transfer_call(state, *destination, &operand_slice, &analyzer.place_info);
if let Operand::Constant(c) = func {
if let ty::FnDef(callee_def_id, _) = c.ty().kind() {
let fn_summaries = analyzer.fn_summaries.borrow();
if let Some(summary) = fn_summaries.get(callee_def_id) {
apply_function_summary(
state,
*destination,
&operand_slice,
summary,
&analyzer.place_info,
);
} else {
drop(fn_summaries);
apply_conservative_alias_for_call(
state,
*destination,
args,
&analyzer.place_info,
);
}
}
}
if let Some(target_bb) = target {
TerminatorEdges::Single(*target_bb)
} else {
TerminatorEdges::None
}
}
TerminatorKind::Drop { target, .. } => TerminatorEdges::Single(*target),
TerminatorKind::SwitchInt { discr, targets } => {
TerminatorEdges::SwitchInt { discr, targets }
}
TerminatorKind::Assert { target, .. } => TerminatorEdges::Single(*target),
TerminatorKind::Goto { target } => TerminatorEdges::Single(*target),
TerminatorKind::Return => TerminatorEdges::None,
_ => TerminatorEdges::None,
}
}