rapx/analysis/safetyflow_analysis/
chain.rs1use 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
10pub 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}