rapx/analysis/opt/data_collection/suboptimal/
slice_contains.rs

1use annotate_snippets::{Level, Renderer, Snippet};
2
3use once_cell::sync::OnceCell;
4
5use rustc_hir::{intravisit, Expr, ExprKind};
6use rustc_middle::ty::TyCtxt;
7use rustc_middle::ty::TypeckResults;
8use rustc_span::Span;
9
10use crate::analysis::core::dataflow::graph::Graph;
11use crate::analysis::opt::OptCheck;
12use crate::analysis::utils::def_path::DefPath;
13
14use crate::utils::log::{
15    relative_pos_range, span_to_filename, span_to_line_number, span_to_source_code,
16};
17
18struct DefPaths {
19    slice_contains: DefPath,
20}
21
22static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
23
24impl DefPaths {
25    pub fn new(tcx: &TyCtxt<'_>) -> Self {
26        Self {
27            slice_contains: DefPath::new("slice::contains", tcx),
28        }
29    }
30}
31
32struct ContainsFinder<'tcx> {
33    typeck_results: &'tcx TypeckResults<'tcx>,
34    record: Vec<Span>,
35}
36
37impl<'tcx> intravisit::Visitor<'tcx> for ContainsFinder<'tcx> {
38    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
39        if let ExprKind::MethodCall(.., span) = ex.kind {
40            let def_id = self
41                .typeck_results
42                .type_dependent_def_id(ex.hir_id)
43                .unwrap();
44            let target_def_id = (&DEFPATHS.get().unwrap()).slice_contains.last_def_id();
45            if def_id == target_def_id {
46                self.record.push(span);
47            }
48        }
49        intravisit::walk_expr(self, ex);
50    }
51}
52
53pub struct SliceContainsCheck {
54    record: Vec<Span>,
55}
56
57impl OptCheck for SliceContainsCheck {
58    fn new() -> Self {
59        Self { record: Vec::new() }
60    }
61
62    fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
63        let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
64        let def_id = graph.def_id;
65        let body = tcx.hir_body_owned_by(def_id.as_local().unwrap());
66        let typeck_results = tcx.typeck(def_id.as_local().unwrap());
67        let mut contains_finder = ContainsFinder {
68            typeck_results,
69            record: Vec::new(),
70        };
71        intravisit::walk_body(&mut contains_finder, body);
72        self.record = contains_finder.record;
73    }
74
75    fn report(&self, graph: &Graph) {
76        for contains_span in self.record.iter() {
77            report_slice_contains_bug(graph, *contains_span);
78        }
79    }
80
81    fn cnt(&self) -> usize {
82        self.record.len()
83    }
84}
85
86fn report_slice_contains_bug(graph: &Graph, contains_span: Span) {
87    let code_source = span_to_source_code(graph.span);
88    let filename = span_to_filename(graph.span);
89    let snippet = Snippet::source(&code_source)
90        .line_start(span_to_line_number(graph.span))
91        .origin(&filename)
92        .fold(true)
93        .annotation(
94            Level::Error
95                .span(relative_pos_range(graph.span, contains_span))
96                .label("Slice contains happens here."),
97        );
98    let message = Level::Warning
99        .title("Improper data collection detected")
100        .snippet(snippet)
101        .footer(Level::Help.title("Use Set instead of Slice."));
102    let renderer = Renderer::styled();
103    println!("{}", renderer.render(message));
104}