Skip to main content

rapx/check/opt/memory_cloning/
used_as_immutable.rs

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