Skip to main content

rapx/check/opt/memory_cloning/
used_as_immutable.rs

1use 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                //filter mutable variables
67                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_string.last_def_id()
72                    || 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                                    //we filter out funcs that may cause false positive
108                                    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}