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