rapx/check/opt/memory_cloning/
used_as_immutable.rs

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