rapx/analysis/alias_analysis/mfp/
intraproc.rs

1use crate::compat::FxHashMap;
2use crate::compat::Spanned;
3use rustc_hir::def_id::DefId;
4use rustc_middle::{
5    mir::{
6        Body, CallReturnPlaces, Location, Operand, Place, Rvalue, Statement, StatementKind,
7        Terminator, TerminatorEdges, TerminatorKind,
8    },
9    ty::{self, Ty, TyCtxt, TypingEnv},
10};
11use rustc_mir_dataflow::{Analysis, JoinSemiLattice, fmt::DebugWithContext};
12use std::cell::RefCell;
13use std::rc::Rc;
14
15use super::super::{FnAliasMap, FnAliasPairs};
16use super::transfer;
17use crate::analysis::alias_analysis::default::types::is_not_drop;
18
19/// Apply a function summary to the current state
20fn apply_function_summary<'tcx>(
21    state: &mut AliasDomain,
22    destination: Place<'tcx>,
23    args: &[Operand<'tcx>],
24    summary: &FnAliasPairs,
25    place_info: &PlaceInfo<'tcx>,
26) {
27    // Convert destination to PlaceId
28    let dest_id = transfer::mir_place_to_place_id(destination);
29
30    // Build a mapping from callee's argument indices to caller's PlaceIds
31    // Index 0 is return value, indices 1+ are arguments
32    let mut actual_places = vec![dest_id.clone()];
33    for arg in args {
34        if let Some(arg_id) = transfer::operand_to_place_id(arg) {
35            actual_places.push(arg_id);
36        } else {
37            // If argument is not a place (e.g., constant), use a dummy
38            actual_places.push(PlaceId::Local(usize::MAX));
39        }
40    }
41
42    // Apply each alias pair from the summary
43    for alias_pair in summary.aliases() {
44        let left_idx = alias_pair.left_local();
45        let right_idx = alias_pair.right_local();
46
47        // Check bounds
48        if left_idx >= actual_places.len() || right_idx >= actual_places.len() {
49            continue;
50        }
51
52        // Skip if either place is a dummy (constant argument)
53        // Dummy places use usize::MAX as a sentinel value
54        if actual_places[left_idx] == PlaceId::Local(usize::MAX)
55            || actual_places[right_idx] == PlaceId::Local(usize::MAX)
56        {
57            continue;
58        }
59
60        // Get actual places with field projections
61        let mut left_place = actual_places[left_idx].clone();
62        for &field_idx in alias_pair.lhs_fields() {
63            left_place = left_place.project_field(field_idx);
64        }
65
66        let mut right_place = actual_places[right_idx].clone();
67        for &field_idx in alias_pair.rhs_fields() {
68            right_place = right_place.project_field(field_idx);
69        }
70
71        // Get indices and union
72        if let (Some(left_place_idx), Some(right_place_idx)) = (
73            place_info.get_index(&left_place),
74            place_info.get_index(&right_place),
75        ) {
76            let left_may_drop = place_info.may_drop(left_place_idx);
77            let right_may_drop = place_info.may_drop(right_place_idx);
78            if left_may_drop && right_may_drop {
79                state.union(left_place_idx, right_place_idx);
80            }
81        }
82    }
83}
84
85/// Conservative fallback for library functions without MIR
86/// Assumes return value may alias with any may_drop argument
87fn apply_conservative_alias_for_call<'tcx>(
88    state: &mut AliasDomain,
89    destination: Place<'tcx>,
90    args: &[Spanned<rustc_middle::mir::Operand<'tcx>>],
91    place_info: &PlaceInfo<'tcx>,
92) {
93    // Get destination place
94    let dest_id = transfer::mir_place_to_place_id(destination);
95    let dest_idx = match place_info.get_index(&dest_id) {
96        Some(idx) => idx,
97        None => {
98            return;
99        }
100    };
101
102    // Only apply if destination may_drop
103    if !place_info.may_drop(dest_idx) {
104        return;
105    }
106
107    // Union with all may_drop arguments
108    for (_i, arg) in args.iter().enumerate() {
109        if let Some(arg_id) = transfer::operand_to_place_id(&arg.node) {
110            if let Some(arg_idx) = place_info.get_index(&arg_id) {
111                if place_info.may_drop(arg_idx) {
112                    // Create conservative alias
113                    state.union(dest_idx, arg_idx);
114
115                    // Sync fields for more precision
116                    transfer::sync_fields(state, &dest_id, &arg_id, place_info);
117                }
118            }
119        }
120    }
121}
122
123/// Place identifier supporting field-sensitive analysis
124#[derive(Debug, Clone, PartialEq, Eq, Hash)]
125pub enum PlaceId {
126    /// A local variable (e.g., _1)
127    Local(usize),
128    /// A field projection (e.g., _1.0)
129    Field {
130        base: Box<PlaceId>,
131        field_idx: usize,
132    },
133}
134
135impl PlaceId {
136    /// Get the root local of this place
137    pub fn root_local(&self) -> usize {
138        match self {
139            PlaceId::Local(idx) => *idx,
140            PlaceId::Field { base, .. } => base.root_local(),
141        }
142    }
143
144    /// Create a field projection
145    pub fn project_field(&self, field_idx: usize) -> PlaceId {
146        PlaceId::Field {
147            base: Box::new(self.clone()),
148            field_idx,
149        }
150    }
151
152    /// Check if this place has the given place as a prefix
153    /// e.g., _1.0.1 has prefix _1, _1.0.1 has prefix _1.0, but not _2
154    pub fn has_prefix(&self, prefix: &PlaceId) -> bool {
155        if self == prefix {
156            return true;
157        }
158
159        match self {
160            PlaceId::Local(_) => false,
161            PlaceId::Field { base, .. } => base.has_prefix(prefix),
162        }
163    }
164}
165
166/// Information about all places in a function
167#[derive(Clone)]
168pub struct PlaceInfo<'tcx> {
169    /// Mapping from PlaceId to index
170    place_to_index: FxHashMap<PlaceId, usize>,
171    /// Mapping from index to PlaceId
172    index_to_place: Vec<PlaceId>,
173    /// Whether each place may need drop
174    may_drop: Vec<bool>,
175    /// Whether each place needs drop
176    need_drop: Vec<bool>,
177    /// Total number of places
178    num_places: usize,
179    _phantom: std::marker::PhantomData<&'tcx ()>,
180}
181
182impl<'tcx> PlaceInfo<'tcx> {
183    /// Create a new PlaceInfo with initial capacity
184    pub fn new() -> Self {
185        PlaceInfo {
186            place_to_index: FxHashMap::default(),
187            index_to_place: Vec::new(),
188            may_drop: Vec::new(),
189            need_drop: Vec::new(),
190            num_places: 0,
191            _phantom: std::marker::PhantomData,
192        }
193    }
194
195    /// Build PlaceInfo from MIR body
196    pub fn build(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) -> Self {
197        let mut info = Self::new();
198        let ty_env = TypingEnv::post_analysis(tcx, def_id);
199
200        // Register all locals first
201        for (local, local_decl) in body.local_decls.iter_enumerated() {
202            let ty = local_decl.ty;
203            let need_drop = ty.needs_drop(tcx, ty_env);
204            let may_drop = !is_not_drop(tcx, ty);
205
206            let place_id = PlaceId::Local(local.as_usize());
207            info.register_place(place_id.clone(), may_drop, need_drop);
208
209            // Create fields for this type recursively
210            info.create_fields_for_type(tcx, ty, place_id, 0, 0, ty_env);
211        }
212
213        info
214    }
215
216    /// Recursively create field PlaceIds for a type
217    fn create_fields_for_type(
218        &mut self,
219        tcx: TyCtxt<'tcx>,
220        ty: Ty<'tcx>,
221        base_place: PlaceId,
222        field_depth: usize,
223        deref_depth: usize,
224        ty_env: TypingEnv<'tcx>,
225    ) {
226        // Limit recursion depth to avoid infinite loops
227        const MAX_FIELD_DEPTH: usize = 5;
228        const MAX_DEREF_DEPTH: usize = 3;
229        if field_depth >= MAX_FIELD_DEPTH || deref_depth >= MAX_DEREF_DEPTH {
230            return;
231        }
232
233        match ty.kind() {
234            // For references, recursively create fields for the inner type
235            // This allows handling patterns like (*_1).0 where _1 is &T
236            ty::Ref(_, inner_ty, _) => {
237                self.create_fields_for_type(
238                    tcx,
239                    *inner_ty,
240                    base_place,
241                    field_depth,
242                    deref_depth + 1,
243                    ty_env,
244                );
245            }
246            // For raw pointers, also create fields for the inner type
247            ty::RawPtr(inner_ty, _) => {
248                self.create_fields_for_type(
249                    tcx,
250                    *inner_ty,
251                    base_place,
252                    field_depth,
253                    deref_depth + 1,
254                    ty_env,
255                );
256            }
257            // For ADTs (structs/enums), create fields
258            ty::Adt(adt_def, substs) => {
259                for (field_idx, field) in adt_def.all_fields().enumerate() {
260                    #[cfg(not(rapx_rustc_ge_198))]
261                    let field_ty = field.ty(tcx, substs);
262                    #[cfg(rapx_rustc_ge_198)]
263                    let field_ty = field.ty(tcx, substs).skip_norm_wip();
264                    let field_place = base_place.project_field(field_idx);
265
266                    // Check if field may/need drop
267                    // Use the ty_env from the function context to avoid param-env mismatch
268                    let need_drop = field_ty.needs_drop(tcx, ty_env);
269
270                    // Special handling: when deref_depth > 0, we are creating fields for
271                    // a type accessed through a reference/pointer (e.g., (*_1).0 where _1 is &T).
272                    // In this case, even if the field type itself doesn't need drop (e.g., i32),
273                    // we should still track it for alias analysis because it represents memory
274                    // accessed through a reference.
275                    let may_drop = if deref_depth > 0 {
276                        true
277                    } else {
278                        !is_not_drop(tcx, field_ty)
279                    };
280
281                    self.register_place(field_place.clone(), may_drop, need_drop);
282
283                    // Recursively create nested fields
284                    self.create_fields_for_type(
285                        tcx,
286                        field_ty,
287                        field_place,
288                        field_depth + 1,
289                        deref_depth,
290                        ty_env,
291                    );
292                }
293            }
294            // For tuples, create fields
295            ty::Tuple(fields) => {
296                for (field_idx, field_ty) in fields.iter().enumerate() {
297                    let field_place = base_place.project_field(field_idx);
298
299                    // For tuples, we conservatively check drop requirements
300                    // Note: Tuple fields don't have a specific DefId, so we use a simpler check
301
302                    // Special handling: when deref_depth > 0, we are creating fields for
303                    // a type accessed through a reference/pointer. Even if the field type
304                    // doesn't need drop, we should track it for alias analysis.
305                    let may_drop = if deref_depth > 0 {
306                        true
307                    } else {
308                        !is_not_drop(tcx, field_ty)
309                    };
310
311                    // For need_drop, use the ty_env from the function context
312                    let need_drop = field_ty.needs_drop(tcx, ty_env);
313
314                    self.register_place(field_place.clone(), may_drop, need_drop);
315
316                    // Recursively create nested fields
317                    self.create_fields_for_type(
318                        tcx,
319                        field_ty,
320                        field_place,
321                        field_depth + 1,
322                        deref_depth,
323                        ty_env,
324                    );
325                }
326            }
327            _ => {
328                // Other types don't have explicit fields we track
329            }
330        }
331    }
332
333    /// Register a new place and return its index
334    pub fn register_place(&mut self, place_id: PlaceId, may_drop: bool, need_drop: bool) -> usize {
335        if let Some(&idx) = self.place_to_index.get(&place_id) {
336            return idx;
337        }
338
339        let idx = self.num_places;
340        self.place_to_index.insert(place_id.clone(), idx);
341        self.index_to_place.push(place_id);
342        self.may_drop.push(may_drop);
343        self.need_drop.push(need_drop);
344        self.num_places += 1;
345        idx
346    }
347
348    /// Get the index of a place
349    pub fn get_index(&self, place_id: &PlaceId) -> Option<usize> {
350        self.place_to_index.get(place_id).copied()
351    }
352
353    /// Get the PlaceId for an index
354    pub fn get_place(&self, idx: usize) -> Option<&PlaceId> {
355        self.index_to_place.get(idx)
356    }
357
358    /// Check if a place may drop
359    pub fn may_drop(&self, idx: usize) -> bool {
360        self.may_drop.get(idx).copied().unwrap_or(false)
361    }
362
363    /// Check if a place needs drop
364    pub fn need_drop(&self, idx: usize) -> bool {
365        self.need_drop.get(idx).copied().unwrap_or(false)
366    }
367
368    /// Get total number of places
369    pub fn num_places(&self) -> usize {
370        self.num_places
371    }
372}
373
374/// Alias domain using Union-Find data structure
375#[derive(Clone, PartialEq, Eq, Debug)]
376pub struct AliasDomain {
377    /// Parent array for Union-Find
378    parent: Vec<usize>,
379    /// Rank for path compression
380    rank: Vec<usize>,
381}
382
383impl AliasDomain {
384    /// Create a new domain with n places
385    pub fn new(num_places: usize) -> Self {
386        AliasDomain {
387            parent: (0..num_places).collect(),
388            rank: vec![0; num_places],
389        }
390    }
391
392    /// Find the representative of a place (with path compression)
393    pub fn find(&mut self, idx: usize) -> usize {
394        if self.parent[idx] != idx {
395            self.parent[idx] = self.find(self.parent[idx]);
396        }
397        self.parent[idx]
398    }
399
400    /// Union two places (returns true if they were not already aliased)
401    pub fn union(&mut self, idx1: usize, idx2: usize) -> bool {
402        let root1 = self.find(idx1);
403        let root2 = self.find(idx2);
404
405        if root1 == root2 {
406            return false;
407        }
408
409        // Union by rank
410        if self.rank[root1] < self.rank[root2] {
411            self.parent[root1] = root2;
412        } else if self.rank[root1] > self.rank[root2] {
413            self.parent[root2] = root1;
414        } else {
415            self.parent[root2] = root1;
416            self.rank[root1] += 1;
417        }
418
419        true
420    }
421
422    /// Check if two places are aliased
423    pub fn are_aliased(&mut self, idx1: usize, idx2: usize) -> bool {
424        self.find(idx1) == self.find(idx2)
425    }
426
427    /// Remove all aliases for a place (used in kill phase)
428    /// This correctly handles the case where idx is the root of a connected component
429    pub fn remove_aliases(&mut self, idx: usize) {
430        // Find the root of the connected component containing idx
431        let root = self.find(idx);
432
433        // Collect all nodes in the same connected component
434        let mut component_nodes = Vec::new();
435        for i in 0..self.parent.len() {
436            if self.find(i) == root {
437                component_nodes.push(i);
438            }
439        }
440
441        // Remove idx from the component
442        component_nodes.retain(|&i| i != idx);
443
444        // Isolate idx
445        self.parent[idx] = idx;
446        self.rank[idx] = 0;
447
448        // Rebuild the remaining component if it's not empty
449        if !component_nodes.is_empty() {
450            // Reset all nodes in the remaining component
451            for &i in &component_nodes {
452                self.parent[i] = i;
453                self.rank[i] = 0;
454            }
455
456            // Re-union them together (excluding idx)
457            let first = component_nodes[0];
458            for &i in &component_nodes[1..] {
459                self.union(first, i);
460            }
461        }
462    }
463
464    /// Remove all aliases for a place and all its field projections
465    /// This ensures that when lv is killed, all lv.* are also killed
466    pub fn remove_aliases_with_prefix(&mut self, place_id: &PlaceId, place_info: &PlaceInfo) {
467        // Collect all place indices that have place_id as a prefix
468        let mut indices_to_remove = Vec::new();
469
470        for idx in 0..self.parent.len() {
471            if let Some(pid) = place_info.get_place(idx) {
472                if pid.has_prefix(place_id) {
473                    indices_to_remove.push(idx);
474                }
475            }
476        }
477
478        // Remove aliases for all collected indices
479        for idx in indices_to_remove {
480            self.remove_aliases(idx);
481        }
482    }
483
484    /// Get all alias pairs (for debugging/summary extraction)
485    pub fn get_all_alias_pairs(&self) -> Vec<(usize, usize)> {
486        let mut pairs = Vec::new();
487        let mut domain_clone = self.clone();
488
489        for i in 0..self.parent.len() {
490            for j in (i + 1)..self.parent.len() {
491                if domain_clone.are_aliased(i, j) {
492                    pairs.push((i, j));
493                }
494            }
495        }
496
497        pairs
498    }
499}
500
501impl JoinSemiLattice for AliasDomain {
502    fn join(&mut self, other: &Self) -> bool {
503        // Safety check: both domains must have the same size
504        // This ensures they represent the same place space
505        assert_eq!(
506            self.parent.len(),
507            other.parent.len(),
508            "AliasDomain::join: size mismatch (self: {}, other: {})",
509            self.parent.len(),
510            other.parent.len()
511        );
512
513        let mut changed = false;
514
515        // Get all alias pairs from other and union them in self
516        let pairs = other.get_all_alias_pairs();
517        for (i, j) in pairs {
518            if self.union(i, j) {
519                changed = true;
520            }
521        }
522
523        changed
524    }
525}
526
527impl DebugWithContext<FnAliasAnalyzer<'_>> for AliasDomain {}
528
529/// Intraprocedural alias analyzer
530pub struct FnAliasAnalyzer<'tcx> {
531    pub tcx: TyCtxt<'tcx>,
532    _body: &'tcx Body<'tcx>,
533    _def_id: DefId,
534    place_info: PlaceInfo<'tcx>,
535    /// Function summaries for interprocedural analysis
536    fn_summaries: Rc<RefCell<FnAliasMap>>,
537    /// (Debug) Number of BBs we have iterated through
538    pub bb_iter_cnt: RefCell<usize>,
539}
540
541impl<'tcx> FnAliasAnalyzer<'tcx> {
542    /// Create a new analyzer for a function
543    pub fn new(
544        tcx: TyCtxt<'tcx>,
545        def_id: DefId,
546        body: &'tcx Body<'tcx>,
547        fn_summaries: Rc<RefCell<FnAliasMap>>,
548    ) -> Self {
549        // Build place info by analyzing the body
550        let place_info = PlaceInfo::build(tcx, def_id, body);
551        FnAliasAnalyzer {
552            tcx,
553            _body: body,
554            _def_id: def_id,
555            place_info,
556            fn_summaries,
557            bb_iter_cnt: RefCell::new(0),
558        }
559    }
560
561    /// Get the place info
562    pub fn place_info(&self) -> &PlaceInfo<'tcx> {
563        &self.place_info
564    }
565}
566
567// Implement Analysis for FnAliasAnalyzer
568// rustc >= 1.93 changed trait methods from &mut self to &self.
569// We provide two impl blocks conditionally compiled for the correct rustc version.
570#[cfg(not(rapx_rustc_ge_193))]
571impl<'tcx> Analysis<'tcx> for FnAliasAnalyzer<'tcx> {
572    type Domain = AliasDomain;
573
574    const NAME: &'static str = "FnAliasAnalyzer";
575
576    fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
577        AliasDomain::new(self.place_info.num_places())
578    }
579
580    fn initialize_start_block(&self, _body: &Body<'tcx>, _state: &mut Self::Domain) {}
581
582    fn apply_primary_statement_effect(
583        &mut self,
584        state: &mut Self::Domain,
585        statement: &Statement<'tcx>,
586        _location: Location,
587    ) {
588        apply_statement_effect(self, state, statement)
589    }
590
591    fn apply_primary_terminator_effect<'mir>(
592        &mut self,
593        state: &mut Self::Domain,
594        terminator: &'mir Terminator<'tcx>,
595        _location: Location,
596    ) -> TerminatorEdges<'mir, 'tcx> {
597        apply_terminator_effect(self, state, terminator)
598    }
599
600    fn apply_call_return_effect(
601        &mut self,
602        _state: &mut Self::Domain,
603        _block: rustc_middle::mir::BasicBlock,
604        _return_places: CallReturnPlaces<'_, 'tcx>,
605    ) {
606    }
607}
608
609#[cfg(rapx_rustc_ge_193)]
610impl<'tcx> Analysis<'tcx> for FnAliasAnalyzer<'tcx> {
611    type Domain = AliasDomain;
612
613    const NAME: &'static str = "FnAliasAnalyzer";
614
615    fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
616        AliasDomain::new(self.place_info.num_places())
617    }
618
619    fn initialize_start_block(&self, _body: &Body<'tcx>, _state: &mut Self::Domain) {}
620
621    fn apply_primary_statement_effect(
622        &self,
623        state: &mut Self::Domain,
624        statement: &Statement<'tcx>,
625        _location: Location,
626    ) {
627        apply_statement_effect(self, state, statement)
628    }
629
630    fn apply_primary_terminator_effect<'mir>(
631        &self,
632        state: &mut Self::Domain,
633        terminator: &'mir Terminator<'tcx>,
634        _location: Location,
635    ) -> TerminatorEdges<'mir, 'tcx> {
636        apply_terminator_effect(self, state, terminator)
637    }
638
639    fn apply_call_return_effect(
640        &self,
641        _state: &mut Self::Domain,
642        _block: rustc_middle::mir::BasicBlock,
643        _return_places: CallReturnPlaces<'_, 'tcx>,
644    ) {
645    }
646}
647
648fn apply_statement_effect<'tcx>(
649    analyzer: &FnAliasAnalyzer<'tcx>,
650    state: &mut AliasDomain,
651    statement: &Statement<'tcx>,
652) {
653    match &statement.kind {
654        StatementKind::Assign(assign) => {
655            let (lv, rvalue) = &**assign;
656            match rvalue {
657                Rvalue::Use(operand, ..) => {
658                    transfer::transfer_assign(state, *lv, operand, &analyzer.place_info);
659                }
660                Rvalue::Ref(_, _, rv) | Rvalue::RawPtr(_, rv) => {
661                    transfer::transfer_ref(state, *lv, *rv, &analyzer.place_info);
662                }
663                Rvalue::CopyForDeref(rv) => {
664                    transfer::transfer_ref(state, *lv, *rv, &analyzer.place_info);
665                }
666                Rvalue::Cast(_, operand, _) => {
667                    transfer::transfer_assign(state, *lv, operand, &analyzer.place_info);
668                }
669                Rvalue::Aggregate(_, operands) => {
670                    let operand_slice: Vec<_> = operands.iter().map(|op| op.clone()).collect();
671                    transfer::transfer_aggregate(state, *lv, &operand_slice, &analyzer.place_info);
672                }
673                #[cfg(not(rapx_rustc_ge_196))]
674                Rvalue::ShallowInitBox(operand, _) => {
675                    transfer::transfer_assign(state, *lv, operand, &analyzer.place_info);
676                }
677                _ => {}
678            }
679        }
680        _ => {}
681    }
682}
683
684fn apply_terminator_effect<'tcx, 'mir>(
685    analyzer: &FnAliasAnalyzer<'tcx>,
686    state: &mut AliasDomain,
687    terminator: &'mir Terminator<'tcx>,
688) -> TerminatorEdges<'mir, 'tcx> {
689    {
690        *analyzer.bb_iter_cnt.borrow_mut() += 1;
691    }
692    match &terminator.kind {
693        TerminatorKind::Call {
694            target,
695            destination,
696            args,
697            func,
698            ..
699        } => {
700            let operand_slice: Vec<_> = args
701                .iter()
702                .map(|spanned_arg| spanned_arg.node.clone())
703                .collect();
704            transfer::transfer_call(state, *destination, &operand_slice, &analyzer.place_info);
705
706            if let Operand::Constant(c) = func {
707                if let ty::FnDef(callee_def_id, _) = c.ty().kind() {
708                    let fn_summaries = analyzer.fn_summaries.borrow();
709                    if let Some(summary) = fn_summaries.get(callee_def_id) {
710                        apply_function_summary(
711                            state,
712                            *destination,
713                            &operand_slice,
714                            summary,
715                            &analyzer.place_info,
716                        );
717                    } else {
718                        drop(fn_summaries);
719                        apply_conservative_alias_for_call(
720                            state,
721                            *destination,
722                            args,
723                            &analyzer.place_info,
724                        );
725                    }
726                }
727            }
728
729            if let Some(target_bb) = target {
730                TerminatorEdges::Single(*target_bb)
731            } else {
732                TerminatorEdges::None
733            }
734        }
735
736        TerminatorKind::Drop { target, .. } => TerminatorEdges::Single(*target),
737
738        TerminatorKind::SwitchInt { discr, targets } => {
739            TerminatorEdges::SwitchInt { discr, targets }
740        }
741
742        TerminatorKind::Assert { target, .. } => TerminatorEdges::Single(*target),
743
744        TerminatorKind::Goto { target } => TerminatorEdges::Single(*target),
745
746        TerminatorKind::Return => TerminatorEdges::None,
747
748        _ => TerminatorEdges::None,
749    }
750}