rapx/analysis/opt/memory_cloning/
hash_key_cloning.rs1use crate::{
2 analysis::{
3 core::dataflow::{graph::*, *},
4 opt::OptCheck,
5 utils::def_path::DefPath,
6 },
7 utils::log::{relative_pos_range, span_to_filename, span_to_line_number, span_to_source_code},
8};
9use annotate_snippets::{Level, Renderer, Snippet};
10use once_cell::sync::OnceCell;
11
12use rustc_hir::{intravisit, Expr, ExprKind};
13use rustc_middle::{
14 mir::Local,
15 ty::{TyCtxt, TypeckResults},
16};
17use rustc_span::Span;
18use std::collections::HashSet;
19static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
20
21struct DefPaths {
22 hashset_insert: DefPath,
23 hashmap_insert: DefPath,
24 hashset_new: DefPath,
25 hashmap_new: DefPath,
26 hashset_with: DefPath,
27 hashmap_with: DefPath,
28 clone: DefPath,
29}
30
31impl DefPaths {
32 pub fn new(tcx: &TyCtxt<'_>) -> Self {
33 Self {
34 hashset_insert: DefPath::new("std::collections::HashSet::insert", tcx),
35 hashmap_insert: DefPath::new("std::collections::HashMap::insert", tcx),
36 hashset_new: DefPath::new("std::collections::HashSet::new", tcx),
37 hashset_with: DefPath::new("std::collections::HashSet::with_capacity", tcx),
38 hashmap_new: DefPath::new("std::collections::HashMap::new", tcx),
39 hashmap_with: DefPath::new("std::collections::HashMap::with_capacity", tcx),
40 clone: DefPath::new("std::clone::Clone::clone", tcx),
41 }
42 }
43}
44
45struct HashInsertFinder<'tcx> {
46 typeck_results: &'tcx TypeckResults<'tcx>,
47 record: HashSet<Span>,
48}
49
50impl<'tcx> intravisit::Visitor<'tcx> for HashInsertFinder<'tcx> {
51 fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
52 if let ExprKind::MethodCall(..) = ex.kind {
53 let def_id = self
54 .typeck_results
55 .type_dependent_def_id(ex.hir_id)
56 .unwrap();
57 if def_id == DEFPATHS.get().unwrap().hashset_insert.last_def_id()
58 || def_id == DEFPATHS.get().unwrap().hashmap_insert.last_def_id()
59 {
60 self.record.insert(ex.span);
61 }
62 }
63 intravisit::walk_expr(self, ex);
64 }
65}
66
67fn find_first_param_upside_clone(graph: &Graph, node: &GraphNode) -> Option<Local> {
69 let mut clone_node_idx = None;
70 let def_paths = &DEFPATHS.get().unwrap();
71 let target_def_id = def_paths.clone.last_def_id();
72 let mut node_operator = |graph: &Graph, idx: Local| -> DFSStatus {
73 let node = &graph.nodes[idx];
74 for op in node.ops.iter() {
75 if let NodeOp::Call(def_id) = op {
76 if *def_id == target_def_id {
77 clone_node_idx = Some(idx);
78 return DFSStatus::Stop;
79 }
80 }
81 }
82 DFSStatus::Continue
83 };
84 let mut seen = HashSet::new();
85 graph.dfs(
86 graph.edges[node.in_edges[1]].src, Direction::Upside,
88 &mut node_operator,
89 &mut Graph::equivalent_edge_validator,
90 false,
91 &mut seen,
92 );
93 clone_node_idx
94}
95
96fn find_hash_new_node(graph: &Graph, node: &GraphNode) -> Option<Local> {
98 let mut new_node_idx = None;
99 let def_paths = &DEFPATHS.get().unwrap();
100 let mut node_operator = |graph: &Graph, idx: Local| -> DFSStatus {
101 let node = &graph.nodes[idx];
102 for op in node.ops.iter() {
103 if let NodeOp::Call(def_id) = op {
104 if *def_id == def_paths.hashset_new.last_def_id()
105 || *def_id == def_paths.hashmap_new.last_def_id()
106 || *def_id == def_paths.hashset_with.last_def_id()
107 || *def_id == def_paths.hashmap_with.last_def_id()
108 {
109 new_node_idx = Some(idx);
110 return DFSStatus::Stop;
111 }
112 }
113 }
114 DFSStatus::Continue
115 };
116 let mut seen = HashSet::new();
117 graph.dfs(
118 graph.edges[node.in_edges[0]].src, Direction::Upside,
120 &mut node_operator,
121 &mut Graph::equivalent_edge_validator,
122 false,
123 &mut seen,
124 );
125 new_node_idx
126}
127
128fn report_hash_key_cloning(graph: &Graph, clone_span: Span, insert_span: Span) {
129 let code_source = span_to_source_code(graph.span);
130 let filename = span_to_filename(clone_span);
131 let snippet = Snippet::source(&code_source)
132 .line_start(span_to_line_number(graph.span))
133 .origin(&filename)
134 .fold(true)
135 .annotation(
136 Level::Error
137 .span(relative_pos_range(graph.span, clone_span))
138 .label("Cloning happens here."),
139 )
140 .annotation(
141 Level::Error
142 .span(relative_pos_range(graph.span, insert_span))
143 .label("Used here."),
144 );
145 let message = Level::Warning
146 .title("Unnecessary memory cloning detected")
147 .snippet(snippet)
148 .footer(Level::Help.title("Use borrowings as keys."));
149 let renderer = Renderer::styled();
150 println!("{}", renderer.render(message));
151}
152
153pub struct HashKeyCloningCheck {
154 record: Vec<(Span, Span)>,
155}
156
157impl OptCheck for HashKeyCloningCheck {
158 fn new() -> Self {
159 Self { record: Vec::new() }
160 }
161
162 fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
163 let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
164 let def_id = graph.def_id;
165 let body = tcx.hir_body_owned_by(def_id.as_local().unwrap());
166 let typeck_results = tcx.typeck(def_id.as_local().unwrap());
167 let mut hash_finder = HashInsertFinder {
168 typeck_results,
169 record: HashSet::new(),
170 };
171 intravisit::walk_body(&mut hash_finder, body);
172 for node in graph.nodes.iter() {
173 if hash_finder.record.contains(&node.span) {
174 if let Some(clone_node_idx) = find_first_param_upside_clone(graph, node) {
175 if let Some(new_node_idx) = find_hash_new_node(graph, node) {
176 if !graph.is_connected(new_node_idx, Local::from_usize(0)) {
177 let clone_span = graph.nodes[clone_node_idx].span;
178 self.record.push((clone_span, node.span));
179 }
180 }
181 }
182 }
183 }
184 }
185
186 fn report(&self, graph: &Graph) {
187 for (clone_span, insert_span) in self.record.iter() {
188 report_hash_key_cloning(graph, *clone_span, *insert_span);
189 }
190 }
191
192 fn cnt(&self) -> usize {
193 self.record.len()
194 }
195}