rapx/analysis/core/alias_analysis/default/
graph.rs

1use super::{MopAAResult, assign::*, block::*, types::*, value::*};
2use crate::{
3    analysis::graphs::scc::{Scc, SccExit},
4    def_id::*,
5    utils::source::*,
6};
7use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8use rustc_middle::{
9    mir::{BasicBlock, Const, Operand, Rvalue, StatementKind, TerminatorKind, UnwindAction},
10    ty::{self, TyCtxt, TypingEnv},
11};
12use rustc_span::{Span, def_id::DefId};
13use std::{fmt, vec::Vec};
14
15pub struct MopGraph<'tcx> {
16    pub def_id: DefId,
17    pub tcx: TyCtxt<'tcx>,
18    // The field is used to detect dangling pointers in arguments after the function returns.
19    pub arg_size: usize,
20    pub span: Span,
21    // All values (including fields) of the function.
22    // For general variables, we use its Local as the value index;
23    // For fields, the value index is determined via auto increment.
24    pub values: Vec<Value>,
25    // All blocks of the function;
26    // Each block is initialized as a basic block of the mir;
27    // The blocks are then aggregated into strongly-connected components.
28    pub blocks: Vec<Block<'tcx>>,
29    // We record the constant value for path sensitivity.
30    pub constants: FxHashMap<usize, usize>,
31    // We record the decision of enumerate typed values for path sensitivity.
32    pub discriminants: FxHashMap<usize, usize>,
33    // a threhold to avoid path explosion.
34    pub visit_times: usize,
35    pub alias_set: Vec<usize>,
36    // contains the return results for inter-procedure analysis.
37    pub ret_alias: MopAAResult,
38    pub terminators: Vec<TerminatorKind<'tcx>>,
39}
40
41impl<'tcx> MopGraph<'tcx> {
42    pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> MopGraph<'tcx> {
43        let fn_name = get_fn_name(tcx, def_id);
44        rap_debug!("Building a new MoP graph for: {:?}", fn_name);
45        // handle variables
46        let body = tcx.optimized_mir(def_id);
47        let locals = &body.local_decls;
48        let arg_size = body.arg_count;
49        let mut values = Vec::<Value>::new();
50        let mut alias = Vec::<usize>::new();
51        let ty_env = TypingEnv::post_analysis(tcx, def_id);
52        for (local, local_decl) in locals.iter_enumerated() {
53            let need_drop = local_decl.ty.needs_drop(tcx, ty_env); // the type is drop
54            let may_drop = !is_not_drop(tcx, local_decl.ty);
55            let mut node = Value::new(
56                local.as_usize(),
57                local.as_usize(),
58                need_drop,
59                need_drop || may_drop,
60            );
61            node.kind = kind(local_decl.ty);
62            alias.push(alias.len());
63            values.push(node);
64        }
65
66        let basicblocks = &body.basic_blocks;
67        let mut blocks = Vec::<Block<'tcx>>::new();
68        let mut discriminants = FxHashMap::default();
69        let mut terminators = Vec::new();
70
71        // handle each basicblock
72        for i in 0..basicblocks.len() {
73            let bb = &basicblocks[BasicBlock::from(i)];
74            let mut cur_bb = Block::new(i, bb.is_cleanup);
75
76            // handle general statements
77            for stmt in &bb.statements {
78                let span = stmt.source_info.span;
79                match &stmt.kind {
80                    StatementKind::Assign(box (place, rvalue)) => {
81                        let lv_place = *place;
82                        let lv_local = place.local.as_usize();
83                        cur_bb.assigned_locals.insert(lv_local);
84                        match rvalue.clone() {
85                            // rvalue is a Rvalue
86                            Rvalue::Use(operand) => {
87                                match operand {
88                                    Operand::Copy(rv_place) => {
89                                        let rv_local = rv_place.local.as_usize();
90                                        if values[lv_local].may_drop && values[rv_local].may_drop {
91                                            let assign = Assignment::new(
92                                                lv_place,
93                                                rv_place,
94                                                AssignType::Copy,
95                                                span,
96                                            );
97                                            cur_bb.assignments.push(assign);
98                                        }
99                                    }
100                                    Operand::Move(rv_place) => {
101                                        let rv_local = rv_place.local.as_usize();
102                                        if values[lv_local].may_drop && values[rv_local].may_drop {
103                                            let assign = Assignment::new(
104                                                lv_place,
105                                                rv_place,
106                                                AssignType::Move,
107                                                span,
108                                            );
109                                            cur_bb.assignments.push(assign);
110                                        }
111                                    }
112                                    Operand::Constant(constant) => {
113                                        /* We should check the correctness due to the update of rustc
114                                         * https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.Const.html
115                                         */
116                                        match constant.const_ {
117                                            Const::Ty(_ty, const_value) => {
118                                                if let Some(val) =
119                                                    const_value.try_to_target_usize(tcx)
120                                                {
121                                                    cur_bb.const_value.push(ConstValue::new(
122                                                        lv_local,
123                                                        val as usize,
124                                                    ));
125                                                }
126                                            }
127                                            Const::Unevaluated(_const_value, _ty) => {}
128                                            Const::Val(const_value, _ty) => {
129                                                if let Some(scalar) =
130                                                    const_value.try_to_scalar_int()
131                                                {
132                                                    let val = scalar.to_uint(scalar.size());
133                                                    cur_bb.const_value.push(ConstValue::new(
134                                                        lv_local,
135                                                        val as usize,
136                                                    ));
137                                                }
138                                            }
139                                        }
140                                    }
141                                }
142                            }
143                            Rvalue::Ref(_, _, rv_place)
144                            | Rvalue::RawPtr(_, rv_place)
145                            | Rvalue::CopyForDeref(rv_place) => {
146                                let rv_local = rv_place.local.as_usize();
147                                if values[lv_local].may_drop && values[rv_local].may_drop {
148                                    let assign =
149                                        Assignment::new(lv_place, rv_place, AssignType::Copy, span);
150                                    cur_bb.assignments.push(assign);
151                                }
152                            }
153                            Rvalue::ShallowInitBox(operand, _) => {
154                                /*
155                                 * Original ShllowInitBox is a two-level pointer: lvl0 -> lvl1 -> lvl2
156                                 * Since our alias analysis does not consider multi-level pointer,
157                                 * We simplify it as: lvl0
158                                 */
159                                if !values[lv_local].fields.contains_key(&0) {
160                                    let mut lvl0 = Value::new(values.len(), lv_local, false, true);
161                                    lvl0.birth = values[lv_local].birth;
162                                    lvl0.field_id = 0;
163                                    values[lv_local].fields.insert(0, lvl0.index);
164                                    alias.push(alias.len());
165                                    //drop_record.push(drop_record[lv_local]);
166                                    values.push(lvl0);
167                                }
168                                match operand {
169                                    Operand::Copy(rv_place) | Operand::Move(rv_place) => {
170                                        let rv_local = rv_place.local.as_usize();
171                                        if values[lv_local].may_drop && values[rv_local].may_drop {
172                                            let assign = Assignment::new(
173                                                lv_place,
174                                                rv_place,
175                                                AssignType::InitBox,
176                                                span,
177                                            );
178                                            cur_bb.assignments.push(assign);
179                                        }
180                                    }
181                                    Operand::Constant(_) => {}
182                                }
183                            }
184                            Rvalue::Cast(_, operand, _) => match operand {
185                                Operand::Copy(rv_place) => {
186                                    let rv_local = rv_place.local.as_usize();
187                                    if values[lv_local].may_drop && values[rv_local].may_drop {
188                                        let assign = Assignment::new(
189                                            lv_place,
190                                            rv_place,
191                                            AssignType::Copy,
192                                            span,
193                                        );
194                                        cur_bb.assignments.push(assign);
195                                    }
196                                }
197                                Operand::Move(rv_place) => {
198                                    let rv_local = rv_place.local.as_usize();
199                                    if values[lv_local].may_drop && values[rv_local].may_drop {
200                                        let assign = Assignment::new(
201                                            lv_place,
202                                            rv_place,
203                                            AssignType::Move,
204                                            span,
205                                        );
206                                        cur_bb.assignments.push(assign);
207                                    }
208                                }
209                                Operand::Constant(_) => {}
210                            },
211                            Rvalue::Aggregate(_, operands) => {
212                                for operand in operands {
213                                    match operand {
214                                        Operand::Copy(rv_place) | Operand::Move(rv_place) => {
215                                            let rv_local = rv_place.local.as_usize();
216                                            if values[lv_local].may_drop
217                                                && values[rv_local].may_drop
218                                            {
219                                                let assign = Assignment::new(
220                                                    lv_place,
221                                                    rv_place,
222                                                    AssignType::Copy,
223                                                    span,
224                                                );
225                                                cur_bb.assignments.push(assign);
226                                            }
227                                        }
228                                        Operand::Constant(_) => {}
229                                    }
230                                }
231                            }
232                            Rvalue::Discriminant(rv_place) => {
233                                let assign =
234                                    Assignment::new(lv_place, rv_place, AssignType::Variant, span);
235                                cur_bb.assignments.push(assign);
236                                discriminants.insert(lv_local, rv_place.local.as_usize());
237                            }
238                            _ => {}
239                        }
240                    }
241                    StatementKind::SetDiscriminant {
242                        place: _,
243                        variant_index: _,
244                    } => {
245                        rap_warn!("SetDiscriminant: {:?} is not handled in RAPx!", stmt);
246                    }
247                    _ => {}
248                }
249            }
250
251            let Some(terminator) = &bb.terminator else {
252                rap_info!(
253                    "Basic block BB{} has no terminator in function {:?}",
254                    i,
255                    fn_name
256                );
257                continue;
258            };
259            terminators.push(terminator.kind.clone());
260            // handle terminator statements
261            match terminator.kind.clone() {
262                TerminatorKind::Goto { ref target } => {
263                    cur_bb.add_next(target.as_usize());
264                }
265                TerminatorKind::SwitchInt {
266                    discr: _,
267                    ref targets,
268                } => {
269                    cur_bb.terminator = Term::Switch(terminator.clone());
270                    for (_, ref target) in targets.iter() {
271                        cur_bb.add_next(target.as_usize());
272                    }
273                    cur_bb.add_next(targets.otherwise().as_usize());
274                }
275                TerminatorKind::Drop {
276                    place: _,
277                    target,
278                    unwind,
279                    replace: _,
280                    drop: _,
281                    async_fut: _,
282                } => {
283                    cur_bb.add_next(target.as_usize());
284                    cur_bb.terminator = Term::Drop(terminator.clone());
285                    if let UnwindAction::Cleanup(target) = unwind {
286                        cur_bb.add_next(target.as_usize());
287                    }
288                }
289                TerminatorKind::Call {
290                    ref func,
291                    args: _,
292                    destination: _,
293                    ref target,
294                    ref unwind,
295                    call_source: _,
296                    fn_span: _,
297                } => {
298                    if let Operand::Constant(c) = func {
299                        if let &ty::FnDef(id, ..) = c.ty().kind() {
300                            // for no_std crates without using alloc,
301                            // dealloc will be never found, thus call dealloc_opt here
302                            if id == drop()
303                                || id == drop_in_place()
304                                || id == manually_drop()
305                                || dealloc_opt().map(|f| f == id).unwrap_or(false)
306                            {
307                                cur_bb.terminator = Term::Drop(terminator.clone());
308                            } else {
309                                cur_bb.terminator = Term::Call(terminator.clone());
310                            }
311                        }
312                    } else {
313                        cur_bb.terminator = Term::Call(terminator.clone());
314                    }
315                    if let Some(tt) = target {
316                        cur_bb.add_next(tt.as_usize());
317                    }
318                    if let UnwindAction::Cleanup(tt) = unwind {
319                        cur_bb.add_next(tt.as_usize());
320                    }
321                }
322
323                TerminatorKind::Assert {
324                    cond: _,
325                    expected: _,
326                    msg: _,
327                    ref target,
328                    ref unwind,
329                } => {
330                    cur_bb.add_next(target.as_usize());
331                    if let UnwindAction::Cleanup(target) = unwind {
332                        cur_bb.add_next(target.as_usize());
333                    }
334                }
335                TerminatorKind::Yield {
336                    value: _,
337                    ref resume,
338                    resume_arg: _,
339                    ref drop,
340                } => {
341                    cur_bb.add_next(resume.as_usize());
342                    if let Some(target) = drop {
343                        cur_bb.add_next(target.as_usize());
344                    }
345                }
346                TerminatorKind::FalseEdge {
347                    ref real_target,
348                    imaginary_target: _,
349                } => {
350                    cur_bb.add_next(real_target.as_usize());
351                }
352                TerminatorKind::FalseUnwind {
353                    ref real_target,
354                    unwind: _,
355                } => {
356                    cur_bb.add_next(real_target.as_usize());
357                }
358                TerminatorKind::InlineAsm {
359                    template: _,
360                    operands: _,
361                    options: _,
362                    line_spans: _,
363                    ref unwind,
364                    targets,
365                    asm_macro: _,
366                } => {
367                    for target in targets {
368                        cur_bb.add_next(target.as_usize());
369                    }
370                    if let UnwindAction::Cleanup(target) = unwind {
371                        cur_bb.add_next(target.as_usize());
372                    }
373                }
374                _ => {}
375            }
376            blocks.push(cur_bb);
377        }
378
379        MopGraph {
380            def_id,
381            tcx,
382            span: body.span,
383            blocks,
384            values,
385            arg_size,
386            alias_set: alias,
387            constants: FxHashMap::default(),
388            ret_alias: MopAAResult::new(arg_size),
389            visit_times: 0,
390            discriminants,
391            terminators,
392        }
393    }
394
395    pub fn dfs_on_spanning_tree(
396        &self,
397        index: usize,
398        stack: &mut Vec<usize>,
399        paths: &mut Vec<Vec<usize>>,
400    ) {
401        let scc_index = self.blocks[index].scc.enter;
402        if self.blocks[scc_index].next.is_empty() {
403            paths.push(stack.to_vec());
404        } else {
405            for child in self.blocks[scc_index].next.iter() {
406                stack.push(*child);
407                self.dfs_on_spanning_tree(*child, stack, paths);
408            }
409        }
410        stack.pop();
411    }
412
413    pub fn get_paths(&self) -> Vec<Vec<usize>> {
414        let mut paths: Vec<Vec<usize>> = Vec::new();
415        let mut stack: Vec<usize> = vec![0];
416        self.dfs_on_spanning_tree(0, &mut stack, &mut paths);
417        paths
418    }
419    pub fn get_all_branch_sub_blocks_paths(&self) -> Vec<Vec<usize>> {
420        // 1. Get all execution paths
421        let all_paths = self.get_paths();
422
423        // Use FxHashSet to collect all unique lists of dominated_scc_bbs.
424        // Vec<usize> implements Hash, so it can be inserted directly into the set.
425        let mut unique_branch_sub_blocks = FxHashSet::<Vec<usize>>::default();
426
427        // 2. Iterate over each path
428        for path in all_paths {
429            // 3. For the current path, get the corresponding dominated_scc_bbs for branch nodes
430            let branch_blocks_for_this_path = self.get_branch_sub_blocks_for_path(&path);
431            rap_debug!(
432                "Branch blocks for path {:?}: {:?}",
433                path,
434                branch_blocks_for_this_path
435            );
436            // 4. Add these dominated_scc_bbs to the set
437            //    Use insert to avoid duplicates
438            unique_branch_sub_blocks.insert(branch_blocks_for_this_path);
439        }
440
441        // 5. Convert the set of unique results back to a Vec and return
442        unique_branch_sub_blocks.into_iter().collect()
443    }
444
445    pub fn get_branch_sub_blocks_for_path(&self, path: &[usize]) -> Vec<usize> {
446        let mut expanded_path = Vec::new();
447        // Use FxHashSet to track which SCCs have already been expanded in this path,
448        // preventing repeated expansion due to cycles.
449        let mut processed_scc_indices = FxHashSet::default();
450
451        for &block_idx in path {
452            // 1. Get the representative SCC index of the current basic block
453            let scc_idx = self.blocks[block_idx].scc.enter;
454
455            // 2. Try inserting scc_idx into the set. If successful (returns true),
456            // it means this SCC is encountered for the first time.
457            if processed_scc_indices.insert(scc_idx) {
458                // First time encountering this SCC: perform full expansion
459
460                // a. First, add the SCC representative itself to the path
461                expanded_path.push(scc_idx);
462
463                // b. Then, retrieve the SCC node information
464                let scc_enter = &self.blocks[scc_idx];
465
466                // c. If it has sub-blocks (i.e., it’s a multi-node SCC),
467                // append all sub-blocks to the path.
468                // dominated_scc_bbs are already ordered (topologically or near-topologically)
469                if !scc_enter.scc.nodes.is_empty() {
470                    expanded_path.extend_from_slice(&scc_enter.scc.nodes);
471                }
472            } else {
473                // SCC already seen before (e.g., due to a cycle in the path):
474                // Only add the representative node as a connector, skip internal blocks.
475                expanded_path.push(scc_idx);
476            }
477        }
478
479        expanded_path
480    }
481
482    pub fn get_switch_conds(&mut self, bb_idx: usize) -> Option<usize> {
483        let term = &self.blocks[bb_idx].terminator;
484        let switch = match term {
485            Term::Switch(s) => s,
486            _ => return None,
487        };
488        let discr = match &switch.kind {
489            TerminatorKind::SwitchInt { discr, .. } => discr,
490            _ => return None,
491        };
492        match discr {
493            Operand::Copy(p) | Operand::Move(p) => Some(self.projection(false, *p)),
494            _ => None,
495        }
496    }
497}
498
499pub trait SccHelper<'tcx> {
500    fn blocks(&self) -> &Vec<Block<'tcx>>; // or whatever the actual type is
501    fn blocks_mut(&mut self) -> &mut Vec<Block<'tcx>>;
502    fn switch_conds(&mut self, node: usize) -> Option<usize>;
503}
504
505impl<'tcx> SccHelper<'tcx> for MopGraph<'tcx> {
506    fn blocks(&self) -> &Vec<Block<'tcx>> {
507        &self.blocks
508    }
509    fn blocks_mut(&mut self) -> &mut Vec<Block<'tcx>> {
510        &mut self.blocks
511    }
512    fn switch_conds(&mut self, node: usize) -> Option<usize> {
513        self.get_switch_conds(node)
514    }
515}
516
517pub fn scc_handler<'tcx, T: SccHelper<'tcx>>(graph: &mut T, root: usize, scc_components: &[usize]) {
518    for &node in &scc_components[1..] {
519        graph.blocks_mut()[root].scc.nodes.push(node);
520        graph.blocks_mut()[node].scc.enter = root;
521        let nexts = graph.blocks_mut()[root].next.clone();
522        for next in nexts {
523            if !scc_components.contains(&next) {
524                //graph.blocks_mut()[root].next.insert(next);
525                let scc_exit = SccExit::new(node, next);
526                graph.blocks_mut()[root].scc.exits.push(scc_exit);
527            }
528        }
529    }
530
531    /* To ensure a resonable order of blocks within one SCC,
532     * so that the scc can be directly used for followup analysis without referencing the
533     * original graph.
534     * */
535    graph.blocks_mut()[root].scc.nodes.reverse();
536}
537
538impl<'tcx> Scc for MopGraph<'tcx> {
539    fn on_scc_found(&mut self, root: usize, scc_components: &[usize]) {
540        scc_handler(self, root, scc_components);
541    }
542    fn get_next(&mut self, root: usize) -> FxHashSet<usize> {
543        self.blocks[root].next.clone()
544    }
545    fn get_size(&mut self) -> usize {
546        self.blocks.len()
547    }
548}
549
550// Implement Display for debugging / printing purposes.
551// Prints selected fields: def_id, values, blocks, constants, discriminants, scc_indices, child_scc.
552impl<'tcx> std::fmt::Display for MopGraph<'tcx> {
553    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554        writeln!(f, "MopGraph {{")?;
555        writeln!(f, "  def_id: {:?}", self.def_id)?;
556        writeln!(f, "  values: {:?}", self.values)?;
557        writeln!(f, "  blocks: {:?}", self.blocks)?;
558        writeln!(f, "  constants: {:?}", self.constants)?;
559        writeln!(f, "  discriminants: {:?}", self.discriminants)?;
560        writeln!(f, "  terminators: {:?}", self.terminators)?;
561        write!(f, "}}")
562    }
563}