rapx/analysis/opt/memory_cloning/
used_as_immutable.rs

1use 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_string: DefPath,
27    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_string: DefPath::new("std::string::ToString::to_string", tcx),
36            to_owned: DefPath::new("std::borrow::ToOwned::to_owned", tcx),
37            deref: DefPath::new("std::ops::Deref::deref", tcx),
38        }
39    }
40}
41
42// whether the cloned value is used as a parameter
43fn 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; //the start point, clone, is a Call node as well
50        }
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                    //we permit deref here
56                    return DFSStatus::Continue;
57                }
58                record = Some((idx, edge_idx.get())); //here, the edge_idx must be the upside edge of the node
59                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) //can not support ref->deref->ref link
67    };
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                //filter mutable variables
96                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_string.last_def_id()
101                    || 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                                    //not &mut T
123                                    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                                    //we filter out funcs that may cause false positive
133                                    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}