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}