rapx/analysis/safetyflow_analysis/
mod.rs

1/*
2 * This module generates the unsafety propagation graph for each Rust module in the target crate.
3 */
4pub mod chain;
5pub mod fn_collector;
6pub mod hir_visitor;
7pub mod root;
8pub mod safetyflow_graph;
9pub mod safetyflow_unit;
10pub mod std_analysis;
11
12use crate::{
13    helpers::{draw_dot::render_dot_graphs, fn_info::*},
14    utils::source::{get_fn_name_byid, get_module_name},
15};
16use fn_collector::FnCollector;
17use root::hir_contains_unsafe;
18use rustc_hir::{Safety, def_id::DefId};
19use rustc_middle::ty::TyCtxt;
20use safetyflow_graph::{SafetyFlowEdge, SafetyFlowGraph};
21use safetyflow_unit::SafetyFlowUnit;
22use std::collections::{HashMap, HashSet};
23
24#[derive(PartialEq)]
25pub enum TargetCrate {
26    Std,
27    Other,
28}
29
30pub struct SafetyFlowAnalysis<'tcx> {
31    pub tcx: TyCtxt<'tcx>,
32    pub units: Vec<SafetyFlowUnit>,
33    pub draw: bool,
34}
35
36impl<'tcx> SafetyFlowAnalysis<'tcx> {
37    pub fn new(tcx: TyCtxt<'tcx>) -> Self {
38        Self {
39            tcx,
40            units: Vec::new(),
41            draw: false,
42        }
43    }
44
45    pub fn with_draw(mut self, draw: bool) -> Self {
46        self.draw = draw;
47        self
48    }
49
50    pub fn start(&mut self, ins: TargetCrate) {
51        // SafetyFlowAnalysis does not implement Analysis directly because
52        // std analysis has a different pipeline. This method is called from lib.rs.
53        match ins {
54            TargetCrate::Std => {
55                self.audit_std_unsafe();
56                return;
57            }
58            _ => {
59                let fns = FnCollector::collect(self.tcx);
60                for vec in fns.values() {
61                    for (body_id, _span) in vec {
62                        let def_id = self.tcx.hir_body_owner_def_id(*body_id).to_def_id();
63                        if hir_contains_unsafe(self.tcx, *body_id) {
64                            self.insert_upg(def_id);
65                        }
66                    }
67                }
68                self.display_summary();
69                if self.draw {
70                    let final_dots = self.collect_dots();
71                    rap_info!("{:?}", final_dots);
72                    render_dot_graphs(final_dots);
73                }
74            }
75        }
76    }
77
78    pub fn insert_upg(&mut self, def_id: DefId) {
79        let Some(root) = root::scan_mir(self.tcx, def_id) else {
80            return;
81        };
82
83        // If the function is entirely safe (no unsafe code, no unsafe callees,
84        // no raw pointer dereferences, and no static mutable accesses), skip.
85        if check_safety(self.tcx, def_id) == Safety::Safe
86            && root.unsafe_callees.is_empty()
87            && root.raw_ptr_locals.is_empty()
88            && root.static_muts.is_empty()
89        {
90            return;
91        }
92
93        let constructors = get_cons(self.tcx, def_id);
94        let caller_typed = append_fn_with_types(self.tcx, def_id);
95        let mut callees_typed = HashSet::new();
96        for callee in &root.unsafe_callees {
97            callees_typed.insert(append_fn_with_types(self.tcx, *callee));
98        }
99        let mut cons_typed = HashSet::new();
100        for con in &constructors {
101            cons_typed.insert(append_fn_with_types(self.tcx, *con));
102        }
103
104        // Skip processing if the caller is the dummy raw pointer dereference function
105        let caller_name = get_fn_name_byid(&def_id);
106        if let Some(_) = caller_name.find("__raw_ptr_deref_dummy") {
107            return;
108        }
109
110        let mut_methods_set = get_all_mutable_methods(self.tcx, def_id);
111        let mut_methods: HashSet<_> = mut_methods_set.keys().copied().collect();
112        let unit = SafetyFlowUnit::new(
113            caller_typed,
114            callees_typed,
115            root.raw_ptr_locals,
116            root.static_muts,
117            cons_typed,
118            mut_methods,
119        );
120        self.units.push(unit);
121    }
122
123    /// Print a human-readable text summary of all safety flow units,
124    /// grouped by module, similar to callgraph's output format.
125    pub fn display_summary(&self) {
126        if self.units.is_empty() {
127            rap_info!("SafetyFlow: no unsafe operations detected.");
128            return;
129        }
130
131        // Group units by module
132        let mut modules: HashMap<String, Vec<&SafetyFlowUnit>> = HashMap::new();
133        for unit in &self.units {
134            let mod_name = get_module_name(self.tcx, unit.caller.def_id);
135            modules.entry(mod_name).or_default().push(unit);
136        }
137        let mut mod_names: Vec<String> = modules.keys().cloned().collect();
138        mod_names.sort();
139
140        let mut total_callers = 0usize;
141        let mut total_callees = 0usize;
142        let mut total_rawptrs = 0usize;
143        let mut total_staticmuts = 0usize;
144
145        for mod_name in &mod_names {
146            let units = &modules[mod_name];
147            rap_info!("");
148            rap_info!("SafetyFlow: {} ({} function(s))", mod_name, units.len());
149
150            for unit in units {
151                let caller_name = self.tcx.def_path_str(unit.caller.def_id);
152                let safety = if unit.caller.fn_safety == Safety::Unsafe {
153                    "[Unsafe]"
154                } else {
155                    "[Safe]"
156                };
157                rap_info!("  {} {}", caller_name, safety);
158                total_callers += 1;
159
160                for callee in &unit.callees {
161                    let name = self.tcx.def_path_str(callee.def_id);
162                    rap_info!("    -> {}", name);
163                    total_callees += 1;
164                }
165
166                if !unit.raw_ptrs.is_empty() {
167                    let locals: Vec<String> =
168                        unit.raw_ptrs.iter().map(|l| format!("{:?}", l)).collect();
169                    rap_info!("    *raw* ptr deref: {}", locals.join(", "));
170                    total_rawptrs += 1;
171                }
172
173                for def_id in &unit.static_muts {
174                    let name = self.tcx.def_path_str(*def_id);
175                    rap_info!("    !static! mut: {}", name);
176                    total_staticmuts += 1;
177                }
178
179                for cons in &unit.caller_cons {
180                    let name = self.tcx.def_path_str(cons.def_id);
181                    rap_info!("    + constructor: {}", name);
182                }
183
184                for m in &unit.mut_methods {
185                    let name = self.tcx.def_path_str(*m);
186                    rap_info!("    ~ mut_self: {}", name);
187                }
188            }
189        }
190
191        rap_info!("");
192        rap_info!("============================================================");
193        rap_info!(
194            "SafetyFlow summary: {} function(s), {} call edge(s), {} raw ptr deref(s), {} static mut access(es)",
195            total_callers,
196            total_callees,
197            total_rawptrs,
198            total_staticmuts
199        );
200        rap_info!("============================================================");
201    }
202
203    /// Aggregate units into per-module DOT graphs and return them.
204    pub fn collect_dots(&self) -> Vec<(String, String)> {
205        let mut modules_data: HashMap<String, SafetyFlowGraph> = HashMap::new();
206
207        let mut collect_unit = |unit: &SafetyFlowUnit| {
208            let caller_id = unit.caller.def_id;
209            let module_name = get_module_name(self.tcx, caller_id);
210            rap_info!("module name: {:?}", module_name);
211
212            let module_data = modules_data
213                .entry(module_name)
214                .or_insert_with(SafetyFlowGraph::new);
215
216            module_data.add_node(self.tcx, unit.caller, None);
217
218            if let Some(adt) = get_adt_via_method(self.tcx, caller_id) {
219                if adt.literal_cons_enabled {
220                    let adt_node_type = FnInfo::new(adt.def_id, Safety::Safe, FnKind::Constructor);
221                    let label = format!("Literal Constructor: {}", self.tcx.item_name(adt.def_id));
222                    module_data.add_node(self.tcx, adt_node_type, Some(label));
223                    if unit.caller.fn_kind == FnKind::Method {
224                        module_data.add_edge(adt.def_id, caller_id, SafetyFlowEdge::ConsToMethod);
225                    }
226                } else {
227                    let adt_node_type = FnInfo::new(adt.def_id, Safety::Safe, FnKind::Method);
228                    let label = format!(
229                        "MutMethod Introduced by PubFields: {}",
230                        self.tcx.item_name(adt.def_id)
231                    );
232                    module_data.add_node(self.tcx, adt_node_type, Some(label));
233                    if unit.caller.fn_kind == FnKind::Method {
234                        module_data.add_edge(adt.def_id, caller_id, SafetyFlowEdge::MutToCaller);
235                    }
236                }
237            }
238
239            // Edge from associated item (constructor) to the method.
240            for cons in &unit.caller_cons {
241                module_data.add_node(self.tcx, *cons, None);
242                module_data.add_edge(
243                    cons.def_id,
244                    unit.caller.def_id,
245                    SafetyFlowEdge::ConsToMethod,
246                );
247            }
248
249            // Edge from mutable access to the caller.
250            for mut_method_id in &unit.mut_methods {
251                let node_type = get_type(self.tcx, *mut_method_id);
252                let fn_safety = check_safety(self.tcx, *mut_method_id);
253                let node = FnInfo::new(*mut_method_id, fn_safety, node_type);
254
255                module_data.add_node(self.tcx, node, None);
256                module_data.add_edge(
257                    *mut_method_id,
258                    unit.caller.def_id,
259                    SafetyFlowEdge::MutToCaller,
260                );
261            }
262
263            // Edge representing a call from caller to callee.
264            for callee in &unit.callees {
265                module_data.add_node(self.tcx, *callee, None);
266                module_data.add_edge(
267                    unit.caller.def_id,
268                    callee.def_id,
269                    SafetyFlowEdge::CallerToCallee,
270                );
271            }
272
273            rap_debug!("raw ptrs: {:?}", unit.raw_ptrs);
274            if !unit.raw_ptrs.is_empty() {
275                let all_raw_ptrs = unit
276                    .raw_ptrs
277                    .iter()
278                    .map(|p| format!("{:?}", p))
279                    .collect::<Vec<_>>()
280                    .join(", ");
281
282                match get_ptr_deref_dummy_def_id(self.tcx) {
283                    Some(dummy_fn_def_id) => {
284                        let rawptr_deref_fn =
285                            FnInfo::new(dummy_fn_def_id, Safety::Unsafe, FnKind::Intrinsic);
286                        module_data.add_node(
287                            self.tcx,
288                            rawptr_deref_fn,
289                            Some(format!("Raw ptr deref: {}", all_raw_ptrs)),
290                        );
291                        module_data.add_edge(
292                            unit.caller.def_id,
293                            dummy_fn_def_id,
294                            SafetyFlowEdge::CallerToCallee,
295                        );
296                    }
297                    None => {
298                        rap_info!("fail to find the dummy ptr deref id.");
299                    }
300                }
301            }
302
303            rap_debug!("static muts: {:?}", unit.static_muts);
304            for def_id in &unit.static_muts {
305                let node = FnInfo::new(*def_id, Safety::Unsafe, FnKind::Intrinsic);
306                module_data.add_node(self.tcx, node, None);
307                module_data.add_edge(unit.caller.def_id, *def_id, SafetyFlowEdge::CallerToCallee);
308            }
309        };
310
311        // Aggregate all Units
312        for upg in &self.units {
313            collect_unit(upg);
314        }
315
316        // Generate string of dot
317        let mut final_dots = Vec::new();
318        for (mod_name, data) in modules_data {
319            let dot = data.to_dot(&mod_name);
320            final_dots.push((mod_name, dot));
321        }
322        final_dots
323    }
324}