Skip to content

Commit 657ee48

Browse files
committed
Ignore instructions following a break from block in never_loop lint
It is not sufficient to ignore break from a block inside the loop. Instructions after the break must be ignored, as they are unreachable. This is also true for all instructions in outer blocks and loops until the right block is reached.
1 parent e9dffa3 commit 657ee48

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed

clippy_lints/src/loops/never_loop.rs

+27-8
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub(super) fn check(
3939
});
4040
},
4141
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
42+
NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(),
4243
}
4344
}
4445

@@ -48,6 +49,8 @@ enum NeverLoopResult {
4849
AlwaysBreak,
4950
// A continue may occur for the main loop.
5051
MayContinueMainLoop,
52+
// Ignore everything until the end of the block with this id
53+
IgnoreUntilEnd(HirId),
5154
Otherwise,
5255
}
5356

@@ -56,22 +59,34 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
5659
match arg {
5760
NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
5861
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
62+
NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id),
5963
}
6064
}
6165

6266
// Combine two results for parts that are called in order.
6367
#[must_use]
6468
fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
6569
match first {
66-
NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first,
70+
NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
71+
first
72+
},
6773
NeverLoopResult::Otherwise => second,
6874
}
6975
}
7076

7177
// Combine two results where only one of the part may have been executed.
7278
#[must_use]
73-
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
79+
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
7480
match (b1, b2) {
81+
(NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
82+
if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
83+
NeverLoopResult::IgnoreUntilEnd(b)
84+
} else {
85+
NeverLoopResult::IgnoreUntilEnd(a)
86+
}
87+
},
88+
(i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
89+
| (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i,
7590
(NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
7691
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
7792
NeverLoopResult::MayContinueMainLoop
@@ -91,7 +106,7 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id
91106
let e = never_loop_expr(e, ignore_ids, main_loop_id);
92107
// els is an else block in a let...else binding
93108
els.map_or(e, |els| {
94-
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id))
109+
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids)
95110
})
96111
})
97112
.fold(NeverLoopResult::Otherwise, combine_seq)
@@ -147,7 +162,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
147162
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
148163
never_loop_expr(e, ignore_ids, main_loop_id)
149164
});
150-
combine_seq(e1, combine_branches(e2, e3))
165+
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
151166
},
152167
ExprKind::Match(e, arms, _) => {
153168
let e = never_loop_expr(e, ignore_ids, main_loop_id);
@@ -166,7 +181,10 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
166181
if l.is_some() {
167182
ignore_ids.pop();
168183
}
169-
ret
184+
match ret {
185+
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
186+
_ => ret,
187+
}
170188
},
171189
ExprKind::Continue(d) => {
172190
let id = d
@@ -180,7 +198,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
180198
},
181199
// checks if break targets a block instead of a loop
182200
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
183-
.map_or(NeverLoopResult::Otherwise, |e| {
201+
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
184202
never_loop_expr(e, ignore_ids, main_loop_id)
185203
}),
186204
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
@@ -232,8 +250,9 @@ fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
232250
ignore_ids: &mut Vec<HirId>,
233251
main_loop_id: HirId,
234252
) -> NeverLoopResult {
235-
e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
236-
.fold(NeverLoopResult::AlwaysBreak, combine_branches)
253+
e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
254+
combine_branches(a, never_loop_expr(b, ignore_ids, main_loop_id), ignore_ids)
255+
})
237256
}
238257

239258
fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {

tests/ui/never_loop.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,48 @@ pub fn test20() {
253253
pub fn test21() {
254254
loop {
255255
'a: {
256-
{ }
256+
{}
257257
break 'a;
258258
}
259259
}
260260
}
261261

262+
// Issue 10304: code after break from block was not considered
263+
// unreachable code and was considered for further analysis of
264+
// whether the loop would ever be executed or not.
265+
pub fn test22() {
266+
for _ in 0..10 {
267+
'block: {
268+
break 'block;
269+
return;
270+
}
271+
println!("looped");
272+
}
273+
}
274+
275+
pub fn test23() {
276+
for _ in 0..10 {
277+
'block: {
278+
for _ in 0..20 {
279+
break 'block;
280+
}
281+
}
282+
println!("looped");
283+
}
284+
}
285+
286+
pub fn test24() {
287+
'a: for _ in 0..10 {
288+
'b: {
289+
let x = Some(1);
290+
match x {
291+
None => break 'a,
292+
Some(_) => break 'b,
293+
}
294+
}
295+
}
296+
}
297+
262298
fn main() {
263299
test1();
264300
test2();

tests/ui/never_loop.stderr

+14-1
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,18 @@ LL | | }
126126
LL | | }
127127
| |_____^
128128

129-
error: aborting due to 11 previous errors
129+
error: this loop never actually loops
130+
--> $DIR/never_loop.rs:278:13
131+
|
132+
LL | / for _ in 0..20 {
133+
LL | | break 'block;
134+
LL | | }
135+
| |_____________^
136+
|
137+
help: if you need the first element of the iterator, try writing
138+
|
139+
LL | if let Some(_) = (0..20).next() {
140+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
141+
142+
error: aborting due to 12 previous errors
130143

0 commit comments

Comments
 (0)