]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use super::WHILE_IMMUTABLE_CONDITION; |
2 | use crate::consts::constant; | |
3 | use crate::utils::span_lint_and_then; | |
4 | use crate::utils::usage::mutated_variables; | |
5 | use if_chain::if_chain; | |
6 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
7 | use rustc_hir::def::{DefKind, Res}; | |
8 | use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; | |
9 | use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath}; | |
10 | use rustc_lint::LateContext; | |
11 | use rustc_middle::hir::map::Map; | |
12 | use std::iter::Iterator; | |
13 | ||
14 | pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { | |
15 | if constant(cx, cx.typeck_results(), cond).is_some() { | |
16 | // A pure constant condition (e.g., `while false`) is not linted. | |
17 | return; | |
18 | } | |
19 | ||
20 | let mut var_visitor = VarCollectorVisitor { | |
21 | cx, | |
22 | ids: FxHashSet::default(), | |
23 | def_ids: FxHashMap::default(), | |
24 | skip: false, | |
25 | }; | |
26 | var_visitor.visit_expr(cond); | |
27 | if var_visitor.skip { | |
28 | return; | |
29 | } | |
30 | let used_in_condition = &var_visitor.ids; | |
31 | let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { | |
32 | used_in_condition.is_disjoint(&used_mutably) | |
33 | } else { | |
34 | return; | |
35 | }; | |
36 | let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); | |
37 | ||
38 | let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { | |
39 | has_break_or_return: false, | |
40 | }; | |
41 | has_break_or_return_visitor.visit_expr(expr); | |
42 | let has_break_or_return = has_break_or_return_visitor.has_break_or_return; | |
43 | ||
44 | if no_cond_variable_mutated && !mutable_static_in_cond { | |
45 | span_lint_and_then( | |
46 | cx, | |
47 | WHILE_IMMUTABLE_CONDITION, | |
48 | cond.span, | |
49 | "variables in the condition are not mutated in the loop body", | |
50 | |diag| { | |
51 | diag.note("this may lead to an infinite or to a never running loop"); | |
52 | ||
53 | if has_break_or_return { | |
54 | diag.note("this loop contains `return`s or `break`s"); | |
55 | diag.help("rewrite it as `if cond { loop { } }`"); | |
56 | } | |
57 | }, | |
58 | ); | |
59 | } | |
60 | } | |
61 | ||
62 | struct HasBreakOrReturnVisitor { | |
63 | has_break_or_return: bool, | |
64 | } | |
65 | ||
66 | impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { | |
67 | type Map = Map<'tcx>; | |
68 | ||
69 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
70 | if self.has_break_or_return { | |
71 | return; | |
72 | } | |
73 | ||
74 | match expr.kind { | |
75 | ExprKind::Ret(_) | ExprKind::Break(_, _) => { | |
76 | self.has_break_or_return = true; | |
77 | return; | |
78 | }, | |
79 | _ => {}, | |
80 | } | |
81 | ||
82 | walk_expr(self, expr); | |
83 | } | |
84 | ||
85 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
86 | NestedVisitorMap::None | |
87 | } | |
88 | } | |
89 | ||
90 | /// Collects the set of variables in an expression | |
91 | /// Stops analysis if a function call is found | |
92 | /// Note: In some cases such as `self`, there are no mutable annotation, | |
93 | /// All variables definition IDs are collected | |
94 | struct VarCollectorVisitor<'a, 'tcx> { | |
95 | cx: &'a LateContext<'tcx>, | |
96 | ids: FxHashSet<HirId>, | |
97 | def_ids: FxHashMap<def_id::DefId, bool>, | |
98 | skip: bool, | |
99 | } | |
100 | ||
101 | impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { | |
102 | fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { | |
103 | if_chain! { | |
104 | if let ExprKind::Path(ref qpath) = ex.kind; | |
105 | if let QPath::Resolved(None, _) = *qpath; | |
106 | let res = self.cx.qpath_res(qpath, ex.hir_id); | |
107 | then { | |
108 | match res { | |
109 | Res::Local(hir_id) => { | |
110 | self.ids.insert(hir_id); | |
111 | }, | |
112 | Res::Def(DefKind::Static, def_id) => { | |
113 | let mutable = self.cx.tcx.is_mutable_static(def_id); | |
114 | self.def_ids.insert(def_id, mutable); | |
115 | }, | |
116 | _ => {}, | |
117 | } | |
118 | } | |
119 | } | |
120 | } | |
121 | } | |
122 | ||
123 | impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { | |
124 | type Map = Map<'tcx>; | |
125 | ||
126 | fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { | |
127 | match ex.kind { | |
128 | ExprKind::Path(_) => self.insert_def_id(ex), | |
129 | // If there is any function/method call… we just stop analysis | |
130 | ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, | |
131 | ||
132 | _ => walk_expr(self, ex), | |
133 | } | |
134 | } | |
135 | ||
136 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
137 | NestedVisitorMap::None | |
138 | } | |
139 | } |