rapx/check/opt/memory_cloning/
used_as_immutable.rs1use crate::{
2 analysis::dataflow::*,
3 check::opt::OptCheck,
4};
5use annotate_snippets::Level;
6
7use crate::check::opt::report::OptReport;
8
9use super::super::LEVEL;
10use rustc_middle::{
11 mir::Local,
12 ty::{Mutability, TyCtxt, TyKind},
13};
14use rustc_span::Span;
15use std::cell::Cell;
16
17crate::def_paths! {
18 clone: "std::clone::Clone::clone",
19 to_owned: "std::borrow::ToOwned::to_owned",
20 deref: "std::ops::Deref::deref",
21}
22
23
24fn find_downside_use_as_param(graph: &Graph, clone_node_idx: Local) -> Option<(Local, EdgeIdx)> {
25 let mut record = None;
26 let captured_edge = Cell::new(0);
27 let deref_id = DEFPATHS.get().unwrap().deref.last_def_id();
28 let mut edge_operator = |graph: &Graph, idx: EdgeIdx| {
29 captured_edge.set(idx);
30 Graph::equivalent_edge_validator(graph, idx)
31 };
32 graph.find_first_node(
33 clone_node_idx,
34 Direction::Downside,
35 &mut |graph: &Graph, idx: Local| {
36 if idx == clone_node_idx {
37 return false;
38 }
39 let node = &graph.nodes[idx];
40 for op in node.ops.iter() {
41 if let NodeOp::Call(def_id) = op {
42 if *def_id == deref_id {
43 return false;
44 }
45 record = Some((idx, captured_edge.get()));
46 return true;
47 }
48 }
49 false
50 },
51 &mut edge_operator,
52 );
53 record
54}
55
56pub struct UsedAsImmutableCheck {
57 record: Vec<(Span, Span)>,
58}
59
60impl OptCheck for UsedAsImmutableCheck {
61 fn new() -> Self {
62 Self { record: Vec::new() }
63 }
64
65 fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
66 let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
67 let def_paths = &DEFPATHS.get().unwrap();
68 let level = LEVEL.lock().unwrap();
69 for (idx, node) in graph.nodes.iter_enumerated() {
70 if node.ops.len() > 1 {
71 continue;
73 }
74 if let NodeOp::Call(def_id) = node.ops[0] {
75 if def_id == def_paths.clone.last_def_id()
76 || def_id == def_paths.to_owned.last_def_id()
78 {
79 if let Some((node_idx, edge_idx)) = find_downside_use_as_param(graph, idx) {
80 let use_node = &graph.nodes[node_idx];
81
82 let seq = graph.edges[edge_idx].seq;
83 let filtered_in_edges: Vec<&usize> = use_node
84 .in_edges
85 .iter()
86 .filter(|idx| graph.edges[**idx].seq == seq)
87 .collect();
88 let index = filtered_in_edges.binary_search(&&edge_idx).unwrap();
89 if let NodeOp::Call(callee_def_id) = use_node.ops[seq] {
90 let callee_fn_sig = tcx.fn_sig(callee_def_id).skip_binder();
91 #[cfg(not(rapx_ge_99))]
92 let fn_sig = tcx.try_normalize_erasing_regions(
93 rustc_middle::ty::TypingEnv::post_analysis(*tcx, def_id),
94 callee_fn_sig,
95 );
96 #[cfg(rapx_ge_99)]
97 let fn_sig = tcx.try_normalize_erasing_regions(
98 rustc_middle::ty::TypingEnv::post_analysis(*tcx, def_id),
99 rustc_type_ir::Unnormalized::dummy(callee_fn_sig),
100 );
101 if fn_sig.is_ok() {
102 let fn_sig = fn_sig.unwrap().skip_binder();
103 let ty = fn_sig.inputs().iter().nth(index).unwrap();
104 if let TyKind::Ref(_, _, Mutability::Mut) = ty.kind() {
105 break;
106 }
107 let callee_func_name = format!("{:?}", callee_def_id);
108 if *level != 2
109 && (callee_func_name.contains("into")
110 || callee_func_name.contains("new"))
111 {
112 break;
114 }
115 let clone_span = node.span;
116 let use_span = use_node.span;
117 self.record.push((clone_span, use_span));
118 }
119 }
120 }
121 }
122 }
123 }
124 }
125
126 fn report(&self, graph: &Graph) {
127 for (clone_span, use_span) in self.record.iter() {
128 report_used_as_immutable(graph, *clone_span, *use_span);
129 }
130 }
131
132 fn cnt(&self) -> usize {
133 self.record.len()
134 }
135}
136
137fn report_used_as_immutable(graph: &Graph, clone_span: Span, use_span: Span) {
138 OptReport::from_graph(graph)
139 .file_name(clone_span)
140 .title("Unnecessary memory cloning detected")
141 .annotate(Level::Error, clone_span, "Cloning happens here.")
142 .annotate(Level::Error, use_span, "Used here")
143 .footer("Use borrowings instead.")
144 .emit();
145}