rapx/analysis/safedrop/
bug_records.rs

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/*
23 * For each bug in the HashMap, the key is local of the value.
24 */
25#[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}