rapx/analysis/safetyflow_analysis/
mod.rs1pub 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 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 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 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 pub fn display_summary(&self) {
126 if self.units.is_empty() {
127 rap_info!("SafetyFlow: no unsafe operations detected.");
128 return;
129 }
130
131 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 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 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 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 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 for upg in &self.units {
313 collect_unit(upg);
314 }
315
316 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}