rapx/analysis/opt/iterator/
next_iterator.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::opt::OptCheck;
10use crate::analysis::utils::def_path::DefPath;
11use crate::utils::log::{
12    relative_pos_range, span_to_filename, span_to_line_number, span_to_source_code,
13};
14use annotate_snippets::{Level, Renderer, Snippet};
15
16static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
17
18struct DefPaths {
19    iter_next: DefPath,
20    iter_chain: DefPath,
21}
22
23impl DefPaths {
24    pub fn new(tcx: &TyCtxt<'_>) -> Self {
25        Self {
26            iter_next: DefPath::new("std::iter::Iterator::next", tcx),
27            iter_chain: DefPath::new("std::iter::Iterator::chain", tcx),
28        }
29    }
30}
31
32struct NextFinder<'tcx> {
33    typeck_results: &'tcx TypeckResults<'tcx>,
34    next_record: Vec<Span>,
35    chain_record: Vec<Span>,
36}
37
38impl<'tcx> intravisit::Visitor<'tcx> for NextFinder<'tcx> {
39    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
40        if let ExprKind::MethodCall(.., span) = ex.kind {
41            let def_id = self
42                .typeck_results
43                .type_dependent_def_id(ex.hir_id)
44                .unwrap();
45            let next_def_id = (&DEFPATHS.get().unwrap()).iter_next.last_def_id();
46            let chain_def_id = (&DEFPATHS.get().unwrap()).iter_chain.last_def_id();
47            if def_id == next_def_id {
48                self.next_record.push(span);
49            }
50            if def_id == chain_def_id {
51                self.chain_record.push(span);
52            }
53        }
54        intravisit::walk_expr(self, ex);
55    }
56}
57
58pub struct NextIteratorCheck {
59    next_record: Vec<Span>,
60    chain_record: Vec<Span>,
61    pub valid: bool,
62}
63
64impl OptCheck for NextIteratorCheck {
65    fn new() -> Self {
66        Self {
67            next_record: Vec::new(),
68            chain_record: Vec::new(),
69            valid: false,
70        }
71    }
72
73    fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
74        let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
75        let def_id = graph.def_id;
76        let body = tcx.hir_body_owned_by(def_id.as_local().unwrap());
77        let typeck_results = tcx.typeck(def_id.as_local().unwrap());
78        let mut next_chain_finder = NextFinder {
79            typeck_results,
80            next_record: Vec::new(),
81            chain_record: Vec::new(),
82        };
83        intravisit::walk_body(&mut next_chain_finder, body);
84        if next_chain_finder.chain_record.is_empty() || next_chain_finder.next_record.is_empty() {
85            self.valid = false;
86        } else {
87            self.valid = true;
88            self.next_record = next_chain_finder.next_record;
89            self.chain_record = next_chain_finder.chain_record;
90        }
91    }
92
93    fn report(&self, graph: &Graph) {
94        report_next_iterator_bug(&self.next_record, &self.chain_record, graph.span);
95    }
96
97    fn cnt(&self) -> usize {
98        self.next_record.len()
99    }
100}
101
102fn report_next_iterator_bug(next_record: &Vec<Span>, chain_record: &Vec<Span>, graph_span: Span) {
103    let code_source = span_to_source_code(graph_span);
104    let filename = span_to_filename(graph_span);
105    let mut snippet = Snippet::source(&code_source)
106        .line_start(span_to_line_number(graph_span))
107        .origin(&filename)
108        .fold(true);
109    for next_span in next_record {
110        snippet = snippet.annotation(Level::Error.span(relative_pos_range(graph_span, *next_span)))
111    }
112    for chain_span in chain_record {
113        snippet = snippet.annotation(Level::Error.span(relative_pos_range(graph_span, *chain_span)))
114    }
115    let message = Level::Warning
116        .title("Inefficient iterators detected")
117        .snippet(snippet)
118        .footer(Level::Help.title("Use chunk iterators."));
119    let renderer = Renderer::styled();
120    println!("{}", renderer.render(message));
121}