Skip to content

Commit 758e4ce

Browse files
committed
never_loop lint: properly handle break from block
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 ff9cf13 commit 758e4ce

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

clippy_lints/src/loops/never_loop.rs

+30-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,23 +59,36 @@ 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) {
75-
(NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
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+
(NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
89+
| (NeverLoopResult::AlwaysBreak, NeverLoopResult::IgnoreUntilEnd(_) | NeverLoopResult::AlwaysBreak) => {
90+
NeverLoopResult::AlwaysBreak
91+
},
7692
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
7793
NeverLoopResult::MayContinueMainLoop
7894
},
@@ -91,7 +107,7 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id
91107
let e = never_loop_expr(e, ignore_ids, main_loop_id);
92108
// els is an else block in a let...else binding
93109
els.map_or(e, |els| {
94-
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id))
110+
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids)
95111
})
96112
})
97113
.fold(NeverLoopResult::Otherwise, combine_seq)
@@ -147,7 +163,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
147163
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
148164
never_loop_expr(e, ignore_ids, main_loop_id)
149165
});
150-
combine_seq(e1, combine_branches(e2, e3))
166+
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
151167
},
152168
ExprKind::Match(e, arms, _) => {
153169
let e = never_loop_expr(e, ignore_ids, main_loop_id);
@@ -164,7 +180,10 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
164180
}
165181
let ret = never_loop_block(b, ignore_ids, main_loop_id);
166182
ignore_ids.pop();
167-
ret
183+
match ret {
184+
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
185+
_ => ret,
186+
}
168187
},
169188
ExprKind::Continue(d) => {
170189
let id = d
@@ -178,7 +197,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
178197
},
179198
// checks if break targets a block instead of a loop
180199
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
181-
.map_or(NeverLoopResult::Otherwise, |e| {
200+
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
182201
never_loop_expr(e, ignore_ids, main_loop_id)
183202
}),
184203
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
@@ -230,8 +249,11 @@ fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
230249
ignore_ids: &mut Vec<HirId>,
231250
main_loop_id: HirId,
232251
) -> NeverLoopResult {
252+
let ignore_ids_saved = ignore_ids.clone();
233253
e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
234-
.fold(NeverLoopResult::AlwaysBreak, combine_branches)
254+
.fold(NeverLoopResult::AlwaysBreak, |a, b| {
255+
combine_branches(a, b, &ignore_ids_saved)
256+
})
235257
}
236258

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

tests/ui/never_loop.rs

+26
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,30 @@ pub fn test20() {
250250
}
251251
}
252252

253+
// Issue 10304: code after break from block was not considered
254+
// unreachable code and was considered for further analysis of
255+
// whether the loop would ever be executed or not.
256+
pub fn test21() {
257+
for _ in 0..10 {
258+
'block: {
259+
break 'block;
260+
return;
261+
}
262+
println!("looped");
263+
}
264+
}
265+
266+
pub fn test22() {
267+
for _ in 0..10 {
268+
'block: {
269+
for _ in 0..20 {
270+
break 'block;
271+
}
272+
}
273+
println!("looped");
274+
}
275+
}
276+
253277
fn main() {
254278
test1();
255279
test2();
@@ -265,4 +289,6 @@ fn main() {
265289
test12(true, false);
266290
test13();
267291
test14();
292+
test21();
293+
test22();
268294
}

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:269: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)