1use crate::compat::FxHashMap;
2use annotate_snippets::{Level, Renderer, Snippet};
3use rustc_span::{Span, symbol::Symbol};
4
5use crate::utils::log::{
6 are_spans_in_same_file, get_basic_block_span, get_variable_name, relative_pos_range,
7 span_to_filename, span_to_line_number, span_to_source_code,
8};
9use rustc_middle::mir::{Body, HasLocalDecls, Local};
10
11use super::drop::LocalSpot;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum BugType {
15 UseAfterFree,
16 DoubleFree,
17 DanglingPointer,
18 UseAfterFreeAndDoubleFree,
19}
20
21#[derive(Debug)]
22pub struct TyBug {
23 pub drop_spot: LocalSpot,
24 pub trigger_info: LocalSpot,
25 pub span: Span,
26 pub confidence: usize,
27 pub bug_type: BugType,
28}
29
30#[derive(Debug)]
34pub struct BugRecords {
35 pub df_bugs: FxHashMap<usize, TyBug>,
36 pub df_bugs_unwind: FxHashMap<usize, TyBug>,
37 pub uaf_bugs: FxHashMap<usize, TyBug>,
38 pub dp_bugs: FxHashMap<usize, TyBug>,
39 pub dp_bugs_unwind: FxHashMap<usize, TyBug>,
40}
41
42impl BugRecords {
43 pub fn new() -> BugRecords {
44 BugRecords {
45 df_bugs: FxHashMap::default(),
46 df_bugs_unwind: FxHashMap::default(),
47 uaf_bugs: FxHashMap::default(),
48 dp_bugs: FxHashMap::default(),
49 dp_bugs_unwind: FxHashMap::default(),
50 }
51 }
52
53 pub fn try_merge_pair(
57 &mut self,
58 drop_spot: LocalSpot,
59 trigger_bb: usize,
60 in_type: BugType,
61 ) -> Option<BugType> {
62 let pair_match =
63 |bug: &TyBug| bug.drop_spot == drop_spot && bug.trigger_info.bb == Some(trigger_bb);
64 if in_type == BugType::UseAfterFree {
65 for bug in self.df_bugs.values_mut() {
66 if pair_match(bug) {
67 bug.bug_type = BugType::UseAfterFreeAndDoubleFree;
68 return None;
69 }
70 }
71 for bug in self.df_bugs_unwind.values_mut() {
72 if pair_match(bug) {
73 bug.bug_type = BugType::UseAfterFreeAndDoubleFree;
74 return None;
75 }
76 }
77 } else {
78 let uaf_keys: Vec<usize> = self
79 .uaf_bugs
80 .iter()
81 .filter(|(_, bug)| pair_match(bug))
82 .map(|(k, _)| *k)
83 .collect();
84 if !uaf_keys.is_empty() {
85 for k in &uaf_keys {
86 self.uaf_bugs.remove(k);
87 }
88 return Some(BugType::UseAfterFreeAndDoubleFree);
89 }
90 }
91 let check = |bugs: &FxHashMap<usize, TyBug>| -> bool { bugs.values().any(pair_match) };
92 if check(&self.uaf_bugs) || check(&self.df_bugs) || check(&self.df_bugs_unwind) {
93 return None;
94 }
95 Some(in_type)
96 }
97
98 pub fn is_bug_free(&self) -> bool {
99 self.df_bugs.is_empty()
100 && self.df_bugs_unwind.is_empty()
101 && self.uaf_bugs.is_empty()
102 && self.dp_bugs.is_empty()
103 && self.dp_bugs_unwind.is_empty()
104 }
105
106 pub fn df_bugs_output<'tcx>(&self, body: &Body<'tcx>, fn_name: Symbol, span: Span) {
107 self.emit_bug_reports(
108 body,
109 &self.df_bugs,
110 fn_name,
111 span,
112 "Double free detected",
113 "Double free detected.",
114 df_uaf_detail,
115 );
116
117 self.emit_bug_reports(
118 body,
119 &self.df_bugs_unwind,
120 fn_name,
121 span,
122 "Double free detected",
123 "Double free detected during unwinding.",
124 df_uaf_detail,
125 );
126 }
127
128 pub fn uaf_bugs_output<'tcx>(&self, body: &Body<'tcx>, fn_name: Symbol, span: Span) {
129 self.emit_bug_reports(
130 body,
131 &self.uaf_bugs,
132 fn_name,
133 span,
134 "Use-after-free detected",
135 "Use-after-free detected.",
136 df_uaf_detail,
137 );
138 }
139
140 pub fn dp_bug_output<'tcx>(&self, body: &Body<'tcx>, fn_name: Symbol, span: Span) {
141 self.emit_bug_reports(
142 body,
143 &self.dp_bugs,
144 fn_name,
145 span,
146 "Dangling pointer detected",
147 "Dangling pointer detected.",
148 dp_detail,
149 );
150
151 self.emit_bug_reports(
152 body,
153 &self.dp_bugs_unwind,
154 fn_name,
155 span,
156 "Dangling pointer detected during unwinding",
157 "Dangling pointer detected during unwinding.",
158 dp_detail,
159 );
160 }
161
162 fn emit_bug_reports<'tcx, F>(
163 &self,
164 body: &Body<'tcx>,
165 bugs: &FxHashMap<usize, TyBug>,
166 fn_name: Symbol,
167 span: Span,
168 log_msg: &str,
169 title: &str,
170 detail_formatter: F,
171 ) where
172 F: Fn(&TyBug, &str, &str, &str, &str) -> String,
173 {
174 if bugs.is_empty() {
175 return;
176 }
177
178 rap_warn!("{} in function {:?}", log_msg, fn_name);
179
180 let code_source = span_to_source_code(span);
181 let filename = span_to_filename(span);
182 let renderer = Renderer::styled();
183
184 for bug in bugs.values() {
185 if are_spans_in_same_file(span, bug.span) {
186 let format_local_info = |id: usize| -> String {
187 if id >= body.local_decls().len() {
188 return format!("UNKNWON(_{}) in {}", id, fn_name.as_str());
189 }
190 let local = Local::from_usize(id);
191 let name_opt = get_variable_name(body, id);
192 let decl_span = body.local_decls[local].source_info.span;
193 let location = format!(
194 "{}:{}",
195 span_to_filename(decl_span),
196 span_to_line_number(decl_span)
197 );
198 match name_opt {
199 Some(name) => format!("_{}({}, {})", id, name, location),
200 None => format!("_{}(_, {})", id, location),
201 }
202 };
203
204 let format_bb_info = |bb_id: usize| -> String {
205 let bb_span = get_basic_block_span(body, bb_id);
206 let location = format!(
207 "{}:{}",
208 span_to_filename(bb_span),
209 span_to_line_number(bb_span)
210 );
211 format!("BB{}({})", bb_id, location)
212 };
213
214 let drop_bb = if let Some(bb) = bug.drop_spot.bb {
215 format_bb_info(bb)
216 } else {
217 String::from("NA")
218 };
219 let drop_local = if let Some(local) = bug.drop_spot.local {
220 format_local_info(local)
221 } else {
222 String::from("NA")
223 };
224 let trigger_bb = if let Some(bb) = bug.trigger_info.bb {
225 format_bb_info(bb)
226 } else {
227 String::from("NA")
228 };
229 let trigger_local = if let Some(local) = bug.trigger_info.local {
230 format_local_info(local)
231 } else {
232 String::from("NA")
233 };
234
235 let detail =
236 detail_formatter(bug, &drop_local, &trigger_local, &drop_bb, &trigger_bb);
237
238 let mut snippet = Snippet::source(&code_source)
239 .line_start(span_to_line_number(span))
240 .origin(&filename)
241 .fold(false);
242
243 snippet = snippet.annotation(
244 Level::Warning
245 .span(relative_pos_range(span, bug.span))
246 .label(&detail),
247 );
248
249 let message = Level::Warning.title(title).snippet(snippet);
250
251 rap_warn!("{}", renderer.render(message));
252 }
253 }
254 }
255}
256
257fn df_uaf_detail(
258 bug: &TyBug,
259 drop_local: &str,
260 trigger_local: &str,
261 drop_bb: &str,
262 trigger_bb: &str,
263) -> String {
264 let line = format!(
265 "{} line {}.",
266 span_to_filename(bug.span),
267 span_to_line_number(bug.span)
268 );
269 match bug.bug_type {
270 BugType::UseAfterFreeAndDoubleFree => format!(
271 "Double free / Use-after-free (confidence {}%): Location in file {}\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} is dropped at {}.",
272 bug.confidence,
273 line,
274 drop_local,
275 trigger_local,
276 drop_local,
277 drop_bb,
278 trigger_local,
279 trigger_bb
280 ),
281 BugType::UseAfterFree => format!(
282 "Use-after-free (confidence {}%): Location in file {}\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} is used at {}.",
283 bug.confidence,
284 line,
285 drop_local,
286 trigger_local,
287 drop_local,
288 drop_bb,
289 trigger_local,
290 trigger_bb
291 ),
292 _ => format!(
293 "Double free (confidence {}%): Location in file {}\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} is dropped at {}.",
294 bug.confidence,
295 line,
296 drop_local,
297 trigger_local,
298 drop_local,
299 drop_bb,
300 trigger_local,
301 trigger_bb
302 ),
303 }
304}
305
306fn dp_detail(
307 bug: &TyBug,
308 drop_local: &str,
309 trigger_local: &str,
310 drop_bb: &str,
311 _trigger_bb: &str,
312) -> String {
313 format!(
314 "Dangling pointer (confidence {}%): Location in file {} line {}.\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} became dangling.",
315 bug.confidence,
316 span_to_filename(bug.span),
317 span_to_line_number(bug.span),
318 drop_local,
319 trigger_local,
320 drop_local,
321 drop_bb,
322 trigger_local,
323 )
324}