Skip to main content

rapx/analysis/alias/
mod.rs

1pub mod default;
2pub mod mfp;
3pub mod observer;
4use crate::utils::source::get_fn_name_byid;
5
6use super::super::Analysis;
7use crate::compat::FxHashMap;
8use rustc_hir::def_id::DefId;
9use rustc_middle::{
10    mir::{Local, Place, StatementKind},
11    ty::{GenericArgsRef, Ty, TyCtxt, TyKind},
12};
13use rustc_span::def_id::LOCAL_CRATE;
14use std::{collections::HashSet, fmt};
15
16/// The data structure to store aliases for a set of functions.
17pub type FnAliasMap = FxHashMap<DefId, FnAliasPairs>;
18
19/// This is a wrapper struct for displaying FnAliasMap.
20pub struct FnAliasMapWrapper(pub FnAliasMap);
21
22/// This trait provides features related to alias analysis.
23pub trait AliasAnalysis: Analysis {
24    /// Return the aliases among the function arguments and return value of a specific function.
25    fn get_fn_alias(&self, def_id: DefId) -> Option<FnAliasPairs>;
26    /// Return the aliases among the function arguments and return value for all functions.
27    fn get_all_fn_alias(&self) -> FnAliasMap;
28    /// Return the aliases among the function arguments and return value for functions of the local
29    /// crate.
30    fn get_local_fn_alias(&self) -> FnAliasMap {
31        self.get_all_fn_alias()
32            .iter()
33            .filter(|(def_id, _)| def_id.krate == LOCAL_CRATE)
34            .map(|(k, v)| (*k, v.clone()))
35            .collect()
36    }
37
38    /// Return the intra-procedural local → origin mapping for a function.
39    /// Default returns empty; analyser implementations may override with cached
40    /// or MoP-based results.
41    fn get_local_origins(&self, _def_id: DefId) -> LocalOriginMap {
42        LocalOriginMap::default()
43    }
44
45    /// If a place (local + field projections) in a method body resolves to a
46    /// struct's `self.field`, return the field identity.
47    fn get_self_field_origin(
48        &self,
49        _def_id: DefId,
50        _local: usize,
51        _fields: &[usize],
52    ) -> Option<FieldOrigin> {
53        None
54    }
55}
56
57/// To store the alias relationships among arguments and return values.
58/// Each function may have multiple return instructions, leading to different RetAlias.
59#[derive(Debug, Clone)]
60pub struct FnAliasPairs {
61    arg_size: usize,
62    alias_set: HashSet<AliasPair>,
63}
64
65impl FnAliasPairs {
66    pub fn new(arg_size: usize) -> FnAliasPairs {
67        Self {
68            arg_size,
69            alias_set: HashSet::new(),
70        }
71    }
72
73    pub fn arg_size(&self) -> usize {
74        self.arg_size
75    }
76
77    pub fn aliases(&self) -> &HashSet<AliasPair> {
78        &self.alias_set
79    }
80
81    pub fn add_alias(&mut self, alias: AliasPair) {
82        self.alias_set.insert(alias);
83    }
84
85    pub fn len(&self) -> usize {
86        self.alias_set.len()
87    }
88
89    pub fn sort_alias_index(&mut self) {
90        let alias_set = std::mem::take(&mut self.alias_set);
91        let mut new_alias_set = HashSet::with_capacity(alias_set.len());
92
93        for mut ra in alias_set.into_iter() {
94            if ra.left_local() >= ra.right_local() {
95                ra.swap();
96            }
97            new_alias_set.insert(ra);
98        }
99        self.alias_set = new_alias_set;
100    }
101
102    /// Compress field paths: truncate each side's field list to its
103    /// first element.  This matches the old MoP alias analysis behaviour
104    /// where deeply nested fields like `0.0.0.0` are shortened to `0.0`.
105    pub fn compress_fields(&mut self) {
106        let alias_set = std::mem::take(&mut self.alias_set);
107        let mut compressed = HashSet::with_capacity(alias_set.len());
108        for mut ra in alias_set.into_iter() {
109            if !ra.lhs_fields.is_empty() {
110                ra.lhs_fields = vec![ra.lhs_fields[0]];
111            }
112            if !ra.rhs_fields.is_empty() {
113                ra.rhs_fields = vec![ra.rhs_fields[0]];
114            }
115            compressed.insert(ra);
116        }
117        self.alias_set = compressed;
118    }
119}
120
121impl fmt::Display for FnAliasPairs {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        if self.aliases().is_empty() {
124            write!(f, "null")?;
125        } else {
126            let mut facts: Vec<_> = self.aliases().iter().collect();
127            facts.sort_by(|a, b| {
128                a.left_local
129                    .cmp(&b.left_local)
130                    .then(a.right_local.cmp(&b.right_local))
131                    .then(a.lhs_fields.cmp(&b.lhs_fields))
132                    .then(a.rhs_fields.cmp(&b.rhs_fields))
133            });
134            let joined = facts
135                .into_iter()
136                .map(|fact| format!("{}", fact))
137                .collect::<Vec<_>>()
138                .join(", ");
139            write!(f, "{}", joined)?;
140        }
141        Ok(())
142    }
143}
144
145impl fmt::Display for FnAliasMapWrapper {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        writeln!(f, "=== Print alias analysis results ===")?;
148        for (def_id, result) in &self.0 {
149            let fn_name = get_fn_name_byid(def_id);
150            writeln!(f, "Alias of {:?}: {}", fn_name, result)?;
151        }
152        Ok(())
153    }
154}
155
156/// Lightweight intra-procedural local → origin mapping.
157/// Maps `local_index` → `(origin_local_index, origin_field_projections)`.
158pub type LocalOriginMap = FxHashMap<usize, (usize, Vec<usize>)>;
159
160/// Identity of a struct field that a place resolves to.
161#[derive(Clone, Debug)]
162pub struct FieldOrigin {
163    pub struct_def_id: DefId,
164    pub field_index: usize,
165    pub field_name: String,
166}
167
168/// Unwrap Ref / RawPtr / Adt layers to get the innermost ADT definition.
169pub fn adt_from_ty<'tcx>(ty: Ty<'tcx>) -> Option<(DefId, GenericArgsRef<'tcx>)> {
170    match ty.kind() {
171        TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => adt_from_ty(*inner),
172        TyKind::Adt(adt, args) => Some((adt.did(), *args)),
173        _ => None,
174    }
175}
176
177/// Build a lightweight intra-procedural origin map by scanning MIR assignments.
178/// For each `local = rvalue`, records the source place if the rvalue is a
179/// simple copy / move / cast / ref / raw-ptr / copy-for-deref.
180pub fn collect_local_origins<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> LocalOriginMap {
181    let body = tcx.optimized_mir(def_id);
182    let mut origins = LocalOriginMap::default();
183
184    for block in body.basic_blocks.iter() {
185        for statement in &block.statements {
186            let StatementKind::Assign(assign) = &statement.kind else {
187                continue;
188            };
189            let (target, rvalue) = assign.as_ref();
190            if let Some(origin) = rvalue_origin(rvalue, &origins) {
191                origins.insert(target.local.as_usize(), origin);
192            }
193        }
194        if let rustc_middle::mir::TerminatorKind::Call { func, args, destination, .. } = &block.terminator().kind {
195            if let rustc_middle::mir::Operand::Constant(c) = func {
196                if let rustc_middle::ty::FnDef(_, _) = c.const_.ty().kind() {
197                    if let Some(first_arg) = args.first() {
198                        if let Some(place) = first_arg.node.place() {
199                            let origin = resolve_place(&place, &origins);
200                            origins.insert(destination.local.as_usize(), origin);
201                        }
202                    }
203                }
204            }
205        }
206    }
207    origins
208}
209
210/// Extract the origin `(local_index, fields)` from a rvalue, chasing through `origins`.
211fn rvalue_origin(
212    rvalue: &rustc_middle::mir::Rvalue<'_>,
213    origins: &LocalOriginMap,
214) -> Option<(usize, Vec<usize>)> {
215    if let Some(place) = crate::helpers::mir_utils::rvalue_source_place(rvalue) {
216        return Some(resolve_place(place, origins));
217    }
218    if let rustc_middle::mir::Rvalue::Cast(_, operand, _) = rvalue {
219        if let Some(place) = operand.place() {
220            return Some(resolve_place(&place, origins));
221        }
222    }
223    None
224}
225
226/// Resolve a MIR Place through the origin map.
227/// If the place has field projections, returns them directly.
228/// Otherwise, follows the alias chain one level.
229pub fn resolve_place(place: &Place<'_>, origins: &LocalOriginMap) -> (usize, Vec<usize>) {
230    let local = place.local.as_usize();
231    let fields: Vec<usize> = place
232        .projection
233        .iter()
234        .filter_map(|elem| match elem {
235            rustc_middle::mir::ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
236            _ => None,
237        })
238        .collect();
239    if !fields.is_empty() {
240        return (local, fields);
241    }
242    origins.get(&local).cloned().unwrap_or((local, fields))
243}
244
245/// If `local` (typically `1` = self) with `fields` in `def_id`'s body
246/// corresponds to a struct field, return its identity.
247pub fn resolve_self_field_origin<'tcx>(
248    tcx: TyCtxt<'tcx>,
249    def_id: DefId,
250    local: usize,
251    fields: &[usize],
252) -> Option<FieldOrigin> {
253    if local != 1 || fields.is_empty() {
254        return None;
255    }
256    let body = tcx.optimized_mir(def_id);
257    let self_ty = body.local_decls[Local::from_usize(1)].ty;
258    let (struct_def_id, _) = adt_from_ty(self_ty)?;
259    let field_index = fields[0];
260    let adt = tcx.adt_def(struct_def_id);
261    let field = adt.all_fields().nth(field_index)?;
262    Some(FieldOrigin {
263        struct_def_id,
264        field_index,
265        field_name: field.name.to_string(),
266    })
267}
268
269/// Like `resolve_self_field_origin` but uses the type of `local` instead
270/// of always `_1`.  For origins from call-site verification.
271pub fn resolve_any_field_origin<'tcx>(
272    tcx: TyCtxt<'tcx>,
273    def_id: DefId,
274    local: usize,
275    fields: &[usize],
276) -> Option<FieldOrigin> {
277    if fields.is_empty() {
278        return None;
279    }
280    let body = tcx.optimized_mir(def_id);
281    let self_ty = body.local_decls[Local::from_usize(local)].ty;
282    let (struct_def_id, _) = adt_from_ty(self_ty)?;
283    let field_index = fields[0];
284    let adt = tcx.adt_def(struct_def_id);
285    let field = adt.all_fields().nth(field_index)?;
286    Some(FieldOrigin {
287        struct_def_id,
288        field_index,
289        field_name: field.name.to_string(),
290    })
291}
292
293/// AliasPair is used to store the alias relationships between two places.
294/// The result is field-sensitive.
295#[derive(Debug, Clone, Hash, PartialEq, Eq)]
296pub struct AliasPair {
297    pub left_local: usize,
298    pub lhs_fields: Vec<usize>,
299    pub right_local: usize,
300    pub rhs_fields: Vec<usize>,
301}
302
303impl AliasPair {
304    pub fn new(left_local: usize, right_local: usize) -> AliasPair {
305        AliasPair {
306            left_local,
307            lhs_fields: Vec::<usize>::new(),
308            right_local,
309            rhs_fields: Vec::<usize>::new(),
310        }
311    }
312
313    /// Swap the two elements of an alias pair, i.e., left to right, and right to left.
314    pub fn swap(&mut self) {
315        std::mem::swap(&mut self.left_local, &mut self.right_local);
316        std::mem::swap(&mut self.lhs_fields, &mut self.rhs_fields);
317    }
318
319    pub fn left_local(&self) -> usize {
320        self.left_local
321    }
322
323    pub fn right_local(&self) -> usize {
324        self.right_local
325    }
326
327    pub fn lhs_fields(&self) -> &[usize] {
328        &self.lhs_fields
329    }
330
331    pub fn rhs_fields(&self) -> &[usize] {
332        &self.rhs_fields
333    }
334}
335
336impl fmt::Display for AliasPair {
337    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
338        write!(
339            f,
340            "({},{})",
341            aa_place_desc_str(self.left_local, &self.lhs_fields, true),
342            aa_place_desc_str(self.right_local, &self.rhs_fields, true)
343        )
344    }
345}
346
347fn aa_place_desc_str(no: usize, fields: &[usize], field_sensitive: bool) -> String {
348    let mut result = String::new();
349    result.push_str(&no.to_string());
350    if !field_sensitive {
351        return result;
352    }
353    for num in fields.iter() {
354        result.push('.');
355        result.push_str(&num.to_string());
356    }
357    result
358}