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