rapx/analysis/opt/checking/bounds_checking/
bounds_loop_push.rs

1use once_cell::sync::OnceCell;
2
3use rustc_hir::{intravisit, Expr, ExprKind};
4use rustc_middle::ty::TyCtxt;
5use rustc_middle::ty::TypeckResults;
6use rustc_span::Span;
7
8use crate::analysis::core::dataflow::graph::Graph;
9use crate::analysis::utils::def_path::DefPath;
10use crate::utils::log::{
11    relative_pos_range, span_to_filename, span_to_first_line, span_to_line_number,
12    span_to_source_code, span_to_trimmed_span,
13};
14use annotate_snippets::{Level, Renderer, Snippet};
15
16use super::super::super::LEVEL;
17use super::super::super::NO_STD;
18static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
19
20struct DefPaths {
21    vec_push: DefPath,
22}
23
24impl DefPaths {
25    pub fn new(tcx: &TyCtxt<'_>) -> Self {
26        let no_std = NO_STD.lock().unwrap();
27        if *no_std {
28            Self {
29                vec_push: DefPath::new("alloc::vec::Vec::push", tcx),
30            }
31        } else {
32            Self {
33                vec_push: DefPath::new("std::vec::Vec::push", tcx),
34            }
35        }
36    }
37}
38
39pub struct LoopFinder<'tcx> {
40    pub typeck_results: &'tcx TypeckResults<'tcx>,
41    pub record: Vec<(Span, Vec<Span>)>,
42}
43
44pub struct PushFinder<'tcx> {
45    typeck_results: &'tcx TypeckResults<'tcx>,
46    record: Vec<Span>,
47}
48
49impl<'tcx> intravisit::Visitor<'tcx> for PushFinder<'tcx> {
50    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
51        if let ExprKind::MethodCall(.., span) = ex.kind {
52            let def_id = self
53                .typeck_results
54                .type_dependent_def_id(ex.hir_id)
55                .unwrap();
56            let target_def_id = (&DEFPATHS.get().unwrap()).vec_push.last_def_id();
57            if def_id == target_def_id {
58                self.record.push(span);
59            }
60        }
61        intravisit::walk_expr(self, ex);
62    }
63}
64
65impl<'tcx> intravisit::Visitor<'tcx> for LoopFinder<'tcx> {
66    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
67        if let ExprKind::Loop(block, ..) = ex.kind {
68            let mut push_finder = PushFinder {
69                typeck_results: self.typeck_results,
70                record: Vec::new(),
71            };
72            intravisit::walk_block(&mut push_finder, block);
73            // if !push_finder.record.is_empty() {
74            //     self.record.push((ex.span, push_finder.record));
75            // }
76            if push_finder.record.len() == 1 {
77                // we only use simple cases
78                self.record.push((ex.span, push_finder.record));
79            }
80        }
81        intravisit::walk_expr(self, ex);
82    }
83}
84
85use crate::analysis::opt::OptCheck;
86
87pub struct BoundsLoopPushCheck {
88    pub record: Vec<(Span, Vec<Span>)>,
89}
90
91impl OptCheck for BoundsLoopPushCheck {
92    fn new() -> Self {
93        Self { record: Vec::new() }
94    }
95
96    fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
97        let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
98        let level = LEVEL.lock().unwrap();
99        if *level == 2 {
100            let def_id = graph.def_id;
101            let body = tcx.hir_body_owned_by(def_id.as_local().unwrap());
102            let typeck_results = tcx.typeck(def_id.as_local().unwrap());
103            let mut loop_finder = LoopFinder {
104                typeck_results,
105                record: Vec::new(),
106            };
107            intravisit::walk_body(&mut loop_finder, body);
108            self.record = loop_finder.record;
109        }
110    }
111
112    fn report(&self, _: &Graph) {
113        for (loop_span, push_record) in self.record.iter() {
114            report_loop_push_bug(*loop_span, push_record);
115        }
116    }
117
118    fn cnt(&self) -> usize {
119        self.record.iter().map(|(_, spans)| spans.len()).sum()
120    }
121}
122
123fn report_loop_push_bug(loop_span: Span, push_record: &Vec<Span>) {
124    let code_source = span_to_source_code(loop_span);
125    let filename = span_to_filename(loop_span);
126    let mut snippet = Snippet::source(&code_source)
127        .line_start(span_to_line_number(loop_span))
128        .origin(&filename)
129        .fold(true)
130        .annotation(
131            Level::Info
132                .span(relative_pos_range(
133                    loop_span,
134                    span_to_trimmed_span(span_to_first_line(loop_span)),
135                ))
136                .label("A loop operation."),
137        );
138    for push_span in push_record {
139        snippet = snippet.annotation(
140            Level::Error
141                .span(relative_pos_range(loop_span, *push_span))
142                .label("Push happens here."),
143        );
144    }
145    let message = Level::Warning
146        .title("Unnecessary bounds checkings detected")
147        .snippet(snippet);
148    let renderer = Renderer::styled();
149    println!("{}", renderer.render(message));
150}