rapx/check/opt/data_collection/initialization/
local_set.rs

1use annotate_snippets::{Level, Renderer, Snippet};
2
3use once_cell::sync::OnceCell;
4
5use crate::{
6    analysis::dataflow::*,
7    check::opt::OptCheck,
8    helpers::def_path::DefPath,
9    utils::log::{relative_pos_range, span_to_filename, span_to_line_number, span_to_source_code},
10};
11use rustc_hir::def_id::DefId;
12use rustc_middle::{mir::Local, ty::TyCtxt};
13use rustc_span::Span;
14
15struct DefPaths {
16    hashset_new: DefPath,
17    hashset_with_capacity: DefPath,
18    hashmap_new: DefPath,
19    hashmap_with_capacity: DefPath,
20    btreeset_new: DefPath,
21    btreemap_new: DefPath,
22}
23
24static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
25
26impl DefPaths {
27    pub fn new(tcx: &TyCtxt<'_>) -> Self {
28        Self {
29            hashset_new: DefPath::new("std::collections::HashSet::new", tcx),
30            hashset_with_capacity: DefPath::new("std::collections::HashSet::with_capacity", tcx),
31            hashmap_new: DefPath::new("std::collections::HashMap::new", tcx),
32            hashmap_with_capacity: DefPath::new("std::collections::HashMap::with_capacity", tcx),
33            btreeset_new: DefPath::new("std::collections::BTreeSet::new", tcx),
34            btreemap_new: DefPath::new("std::collections::BTreeMap::new", tcx),
35        }
36    }
37
38    fn has_id(&self, id: DefId) -> bool {
39        id == self.hashset_new.last_def_id()
40            || id == self.hashmap_new.last_def_id()
41            || id == self.btreemap_new.last_def_id()
42            || id == self.btreeset_new.last_def_id()
43            || id == self.hashmap_with_capacity.last_def_id()
44            || id == self.hashset_with_capacity.last_def_id()
45    }
46}
47
48pub struct LocalSetCheck {
49    record: Vec<Span>,
50}
51
52impl OptCheck for LocalSetCheck {
53    fn new() -> Self {
54        Self { record: Vec::new() }
55    }
56
57    fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
58        let def_paths = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
59        for (node_idx, node) in graph.nodes.iter_enumerated() {
60            for op in node.ops.iter() {
61                if let NodeOp::Call(def_id) = op {
62                    if def_paths.has_id(*def_id)
63                        && !graph.is_connected(Local::from_usize(0), node_idx)
64                    {
65                        self.record.push(node.span);
66                    }
67                }
68            }
69        }
70    }
71
72    fn report(&self, graph: &Graph) {
73        for span in self.record.iter() {
74            report_local_set(graph, *span);
75        }
76    }
77
78    fn cnt(&self) -> usize {
79        self.record.len()
80    }
81}
82
83fn report_local_set(graph: &Graph, span: Span) {
84    let code_source = span_to_source_code(graph.span);
85    let filename = span_to_filename(span);
86    let snippet = Snippet::source(&code_source)
87        .line_start(span_to_line_number(graph.span))
88        .origin(&filename)
89        .fold(true)
90        .annotation(
91            Level::Error
92                .span(relative_pos_range(graph.span, span))
93                .label("Initialization happens here"),
94        );
95    let message = Level::Warning
96        .title("Unnecessary data collection initialization detected")
97        .snippet(snippet)
98        .footer(
99            Level::Help.title("Move it into parameter list and use hash table to save allocation."),
100        );
101    let renderer = Renderer::styled();
102    rap_warn!("{}", renderer.render(message));
103}