Skip to content

Commit 6801189

Browse files
committed
Rewrite never_loop as a strict reachability pass
fixes rust-lang#11004
1 parent a8b5245 commit 6801189

File tree

3 files changed

+163
-124
lines changed

3 files changed

+163
-124
lines changed

clippy_lints/src/loops/never_loop.rs

Lines changed: 120 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use super::utils::make_iterator_snippet;
22
use super::NEVER_LOOP;
3-
use clippy_utils::consts::{constant, Constant};
43
use clippy_utils::diagnostics::span_lint_and_then;
54
use clippy_utils::higher::ForLoop;
65
use clippy_utils::source::snippet;
@@ -18,7 +17,7 @@ pub(super) fn check<'tcx>(
1817
for_loop: Option<&ForLoop<'_>>,
1918
) {
2019
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
21-
NeverLoopResult::AlwaysBreak => {
20+
NeverLoopResult::Diverging => {
2221
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
2322
if let Some(ForLoop {
2423
arg: iterator,
@@ -39,83 +38,98 @@ pub(super) fn check<'tcx>(
3938
}
4039
});
4140
},
42-
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
43-
NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(),
41+
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Normal => (),
4442
}
4543
}
4644

45+
/// The `never_loop` analysis keeps track of three things:
46+
///
47+
/// * Has any (reachable) code path hit a `continue` of the main loop?
48+
/// * Is the current code path diverging (that is, the next expression is not reachable)
49+
/// * For each block label `'a` inside the main loop, has any (reachable) code path encountered a
50+
/// `break 'a`?
51+
///
52+
/// The first two bits of information are in this enum, and the last part is in the
53+
/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
54+
/// scope.
4755
#[derive(Copy, Clone)]
4856
enum NeverLoopResult {
49-
// A break/return always get triggered but not necessarily for the main loop.
50-
AlwaysBreak,
51-
// A continue may occur for the main loop.
57+
/// A continue may occur for the main loop.
5258
MayContinueMainLoop,
53-
// Ignore everything until the end of the block with this id
54-
IgnoreUntilEnd(HirId),
55-
Otherwise,
59+
/// We have not encountered any main loop continue,
60+
/// but we are diverging (subsequent control flow is not reachable)
61+
Diverging,
62+
/// We have not encountered any main loop continue,
63+
/// and subsequent control flow is (possibly) reachable
64+
Normal,
5665
}
5766

5867
#[must_use]
5968
fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
6069
match arg {
61-
NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
70+
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
6271
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
63-
NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id),
6472
}
6573
}
6674

6775
// Combine two results for parts that are called in order.
6876
#[must_use]
69-
fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
77+
fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult {
7078
match first {
71-
NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
72-
first
73-
},
74-
NeverLoopResult::Otherwise => second,
79+
NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first,
80+
NeverLoopResult::Normal => second(),
81+
}
82+
}
83+
84+
// Combine an iterator of results for parts that are called in order.
85+
#[must_use]
86+
fn combine_seq_many(iter: impl IntoIterator<Item = NeverLoopResult>) -> NeverLoopResult {
87+
for e in iter {
88+
if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e {
89+
return e;
90+
}
7591
}
92+
NeverLoopResult::Normal
7693
}
7794

7895
// Combine two results where only one of the part may have been executed.
7996
#[must_use]
80-
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
97+
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
8198
match (b1, b2) {
82-
(NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
83-
if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
84-
NeverLoopResult::IgnoreUntilEnd(b)
85-
} else {
86-
NeverLoopResult::IgnoreUntilEnd(a)
87-
}
88-
},
89-
(i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
90-
| (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i,
91-
(NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
9299
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
93100
NeverLoopResult::MayContinueMainLoop
94101
},
95-
(NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
102+
(NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal,
103+
(NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging,
96104
}
97105
}
98106

99107
fn never_loop_block<'tcx>(
100108
cx: &LateContext<'tcx>,
101109
block: &Block<'tcx>,
102-
ignore_ids: &mut Vec<HirId>,
110+
local_labels: &mut Vec<(HirId, bool)>,
103111
main_loop_id: HirId,
104112
) -> NeverLoopResult {
105113
let iter = block
106114
.stmts
107115
.iter()
108116
.filter_map(stmt_to_expr)
109117
.chain(block.expr.map(|expr| (expr, None)));
110-
111-
iter.map(|(e, els)| {
112-
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
118+
combine_seq_many(iter.map(|(e, els)| {
119+
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
113120
// els is an else block in a let...else binding
114121
els.map_or(e, |els| {
115-
combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
122+
combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) {
123+
// Returning MayContinueMainLoop here means that
124+
// we will not evaluate the rest of the body
125+
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
126+
// An else block always diverges, so the Normal case should not happen,
127+
// but the analysis is approximate so it might return Normal anyway.
128+
// Returning Normal here says that nothing more happens on the main path
129+
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
130+
})
116131
})
117-
})
118-
.fold(NeverLoopResult::Otherwise, combine_seq)
132+
}))
119133
}
120134

121135
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
@@ -131,7 +145,7 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
131145
fn never_loop_expr<'tcx>(
132146
cx: &LateContext<'tcx>,
133147
expr: &Expr<'tcx>,
134-
ignore_ids: &mut Vec<HirId>,
148+
local_labels: &mut Vec<(HirId, bool)>,
135149
main_loop_id: HirId,
136150
) -> NeverLoopResult {
137151
match expr.kind {
@@ -141,66 +155,61 @@ fn never_loop_expr<'tcx>(
141155
| ExprKind::Field(e, _)
142156
| ExprKind::AddrOf(_, _, e)
143157
| ExprKind::Repeat(e, _)
144-
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
145-
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
146-
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
158+
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, local_labels, main_loop_id),
159+
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, local_labels, main_loop_id),
160+
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, es.iter(), local_labels, main_loop_id),
147161
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
148162
cx,
149-
&mut std::iter::once(receiver).chain(es.iter()),
150-
ignore_ids,
163+
std::iter::once(receiver).chain(es.iter()),
164+
local_labels,
151165
main_loop_id,
152166
),
153167
ExprKind::Struct(_, fields, base) => {
154-
let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
168+
let fields = never_loop_expr_all(cx, fields.iter().map(|f| f.expr), local_labels, main_loop_id);
155169
if let Some(base) = base {
156-
combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
170+
combine_seq(fields, || never_loop_expr(cx, base, local_labels, main_loop_id))
157171
} else {
158172
fields
159173
}
160174
},
161-
ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
175+
ExprKind::Call(e, es) => never_loop_expr_all(cx, once(e).chain(es.iter()), local_labels, main_loop_id),
162176
ExprKind::Binary(_, e1, e2)
163177
| ExprKind::Assign(e1, e2, _)
164178
| ExprKind::AssignOp(_, e1, e2)
165-
| ExprKind::Index(e1, e2, _) => {
166-
never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id)
167-
},
179+
| ExprKind::Index(e1, e2, _) => never_loop_expr_all(cx, [e1, e2].iter().copied(), local_labels, main_loop_id),
168180
ExprKind::Loop(b, _, _, _) => {
169-
// Break can come from the inner loop so remove them.
170-
absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
181+
// We don't attempt to track reachability after a loop,
182+
// just assume there may have been a break somewhere
183+
absorb_break(never_loop_block(cx, b, local_labels, main_loop_id))
171184
},
172185
ExprKind::If(e, e2, e3) => {
173-
let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
174-
let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
175-
// If we know the `if` condition evaluates to `true`, don't check everything past it; it
176-
// should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
177-
if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
178-
return combine_seq(e1, e2);
179-
}
180-
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
181-
never_loop_expr(cx, e, ignore_ids, main_loop_id)
182-
});
183-
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
186+
let e1 = never_loop_expr(cx, e, local_labels, main_loop_id);
187+
combine_seq(e1, || {
188+
let e2 = never_loop_expr(cx, e2, local_labels, main_loop_id);
189+
let e3 = e3.as_ref().map_or(NeverLoopResult::Normal, |e| {
190+
never_loop_expr(cx, e, local_labels, main_loop_id)
191+
});
192+
combine_branches(e2, e3)
193+
})
184194
},
185195
ExprKind::Match(e, arms, _) => {
186-
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
196+
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
187197
if arms.is_empty() {
188198
e
189199
} else {
190-
let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
191-
combine_seq(e, arms)
200+
combine_seq(e, || {
201+
never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), local_labels, main_loop_id)
202+
})
192203
}
193204
},
194205
ExprKind::Block(b, l) => {
195206
if l.is_some() {
196-
ignore_ids.push(b.hir_id);
197-
}
198-
let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
199-
if l.is_some() {
200-
ignore_ids.pop();
207+
local_labels.push((b.hir_id, false));
201208
}
209+
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
210+
let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
202211
match ret {
203-
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
212+
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
204213
_ => ret,
205214
}
206215
},
@@ -211,73 +220,70 @@ fn never_loop_expr<'tcx>(
211220
if id == main_loop_id {
212221
NeverLoopResult::MayContinueMainLoop
213222
} else {
214-
NeverLoopResult::AlwaysBreak
223+
NeverLoopResult::Diverging
215224
}
216225
},
217-
// checks if break targets a block instead of a loop
218-
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
219-
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
220-
never_loop_expr(cx, e, ignore_ids, main_loop_id)
221-
}),
222-
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
223-
combine_seq(
224-
never_loop_expr(cx, e, ignore_ids, main_loop_id),
225-
NeverLoopResult::AlwaysBreak,
226-
)
227-
}),
228-
ExprKind::Become(e) => combine_seq(
229-
never_loop_expr(cx, e, ignore_ids, main_loop_id),
230-
NeverLoopResult::AlwaysBreak,
231-
),
232-
ExprKind::InlineAsm(asm) => asm
233-
.operands
234-
.iter()
235-
.map(|(o, _)| match o {
236-
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
237-
never_loop_expr(cx, expr, ignore_ids, main_loop_id)
238-
},
239-
InlineAsmOperand::Out { expr, .. } => {
240-
never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
241-
},
242-
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
243-
cx,
244-
&mut once(*in_expr).chain(out_expr.iter().copied()),
245-
ignore_ids,
246-
main_loop_id,
247-
),
248-
InlineAsmOperand::Const { .. }
249-
| InlineAsmOperand::SymFn { .. }
250-
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
226+
ExprKind::Break(_, e) | ExprKind::Ret(e) => {
227+
let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| {
228+
never_loop_expr(cx, e, local_labels, main_loop_id)
229+
});
230+
combine_seq(first, || {
231+
// checks if break targets a block instead of a loop
232+
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind {
233+
if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) {
234+
*reachable = true;
235+
}
236+
}
237+
NeverLoopResult::Diverging
251238
})
252-
.fold(NeverLoopResult::Otherwise, combine_seq),
239+
},
240+
ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
241+
NeverLoopResult::Diverging
242+
}),
243+
ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
244+
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
245+
never_loop_expr(cx, expr, local_labels, main_loop_id)
246+
},
247+
InlineAsmOperand::Out { expr, .. } => {
248+
never_loop_expr_all(cx, expr.iter().copied(), local_labels, main_loop_id)
249+
},
250+
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
251+
cx,
252+
once(*in_expr).chain(out_expr.iter().copied()),
253+
local_labels,
254+
main_loop_id,
255+
),
256+
InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
257+
NeverLoopResult::Normal
258+
},
259+
})),
253260
ExprKind::OffsetOf(_, _)
254261
| ExprKind::Yield(_, _)
255262
| ExprKind::Closure { .. }
256263
| ExprKind::Path(_)
257264
| ExprKind::ConstBlock(_)
258265
| ExprKind::Lit(_)
259-
| ExprKind::Err(_) => NeverLoopResult::Otherwise,
266+
| ExprKind::Err(_) => NeverLoopResult::Normal,
260267
}
261268
}
262269

263270
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
264271
cx: &LateContext<'tcx>,
265-
es: &mut T,
266-
ignore_ids: &mut Vec<HirId>,
272+
es: T,
273+
local_labels: &mut Vec<(HirId, bool)>,
267274
main_loop_id: HirId,
268275
) -> NeverLoopResult {
269-
es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
270-
.fold(NeverLoopResult::Otherwise, combine_seq)
276+
combine_seq_many(es.map(|e| never_loop_expr(cx, e, local_labels, main_loop_id)))
271277
}
272278

273279
fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
274280
cx: &LateContext<'tcx>,
275281
e: &mut T,
276-
ignore_ids: &mut Vec<HirId>,
282+
local_labels: &mut Vec<(HirId, bool)>,
277283
main_loop_id: HirId,
278284
) -> NeverLoopResult {
279-
e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
280-
combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
285+
e.fold(NeverLoopResult::Diverging, |a, b| {
286+
combine_branches(a, never_loop_expr(cx, b, local_labels, main_loop_id))
281287
})
282288
}
283289

0 commit comments

Comments
 (0)