rapx/analysis/opt/memory_cloning/
used_as_immutable.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;
11use rustc_ast::Mutability;
12
13use super::super::LEVEL;
14use rustc_middle::{
15 mir::Local,
16 ty::{TyCtxt, TyKind},
17};
18use rustc_span::Span;
19use std::cell::Cell;
20use std::collections::HashSet;
21
22static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
23
24struct DefPaths {
25 clone: DefPath,
26 to_owned: DefPath,
28 deref: DefPath,
29}
30
31impl DefPaths {
32 pub fn new(tcx: &TyCtxt<'_>) -> Self {
33 Self {
34 clone: DefPath::new("std::clone::Clone::clone", tcx),
35 to_owned: DefPath::new("std::borrow::ToOwned::to_owned", tcx),
37 deref: DefPath::new("std::ops::Deref::deref", tcx),
38 }
39 }
40}
41
42fn find_downside_use_as_param(graph: &Graph, clone_node_idx: Local) -> Option<(Local, EdgeIdx)> {
44 let mut record = None;
45 let edge_idx = Cell::new(0 as usize);
46 let deref_id = DEFPATHS.get().unwrap().deref.last_def_id();
47 let mut node_operator = |graph: &Graph, idx: Local| {
48 if idx == clone_node_idx {
49 return DFSStatus::Continue; }
51 let node = &graph.nodes[idx];
52 for op in node.ops.iter() {
53 if let NodeOp::Call(def_id) = op {
54 if *def_id == deref_id {
55 return DFSStatus::Continue;
57 }
58 record = Some((idx, edge_idx.get())); return DFSStatus::Stop;
60 }
61 }
62 DFSStatus::Continue
63 };
64 let mut edge_operator = |graph: &Graph, idx: EdgeIdx| {
65 edge_idx.set(idx);
66 Graph::equivalent_edge_validator(graph, idx) };
68 let mut seen = HashSet::new();
69 graph.dfs(
70 clone_node_idx,
71 Direction::Downside,
72 &mut node_operator,
73 &mut edge_operator,
74 true,
75 &mut seen,
76 );
77 record
78}
79
80pub struct UsedAsImmutableCheck {
81 record: Vec<(Span, Span)>,
82}
83
84impl OptCheck for UsedAsImmutableCheck {
85 fn new() -> Self {
86 Self { record: Vec::new() }
87 }
88
89 fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
90 let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
91 let def_paths = &DEFPATHS.get().unwrap();
92 let level = LEVEL.lock().unwrap();
93 for (idx, node) in graph.nodes.iter_enumerated() {
94 if node.ops.len() > 1 {
95 continue;
97 }
98 if let NodeOp::Call(def_id) = node.ops[0] {
99 if def_id == def_paths.clone.last_def_id()
100 || def_id == def_paths.to_owned.last_def_id()
102 {
103 if let Some((node_idx, edge_idx)) = find_downside_use_as_param(graph, idx) {
104 let use_node = &graph.nodes[node_idx];
105
106 let seq = graph.edges[edge_idx].seq;
107 let filtered_in_edges: Vec<&usize> = use_node
108 .in_edges
109 .iter()
110 .filter(|idx| graph.edges[**idx].seq == seq)
111 .collect();
112 let index = filtered_in_edges.binary_search(&&edge_idx).unwrap();
113 if let NodeOp::Call(callee_def_id) = use_node.ops[seq] {
114 let fn_sig = tcx.try_normalize_erasing_regions(
115 rustc_middle::ty::TypingEnv::post_analysis(*tcx, def_id),
116 tcx.fn_sig(callee_def_id).skip_binder(),
117 );
118 if fn_sig.is_ok() {
119 let fn_sig = fn_sig.unwrap().skip_binder();
120 let ty = fn_sig.inputs().iter().nth(index).unwrap();
121 if let TyKind::Ref(_, _, mutability) = ty.kind() {
122 if *mutability == Mutability::Mut {
124 break;
125 }
126 }
127 let callee_func_name = format!("{:?}", callee_def_id);
128 if *level != 2
129 && (callee_func_name.contains("into")
130 || callee_func_name.contains("new"))
131 {
132 break;
134 }
135 let clone_span = node.span;
136 let use_span = use_node.span;
137 self.record.push((clone_span, use_span));
138 }
139 }
140 }
141 }
142 }
143 }
144 }
145
146 fn report(&self, graph: &Graph) {
147 for (clone_span, use_span) in self.record.iter() {
148 report_used_as_immutable(graph, *clone_span, *use_span);
149 }
150 }
151
152 fn cnt(&self) -> usize {
153 self.record.len()
154 }
155}
156
157fn report_used_as_immutable(graph: &Graph, clone_span: Span, use_span: Span) {
158 let code_source = span_to_source_code(graph.span);
159 let filename = span_to_filename(clone_span);
160 let snippet = Snippet::source(&code_source)
161 .line_start(span_to_line_number(graph.span))
162 .origin(&filename)
163 .fold(true)
164 .annotation(
165 Level::Error
166 .span(relative_pos_range(graph.span, clone_span))
167 .label("Cloning happens here."),
168 )
169 .annotation(
170 Level::Error
171 .span(relative_pos_range(graph.span, use_span))
172 .label("Used here"),
173 );
174 let message = Level::Warning
175 .title("Unnecessary memory cloning detected")
176 .snippet(snippet)
177 .footer(Level::Help.title("Use borrowings instead."));
178 let renderer = Renderer::styled();
179 println!("{}", renderer.render(message));
180}