rapx/analysis/core/alias_analysis/default/graph.rs
1use super::{MopFnAliasPairs, 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::{
10 AggregateKind, BasicBlock, Const, Operand, Rvalue, StatementKind, TerminatorKind,
11 UnwindAction,
12 },
13 ty::{self, TyCtxt, TypingEnv},
14};
15use rustc_span::{Span, def_id::DefId};
16use std::{
17 fmt::{self, Display},
18 vec::Vec,
19};
20
21#[derive(Clone)]
22pub struct MopGraph<'tcx> {
23 pub def_id: DefId,
24 pub tcx: TyCtxt<'tcx>,
25 // The field is used to detect dangling pointers in arguments after the function returns.
26 pub arg_size: usize,
27 pub span: Span,
28 // All values (including fields) of the function.
29 // For general variables, we use its Local as the value index;
30 // For fields, the value index is determined via auto increment.
31 pub values: Vec<Value>,
32 // All blocks of the function;
33 // Each block is initialized as a basic block of the mir;
34 // The blocks are then aggregated into strongly-connected components.
35 pub blocks: Vec<Block<'tcx>>,
36 // We record the constant value for path sensitivity.
37 pub constants: FxHashMap<usize, usize>,
38 // We record the decision of enumerate typed values for path sensitivity.
39 pub discriminants: FxHashMap<usize, usize>,
40 // a threhold to avoid path explosion.
41 pub visit_times: usize,
42 pub alias_sets: Vec<FxHashSet<usize>>,
43 // contains the return results for inter-procedure analysis.
44 pub ret_alias: MopFnAliasPairs,
45 pub terminators: Vec<TerminatorKind<'tcx>>,
46}
47
48impl<'tcx> MopGraph<'tcx> {
49 pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> MopGraph<'tcx> {
50 let fn_name = get_fn_name(tcx, def_id);
51 rap_debug!("New a MopGraph for: {:?}", fn_name);
52 // handle variables
53 let body = tcx.optimized_mir(def_id);
54 //display_mir(def_id, body);
55 let locals = &body.local_decls;
56 let arg_size = body.arg_count;
57 let mut values = Vec::<Value>::new();
58 let ty_env = TypingEnv::post_analysis(tcx, def_id);
59 for (local, local_decl) in locals.iter_enumerated() {
60 let need_drop = local_decl.ty.needs_drop(tcx, ty_env); // the type is drop
61 let may_drop = !is_not_drop(tcx, local_decl.ty);
62 let mut node = Value::new(
63 local.as_usize(),
64 local.as_usize(),
65 need_drop,
66 need_drop || may_drop,
67 );
68 node.kind = kind(local_decl.ty);
69 values.push(node);
70 }
71
72 let basicblocks = &body.basic_blocks;
73 let mut blocks = Vec::<Block<'tcx>>::new();
74 let mut discriminants = FxHashMap::default();
75 let mut terminators = Vec::new();
76
77 // handle each basicblock
78 for i in 0..basicblocks.len() {
79 let bb = &basicblocks[BasicBlock::from(i)];
80 let mut cur_bb = Block::new(i, bb.is_cleanup);
81
82 // handle general statements
83 for stmt in &bb.statements {
84 let span = stmt.source_info.span;
85 match &stmt.kind {
86 StatementKind::Assign(box (place, rvalue)) => {
87 let lv_place = *place;
88 let lv_local = place.local.as_usize();
89 cur_bb.assigned_locals.insert(lv_local);
90 match rvalue.clone() {
91 // rvalue is a Rvalue
92 Rvalue::Use(operand) => {
93 match operand {
94 Operand::Copy(rv_place) => {
95 let rv_local = rv_place.local.as_usize();
96 if values[lv_local].may_drop && values[rv_local].may_drop {
97 let assign = Assignment::new(
98 lv_place,
99 rv_place,
100 AssignType::Copy,
101 span,
102 );
103 cur_bb.assignments.push(assign);
104 }
105 }
106 Operand::Move(rv_place) => {
107 let rv_local = rv_place.local.as_usize();
108 if values[lv_local].may_drop && values[rv_local].may_drop {
109 let assign = Assignment::new(
110 lv_place,
111 rv_place,
112 AssignType::Move,
113 span,
114 );
115 cur_bb.assignments.push(assign);
116 }
117 }
118 Operand::Constant(constant) => {
119 /* We should check the correctness due to the update of rustc
120 * https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.Const.html
121 */
122 match constant.const_ {
123 Const::Ty(_ty, const_value) => {
124 if let Some(val) =
125 const_value.try_to_target_usize(tcx)
126 {
127 cur_bb.const_value.push(ConstValue::new(
128 lv_local,
129 val as usize,
130 ));
131 }
132 }
133 Const::Unevaluated(_const_value, _ty) => {}
134 Const::Val(const_value, _ty) => {
135 if let Some(scalar) =
136 const_value.try_to_scalar_int()
137 {
138 let val = scalar.to_uint(scalar.size());
139 cur_bb.const_value.push(ConstValue::new(
140 lv_local,
141 val as usize,
142 ));
143 }
144 }
145 }
146 }
147 }
148 }
149 Rvalue::Ref(_, _, rv_place)
150 | Rvalue::RawPtr(_, rv_place)
151 | Rvalue::CopyForDeref(rv_place) => {
152 let rv_local = rv_place.local.as_usize();
153 if values[lv_local].may_drop && values[rv_local].may_drop {
154 let assign =
155 Assignment::new(lv_place, rv_place, AssignType::Copy, span);
156 cur_bb.assignments.push(assign);
157 }
158 }
159 Rvalue::ShallowInitBox(operand, _) => {
160 /*
161 * Original ShllowInitBox is a two-level pointer: lvl0 -> lvl1 -> lvl2
162 * Since our alias analysis does not consider multi-level pointer,
163 * We simplify it as: lvl0
164 */
165 if !values[lv_local].fields.contains_key(&0) {
166 let mut lvl0 = Value::new(values.len(), lv_local, false, true);
167 lvl0.father = Some(FatherInfo::new(lv_local, 0));
168 values[lv_local].fields.insert(0, lvl0.index);
169 values.push(lvl0);
170 }
171 match operand {
172 Operand::Copy(rv_place) | Operand::Move(rv_place) => {
173 let rv_local = rv_place.local.as_usize();
174 if values[lv_local].may_drop && values[rv_local].may_drop {
175 let assign = Assignment::new(
176 lv_place,
177 rv_place,
178 AssignType::InitBox,
179 span,
180 );
181 cur_bb.assignments.push(assign);
182 }
183 }
184 Operand::Constant(_) => {}
185 }
186 }
187 Rvalue::Cast(_, operand, _) => match operand {
188 Operand::Copy(rv_place) => {
189 let rv_local = rv_place.local.as_usize();
190 if values[lv_local].may_drop && values[rv_local].may_drop {
191 let assign = Assignment::new(
192 lv_place,
193 rv_place,
194 AssignType::Copy,
195 span,
196 );
197 cur_bb.assignments.push(assign);
198 }
199 }
200 Operand::Move(rv_place) => {
201 let rv_local = rv_place.local.as_usize();
202 if values[lv_local].may_drop && values[rv_local].may_drop {
203 let assign = Assignment::new(
204 lv_place,
205 rv_place,
206 AssignType::Move,
207 span,
208 );
209 cur_bb.assignments.push(assign);
210 }
211 }
212 Operand::Constant(_) => {}
213 },
214 Rvalue::Aggregate(kind, operands) => {
215 match kind.as_ref() {
216 // For tuple aggregation such as _10 = (move _11, move _12)
217 // we create `_10.0 = move _11` and `_10.1 = move _12` to achieve field sensitivity
218 // and prevent transitive alias: (_10, _11) + (_10, _12) => (_11, _12)
219 AggregateKind::Tuple => {
220 let lv_ty = lv_place.ty(&body.local_decls, tcx).ty;
221 for (field_idx, operand) in operands.iter_enumerated() {
222 match operand {
223 Operand::Copy(rv_place)
224 | Operand::Move(rv_place) => {
225 let rv_local = rv_place.local.as_usize();
226 if values[lv_local].may_drop
227 && values[rv_local].may_drop
228 {
229 // Get field type from tuple or array
230 let field_ty = match lv_ty.kind() {
231 ty::Tuple(fields) => {
232 fields[field_idx.as_usize()]
233 }
234 _ => {
235 continue;
236 }
237 };
238
239 // Create lv.field_idx Place using tcx.mk_place_field
240 let lv_field_place = tcx.mk_place_field(
241 lv_place, field_idx, field_ty,
242 );
243
244 let assign = Assignment::new(
245 lv_field_place,
246 *rv_place,
247 if matches!(operand, Operand::Move(_)) {
248 AssignType::Move
249 } else {
250 AssignType::Copy
251 },
252 span,
253 );
254 cur_bb.assignments.push(assign);
255 rap_debug!(
256 "Aggregate field assignment: {:?}.{} = {:?}",
257 lv_place,
258 field_idx.as_usize(),
259 rv_place
260 );
261 }
262 }
263 Operand::Constant(_) => {
264 // Constants don't need alias analysis
265 }
266 }
267 }
268 }
269 AggregateKind::Adt(_, _, _, _, _) => {
270 // For ADTs (structs/enums), handle field assignments field-sensitively.
271 // NOTE: Here we treat the ADT similarly to tuples,
272 // but fields might be named and ADT type info is available, so more precise field indexing is possible if needed.
273 let lv_ty = lv_place.ty(&body.local_decls, tcx).ty;
274 for (field_idx, operand) in operands.iter_enumerated() {
275 match operand {
276 Operand::Copy(rv_place)
277 | Operand::Move(rv_place) => {
278 let rv_local = rv_place.local.as_usize();
279 if values[lv_local].may_drop
280 && values[rv_local].may_drop
281 {
282 // If possible, resolve field type for better analysis. Here we use tuple logic as a template.
283 let field_ty = match lv_ty.kind() {
284 ty::Adt(adt_def, substs) => {
285 // Try getting the field type if available.
286 if field_idx.as_usize()
287 < adt_def.all_fields().count()
288 {
289 adt_def
290 .all_fields()
291 .nth(field_idx.as_usize())
292 .map(|f| f.ty(tcx, substs))
293 .unwrap_or(lv_ty) // fallback
294 } else {
295 lv_ty
296 }
297 }
298 _ => lv_ty,
299 };
300
301 // Create lv.field_idx Place using tcx.mk_place_field, as for tuples.
302 let lv_field_place = tcx.mk_place_field(
303 lv_place, field_idx, field_ty,
304 );
305
306 let assign = Assignment::new(
307 lv_field_place,
308 *rv_place,
309 if matches!(operand, Operand::Move(_)) {
310 AssignType::Move
311 } else {
312 AssignType::Copy
313 },
314 span,
315 );
316 cur_bb.assignments.push(assign);
317 rap_debug!(
318 "Aggregate ADT field assignment: {:?}.{} = {:?}",
319 lv_place,
320 field_idx.as_usize(),
321 rv_place
322 );
323 }
324 }
325 Operand::Constant(_) => {
326 // Constants don't need alias analysis for this context.
327 }
328 }
329 }
330 }
331 // TODO: Support alias for array
332 AggregateKind::Array(_) => {}
333 // For other aggregate types, simply create an assignment for each aggregated operand
334 _ => {
335 for operand in operands {
336 match operand {
337 Operand::Copy(rv_place)
338 | Operand::Move(rv_place) => {
339 let rv_local = rv_place.local.as_usize();
340 if values[lv_local].may_drop
341 && values[rv_local].may_drop
342 {
343 let assign = Assignment::new(
344 lv_place,
345 rv_place,
346 AssignType::Copy,
347 span,
348 );
349 cur_bb.assignments.push(assign);
350 }
351 }
352 Operand::Constant(_) => {}
353 }
354 }
355 }
356 }
357 }
358 Rvalue::Discriminant(rv_place) => {
359 let assign =
360 Assignment::new(lv_place, rv_place, AssignType::Variant, span);
361 cur_bb.assignments.push(assign);
362 discriminants.insert(lv_local, rv_place.local.as_usize());
363 }
364 _ => {}
365 }
366 }
367 StatementKind::SetDiscriminant {
368 place: _,
369 variant_index: _,
370 } => {
371 rap_warn!("SetDiscriminant: {:?} is not handled in RAPx!", stmt);
372 }
373 _ => {}
374 }
375 }
376
377 let Some(terminator) = &bb.terminator else {
378 rap_info!(
379 "Basic block BB{} has no terminator in function {:?}",
380 i,
381 fn_name
382 );
383 continue;
384 };
385 terminators.push(terminator.kind.clone());
386 // handle terminator statements
387 match terminator.kind.clone() {
388 TerminatorKind::Goto { ref target } => {
389 cur_bb.add_next(target.as_usize());
390 }
391 TerminatorKind::SwitchInt {
392 discr: _,
393 ref targets,
394 } => {
395 cur_bb.terminator = Term::Switch(terminator.clone());
396 for (_, ref target) in targets.iter() {
397 cur_bb.add_next(target.as_usize());
398 }
399 cur_bb.add_next(targets.otherwise().as_usize());
400 }
401 TerminatorKind::Drop {
402 place: _,
403 target,
404 unwind,
405 replace: _,
406 drop: _,
407 async_fut: _,
408 } => {
409 cur_bb.add_next(target.as_usize());
410 cur_bb.terminator = Term::Drop(terminator.clone());
411 if let UnwindAction::Cleanup(target) = unwind {
412 cur_bb.add_next(target.as_usize());
413 }
414 }
415 TerminatorKind::Call {
416 ref func,
417 args: _,
418 destination: _,
419 ref target,
420 ref unwind,
421 call_source: _,
422 fn_span: _,
423 } => {
424 if let Operand::Constant(c) = func {
425 if let &ty::FnDef(id, ..) = c.ty().kind() {
426 if is_drop_fn(id) {
427 cur_bb.terminator = Term::Drop(terminator.clone());
428 } else {
429 cur_bb.terminator = Term::Call(terminator.clone());
430 }
431 }
432 } else {
433 cur_bb.terminator = Term::Call(terminator.clone());
434 }
435 if let Some(tt) = target {
436 cur_bb.add_next(tt.as_usize());
437 }
438 if let UnwindAction::Cleanup(tt) = unwind {
439 cur_bb.add_next(tt.as_usize());
440 }
441 }
442
443 TerminatorKind::Assert {
444 cond: _,
445 expected: _,
446 msg: _,
447 ref target,
448 ref unwind,
449 } => {
450 cur_bb.add_next(target.as_usize());
451 if let UnwindAction::Cleanup(target) = unwind {
452 cur_bb.add_next(target.as_usize());
453 }
454 }
455 TerminatorKind::Yield {
456 value: _,
457 ref resume,
458 resume_arg: _,
459 ref drop,
460 } => {
461 cur_bb.add_next(resume.as_usize());
462 if let Some(target) = drop {
463 cur_bb.add_next(target.as_usize());
464 }
465 }
466 TerminatorKind::FalseEdge {
467 ref real_target,
468 imaginary_target: _,
469 } => {
470 cur_bb.add_next(real_target.as_usize());
471 }
472 TerminatorKind::FalseUnwind {
473 ref real_target,
474 unwind: _,
475 } => {
476 cur_bb.add_next(real_target.as_usize());
477 }
478 TerminatorKind::InlineAsm {
479 template: _,
480 operands: _,
481 options: _,
482 line_spans: _,
483 ref unwind,
484 targets,
485 asm_macro: _,
486 } => {
487 for target in targets {
488 cur_bb.add_next(target.as_usize());
489 }
490 if let UnwindAction::Cleanup(target) = unwind {
491 cur_bb.add_next(target.as_usize());
492 }
493 }
494 _ => {}
495 }
496 blocks.push(cur_bb);
497 }
498
499 MopGraph {
500 def_id,
501 tcx,
502 span: body.span,
503 blocks,
504 values,
505 arg_size,
506 alias_sets: Vec::<FxHashSet<usize>>::new(),
507 constants: FxHashMap::default(),
508 ret_alias: MopFnAliasPairs::new(arg_size),
509 visit_times: 0,
510 discriminants,
511 terminators,
512 }
513 }
514
515 pub fn dfs_on_spanning_tree(
516 &self,
517 _index: usize,
518 _stack: &mut Vec<usize>,
519 _paths: &mut Vec<Vec<usize>>,
520 ) {
521 // TOBE REMOVED!
522 }
523
524 pub fn get_paths(&self) -> Vec<Vec<usize>> {
525 let mut paths: Vec<Vec<usize>> = Vec::new();
526 let mut stack: Vec<usize> = vec![0];
527 self.dfs_on_spanning_tree(0, &mut stack, &mut paths);
528 paths
529 }
530 pub fn get_all_branch_sub_blocks_paths(&self) -> Vec<Vec<usize>> {
531 // 1. Get all execution paths
532 let all_paths = self.get_paths();
533
534 // Use FxHashSet to collect all unique lists of dominated_scc_bbs.
535 // Vec<usize> implements Hash, so it can be inserted directly into the set.
536 let mut unique_branch_sub_blocks = FxHashSet::<Vec<usize>>::default();
537
538 // 2. Iterate over each path
539 for path in all_paths {
540 // 3. For the current path, get the corresponding dominated_scc_bbs for branch nodes
541 let branch_blocks_for_this_path = self.get_branch_sub_blocks_for_path(&path);
542 rap_debug!(
543 "Branch blocks for path {:?}: {:?}",
544 path,
545 branch_blocks_for_this_path
546 );
547 // 4. Add these dominated_scc_bbs to the set
548 // Use insert to avoid duplicates
549 unique_branch_sub_blocks.insert(branch_blocks_for_this_path);
550 }
551
552 // 5. Convert the set of unique results back to a Vec and return
553 unique_branch_sub_blocks.into_iter().collect()
554 }
555
556 pub fn get_branch_sub_blocks_for_path(&self, path: &[usize]) -> Vec<usize> {
557 let mut expanded_path = Vec::new();
558 // Use FxHashSet to track which SCCs have already been expanded in this path,
559 // preventing repeated expansion due to cycles.
560 let mut processed_scc_indices = FxHashSet::default();
561
562 for &block_idx in path {
563 // 1. Get the representative SCC index of the current basic block
564 let scc_idx = self.blocks[block_idx].scc.enter;
565
566 // 2. Try inserting scc_idx into the set. If successful (returns true),
567 // it means this SCC is encountered for the first time.
568 if processed_scc_indices.insert(scc_idx) {
569 // First time encountering this SCC: perform full expansion
570
571 // a. First, add the SCC representative itself to the path
572 expanded_path.push(scc_idx);
573
574 // b. Then, retrieve the SCC node information
575 let scc_enter = &self.blocks[scc_idx];
576
577 // c. If it has sub-blocks (i.e., it’s a multi-node SCC),
578 // append all sub-blocks to the path.
579 // dominated_scc_bbs are already ordered (topologically or near-topologically)
580 if !scc_enter.scc.nodes.is_empty() {
581 //expanded_path.extend_from_slice(&scc_enter.scc.nodes);
582 }
583 } else {
584 // SCC already seen before (e.g., due to a cycle in the path):
585 // Only add the representative node as a connector, skip internal blocks.
586 expanded_path.push(scc_idx);
587 }
588 }
589
590 expanded_path
591 }
592}
593
594pub trait SccHelper<'tcx> {
595 fn tcx(&self) -> TyCtxt<'tcx>;
596 fn defid(&self) -> DefId;
597 fn blocks(&self) -> &Vec<Block<'tcx>>; // or whatever the actual type is
598 fn blocks_mut(&mut self) -> &mut Vec<Block<'tcx>>;
599}
600
601impl<'tcx> SccHelper<'tcx> for MopGraph<'tcx> {
602 fn tcx(&self) -> TyCtxt<'tcx> {
603 self.tcx
604 }
605 fn defid(&self) -> DefId {
606 self.def_id
607 }
608 fn blocks(&self) -> &Vec<Block<'tcx>> {
609 &self.blocks
610 }
611 fn blocks_mut(&mut self) -> &mut Vec<Block<'tcx>> {
612 &mut self.blocks
613 }
614}
615
616pub fn scc_handler<'tcx, T: SccHelper<'tcx> + Scc + Clone + Display>(
617 graph: &mut T,
618 root: usize,
619 scc_components: &[usize],
620) {
621 rap_debug!(
622 "Scc found: root = {}, components = {:?}",
623 root,
624 scc_components
625 );
626 graph.blocks_mut()[root].scc.enter = root;
627 if scc_components.len() <= 1 {
628 return;
629 }
630
631 // If the scc enter is also an exit of the scc; add it to the scc exit;
632 let nexts = graph.blocks_mut()[root].next.clone();
633 for next in nexts {
634 if !&scc_components.contains(&next) {
635 let scc_exit = SccExit::new(root, next);
636 graph.blocks_mut()[root].scc.exits.insert(scc_exit);
637 }
638 }
639 // Handle other nodes of the scc;
640 for &node in &scc_components[1..] {
641 graph.blocks_mut()[root].scc.nodes.insert(node);
642 graph.blocks_mut()[node].scc.enter = root;
643 let nexts = graph.blocks_mut()[node].next.clone();
644 for next in nexts {
645 // The node is an scc exit.
646 if !&scc_components.contains(&next) {
647 let scc_exit = SccExit::new(node, next);
648 graph.blocks_mut()[root].scc.exits.insert(scc_exit);
649 }
650 // The node initiates a back edge to the scc enter.
651 if next == root {
652 graph.blocks_mut()[root].scc.backnodes.insert(node);
653 }
654 }
655 }
656
657 rap_debug!("Scc Info: {:?}", graph.blocks_mut()[root].scc);
658 // Recursively detect sub sccs within the scc.
659 // This is performed on a modified graph with the starting node and scc components only;
660 // Before modification, we have to backup corresponding information.
661 let mut backups: Vec<(usize, FxHashSet<usize>)> = Vec::new();
662
663 let block0 = &mut graph.blocks_mut()[0];
664 backups.push((0, block0.next.clone()));
665
666 // Change the next of block0 to the scc enter.
667 block0.next.clear();
668 block0.next.insert(root);
669
670 let scc_exits = graph.blocks()[root].scc.exits.clone();
671 let backnodes = graph.blocks()[root].scc.backnodes.clone();
672
673 for &node in scc_components.iter() {
674 let block = &mut graph.blocks_mut()[node];
675 if backnodes.contains(&node) {
676 backups.push((node, block.next.clone()));
677 block.next.remove(&root);
678 }
679 }
680 // isolate the scc components from other parts of the graph.
681 for exit in &scc_exits {
682 let block_to = &mut graph.blocks_mut()[exit.to];
683 backups.push((exit.to, block_to.next.clone()));
684 block_to.next.clear();
685 }
686 graph.find_scc();
687
688 // Restore the original graph.
689 for backup in &backups {
690 graph.blocks_mut()[backup.0].next = backup.1.clone();
691 }
692}
693
694impl<'tcx> Scc for MopGraph<'tcx> {
695 fn on_scc_found(&mut self, root: usize, scc_components: &[usize]) {
696 scc_handler(self, root, scc_components);
697 }
698 fn get_next(&mut self, root: usize) -> FxHashSet<usize> {
699 self.blocks[root].next.clone()
700 }
701 fn get_size(&mut self) -> usize {
702 self.blocks.len()
703 }
704}
705
706// Implement Display for debugging / printing purposes.
707// Prints selected fields: def_id, values, blocks, constants, discriminants, scc_indices, child_scc.
708impl<'tcx> std::fmt::Display for MopGraph<'tcx> {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 writeln!(f, "MopGraph {{")?;
711 writeln!(f, " def_id: {:?}", self.def_id)?;
712 writeln!(f, " values: {:?}", self.values)?;
713 writeln!(f, " blocks: {:?}", self.blocks)?;
714 writeln!(f, " constants: {:?}", self.constants)?;
715 writeln!(f, " discriminants: {:?}", self.discriminants)?;
716 writeln!(f, " terminators: {:?}", self.terminators)?;
717 write!(f, "}}")
718 }
719}