Skip to content

Commit 82db08b

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 14af563 commit 82db08b

File tree

2 files changed

+47
-8
lines changed

2 files changed

+47
-8
lines changed

clippy_lints/src/loops/never_loop.rs

+33-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,14 +59,17 @@ 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
}
@@ -75,16 +81,29 @@ fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResul
7581
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
7682
NeverLoopResult::MayContinueMainLoop
7783
},
84+
(NeverLoopResult::IgnoreUntilEnd(a), _) | (_, NeverLoopResult::IgnoreUntilEnd(a)) => {
85+
NeverLoopResult::IgnoreUntilEnd(a)
86+
},
7887
(NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
7988
(NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
8089
}
8190
}
8291

8392
// Combine two results where only one of the part may have been executed.
8493
#[must_use]
85-
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
94+
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
8695
match (b1, b2) {
87-
(NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
96+
(NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
97+
if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
98+
NeverLoopResult::IgnoreUntilEnd(b)
99+
} else {
100+
NeverLoopResult::IgnoreUntilEnd(a)
101+
}
102+
},
103+
(NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
104+
| (NeverLoopResult::AlwaysBreak, NeverLoopResult::IgnoreUntilEnd(_) | NeverLoopResult::AlwaysBreak) => {
105+
NeverLoopResult::AlwaysBreak
106+
},
88107
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
89108
NeverLoopResult::MayContinueMainLoop
90109
},
@@ -103,7 +122,7 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id
103122
let e = never_loop_expr(e, ignore_ids, main_loop_id);
104123
// els is an else block in a let...else binding
105124
els.map_or(e, |els| {
106-
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id))
125+
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids)
107126
})
108127
})
109128
.fold(NeverLoopResult::Otherwise, combine_seq)
@@ -159,7 +178,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
159178
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
160179
never_loop_expr(e, ignore_ids, main_loop_id)
161180
});
162-
combine_seq(e1, combine_branches(e2, e3))
181+
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
163182
},
164183
ExprKind::Match(e, arms, _) => {
165184
let e = never_loop_expr(e, ignore_ids, main_loop_id);
@@ -176,7 +195,10 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
176195
}
177196
let ret = never_loop_block(b, ignore_ids, main_loop_id);
178197
ignore_ids.pop();
179-
ret
198+
match ret {
199+
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
200+
_ => ret,
201+
}
180202
},
181203
ExprKind::Continue(d) => {
182204
let id = d
@@ -190,7 +212,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
190212
},
191213
// checks if break targets a block instead of a loop
192214
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
193-
.map_or(NeverLoopResult::Otherwise, |e| {
215+
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
194216
never_loop_expr(e, ignore_ids, main_loop_id)
195217
}),
196218
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
@@ -242,8 +264,11 @@ fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
242264
ignore_ids: &mut Vec<HirId>,
243265
main_loop_id: HirId,
244266
) -> NeverLoopResult {
267+
let ignore_ids_saved = ignore_ids.clone();
245268
e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
246-
.fold(NeverLoopResult::AlwaysBreak, combine_branches)
269+
.fold(NeverLoopResult::AlwaysBreak, |a, b| {
270+
combine_branches(a, b, &ignore_ids_saved)
271+
})
247272
}
248273

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

tests/ui/never_loop.rs

+14
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,19 @@ 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+
253266
fn main() {
254267
test1();
255268
test2();
@@ -265,4 +278,5 @@ fn main() {
265278
test12(true, false);
266279
test13();
267280
test14();
281+
test21();
268282
}

0 commit comments

Comments
 (0)