rapx/analysis/opt/checking/bounds_checking/
bounds_loop_push.rs1use 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.len() == 1 {
77 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}