rapx/analysis/opt/iterator/
next_iterator.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::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}