1use annotate_snippets::{Level, Renderer, Snippet};
2use rustc_data_structures::fx::FxHashMap;
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)]
14pub struct TyBug {
15 pub drop_spot: LocalSpot,
16 pub trigger_info: LocalSpot,
17 pub prop_chain: Vec<usize>,
18 pub span: Span,
19 pub confidence: usize,
20}
21
22#[derive(Debug)]
26pub struct BugRecords {
27 pub df_bugs: FxHashMap<usize, TyBug>,
28 pub df_bugs_unwind: FxHashMap<usize, TyBug>,
29 pub uaf_bugs: FxHashMap<usize, TyBug>,
30 pub dp_bugs: FxHashMap<usize, TyBug>,
31 pub dp_bugs_unwind: FxHashMap<usize, TyBug>,
32}
33
34impl BugRecords {
35 pub fn new() -> BugRecords {
36 BugRecords {
37 df_bugs: FxHashMap::default(),
38 df_bugs_unwind: FxHashMap::default(),
39 uaf_bugs: FxHashMap::default(),
40 dp_bugs: FxHashMap::default(),
41 dp_bugs_unwind: FxHashMap::default(),
42 }
43 }
44
45 pub fn is_bug_free(&self) -> bool {
46 self.df_bugs.is_empty()
47 && self.df_bugs_unwind.is_empty()
48 && self.uaf_bugs.is_empty()
49 && self.dp_bugs.is_empty()
50 && self.dp_bugs_unwind.is_empty()
51 }
52
53 pub fn df_bugs_output<'tcx>(&self, body: &Body<'tcx>, fn_name: Symbol, span: Span) {
54 self.emit_bug_reports(
55 body, &self.df_bugs, fn_name, span,
56 "Double free detected",
57 "Double free detected.",
58 |bug, drop_local, trigger_local, drop_bb, trigger_bb| {
59 format!(
60 "Double free (confidence {}%): Location in file {} line {}.\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} is dropped at {}.",
61 bug.confidence,
62 span_to_filename(bug.span),
63 span_to_line_number(bug.span),
64 drop_local, trigger_local,
65 drop_local, drop_bb,
66 trigger_local, trigger_bb
67 )
68 }
69 );
70
71 self.emit_bug_reports(
72 body, &self.df_bugs_unwind, fn_name, span,
73 "Double free detected",
74 "Double free detected during unwinding.",
75 |bug, drop_local, trigger_local, drop_bb, trigger_bb| {
76 format!(
77 "Double free (confidence {}%): Location in file {} line {}.\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} is dropped at {}.",
78 bug.confidence,
79 span_to_filename(bug.span),
80 span_to_line_number(bug.span),
81 drop_local, trigger_local,
82 drop_local, drop_bb,
83 trigger_local, trigger_bb
84 )
85 }
86 );
87 }
88
89 pub fn uaf_bugs_output<'tcx>(&self, body: &Body<'tcx>, fn_name: Symbol, span: Span) {
90 self.emit_bug_reports(
91 body, &self.uaf_bugs, fn_name, span,
92 "Use-after-free detected",
93 "Use-after-free detected.",
94 |bug, drop_local, trigger_local, drop_bb, trigger_bb| {
95 format!(
96 "Use-after-free (confidence {}%): Location in file {} line {}.\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} is used at {}.",
97 bug.confidence,
98 span_to_filename(bug.span),
99 span_to_line_number(bug.span),
100 drop_local, trigger_local,
101 drop_local, drop_bb,
102 trigger_local, trigger_bb
103 )
104 }
105 );
106 }
107
108 pub fn dp_bug_output<'tcx>(&self, body: &Body<'tcx>, fn_name: Symbol, span: Span) {
109 self.emit_bug_reports(
110 body, &self.dp_bugs, fn_name, span,
111 "Dangling pointer detected",
112 "Dangling pointer detected.",
113 |bug, drop_local, trigger_local, drop_bb, _trigger_bb| {
114 format!(
115 "Dangling pointer (confidence {}%): Location in file {} line {}.\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} became dangling.",
116 bug.confidence,
117 span_to_filename(bug.span),
118 span_to_line_number(bug.span),
119 drop_local, trigger_local,
120 drop_local, drop_bb,
121 trigger_local,
122 )
123 }
124 );
125
126 self.emit_bug_reports(
127 body, &self.dp_bugs_unwind, fn_name, span,
128 "Dangling pointer detected during unwinding",
129 "Dangling pointer detected during unwinding.",
130 |bug, drop_local, trigger_local, drop_bb, _trigger_bb| {
131 format!(
132 "Dangling pointer (confidence {}%): Location in file {} line {}.\n | MIR detail: Value {} and {} are alias.\n | MIR detail: {} is dropped at {}; {} became dangling.",
133 bug.confidence,
134 span_to_filename(bug.span),
135 span_to_line_number(bug.span),
136 drop_local, trigger_local,
137 drop_local, drop_bb,
138 trigger_local,
139 )
140 }
141 );
142 }
143
144 fn emit_bug_reports<'tcx, F>(
145 &self,
146 body: &Body<'tcx>,
147 bugs: &FxHashMap<usize, TyBug>,
148 fn_name: Symbol,
149 span: Span,
150 log_msg: &str,
151 title: &str,
152 detail_formatter: F,
153 ) where
154 F: Fn(&TyBug, &str, &str, &str, &str) -> String,
155 {
156 if bugs.is_empty() {
157 return;
158 }
159
160 rap_warn!("{} in function {:?}", log_msg, fn_name);
161
162 let code_source = span_to_source_code(span);
163 let filename = span_to_filename(span);
164 let renderer = Renderer::styled();
165
166 for bug in bugs.values() {
167 if are_spans_in_same_file(span, bug.span) {
168 let format_local_info = |id: usize| -> String {
169 if id >= body.local_decls().len() {
170 return format!("UNKNWON(_{}) in {}", id, fn_name.as_str());
171 }
172 let local = Local::from_usize(id);
173 let name_opt = get_variable_name(body, id);
174 let decl_span = body.local_decls[local].source_info.span;
175 let location = format!(
176 "{}:{}",
177 span_to_filename(decl_span),
178 span_to_line_number(decl_span)
179 );
180 match name_opt {
181 Some(name) => format!("_{}({}, {})", id, name, location),
182 None => format!("_{}(_, {})", id, location),
183 }
184 };
185
186 let format_bb_info = |bb_id: usize| -> String {
187 let bb_span = get_basic_block_span(body, bb_id);
188 let location = format!(
189 "{}:{}",
190 span_to_filename(bb_span),
191 span_to_line_number(bb_span)
192 );
193 format!("BB{}({})", bb_id, location)
194 };
195
196 let drop_bb = if let Some(bb) = bug.drop_spot.bb {
197 format_bb_info(bb)
198 } else {
199 String::from("NA")
200 };
201 let drop_local = if let Some(local) = bug.drop_spot.local {
202 format_local_info(local)
203 } else {
204 String::from("NA")
205 };
206 let trigger_bb = if let Some(bb) = bug.trigger_info.bb {
207 format_bb_info(bb)
208 } else {
209 String::from("NA")
210 };
211 let trigger_local = if let Some(local) = bug.trigger_info.local {
212 format_local_info(local)
213 } else {
214 String::from("NA")
215 };
216
217 let detail =
218 detail_formatter(bug, &drop_local, &trigger_local, &drop_bb, &trigger_bb);
219
220 let mut snippet = Snippet::source(&code_source)
221 .line_start(span_to_line_number(span))
222 .origin(&filename)
223 .fold(false);
224
225 snippet = snippet.annotation(
226 Level::Warning
227 .span(relative_pos_range(span, bug.span))
228 .label(&detail),
229 );
230
231 let message = Level::Warning.title(title).snippet(snippet);
232
233 println!("{}", renderer.render(message));
234 }
235 }
236 }
237}