rapx/analysis/safetyflow_analysis/
chain.rs

1use crate::helpers::mir_scan::check_safety;
2use crate::helpers::name::get_cleaned_def_path_name;
3use rustc_hir::{Safety, def_id::DefId};
4use rustc_middle::{
5    mir::{Operand, Terminator, TerminatorKind},
6    ty::{self, TyCtxt},
7};
8use std::collections::HashSet;
9
10/// DFS-based unsafe call chain analysis.
11///
12/// Starting from `def_id`, traverses all callees that are `unsafe fn`,
13/// collecting paths until a leaf (function with no unsafe callees) is reached.
14pub fn get_all_std_unsafe_chains(tcx: TyCtxt, def_id: DefId) -> Vec<Vec<String>> {
15    let mut results = Vec::new();
16    let mut visited = HashSet::new();
17    let mut current_chain = Vec::new();
18
19    dfs_find_unsafe_chains(tcx, def_id, &mut current_chain, &mut results, &mut visited);
20    results
21}
22
23fn dfs_find_unsafe_chains(
24    tcx: TyCtxt,
25    def_id: DefId,
26    current_chain: &mut Vec<String>,
27    results: &mut Vec<Vec<String>>,
28    visited: &mut HashSet<DefId>,
29) {
30    if visited.contains(&def_id) {
31        return;
32    }
33    visited.insert(def_id);
34
35    let current_func_name = get_cleaned_def_path_name(tcx, def_id);
36    current_chain.push(current_func_name.clone());
37
38    let unsafe_callees = find_unsafe_callees_in_function(tcx, def_id);
39
40    if unsafe_callees.is_empty() {
41        results.push(current_chain.clone());
42    } else {
43        for (callee_def_id, _callee_name) in unsafe_callees {
44            dfs_find_unsafe_chains(tcx, callee_def_id, current_chain, results, visited);
45        }
46    }
47
48    current_chain.pop();
49    visited.remove(&def_id);
50}
51
52fn find_unsafe_callees_in_function(tcx: TyCtxt, def_id: DefId) -> Vec<(DefId, String)> {
53    let mut callees = Vec::new();
54
55    if let Some(body) = try_get_mir(tcx, def_id) {
56        for bb in body.basic_blocks.iter() {
57            if let Some(terminator) = &bb.terminator {
58                if let Some((callee_def_id, callee_name)) = extract_unsafe_callee(tcx, terminator) {
59                    callees.push((callee_def_id, callee_name));
60                }
61            }
62        }
63    }
64
65    callees
66}
67
68fn extract_unsafe_callee(tcx: TyCtxt<'_>, terminator: &Terminator<'_>) -> Option<(DefId, String)> {
69    if let TerminatorKind::Call { func, .. } = &terminator.kind {
70        if let Operand::Constant(func_constant) = func {
71            if let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() {
72                if check_safety(tcx, *callee_def_id) == Safety::Unsafe {
73                    let func_name = get_cleaned_def_path_name(tcx, *callee_def_id);
74                    return Some((*callee_def_id, func_name));
75                }
76            }
77        }
78    }
79    None
80}
81
82fn try_get_mir(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&rustc_middle::mir::Body<'_>> {
83    if tcx.is_mir_available(def_id) {
84        Some(tcx.optimized_mir(def_id))
85    } else {
86        None
87    }
88}
89
90pub fn print_unsafe_chains(chains: &[Vec<String>]) {
91    if chains.is_empty() {
92        return;
93    }
94
95    println!("==============================");
96    println!("Found {} unsafe call chain(s):", chains.len());
97    for (i, chain) in chains.iter().enumerate() {
98        println!("Chain {}:", i + 1);
99        for (j, func_name) in chain.iter().enumerate() {
100            let indent = "  ".repeat(j);
101            println!("{}{}-> {}", indent, if j > 0 { " " } else { "" }, func_name);
102        }
103        println!();
104    }
105}