Skip to content

Commit 346a9f2

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 9636687 commit 346a9f2

File tree

3 files changed

+73
-24
lines changed

3 files changed

+73
-24
lines changed

clippy_lints/src/loops/never_loop.rs

Lines changed: 33 additions & 23 deletions
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,35 +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,
67-
NeverLoopResult::Otherwise => second,
68-
}
69-
}
70-
71-
// Combine two results where both parts are called but not necessarily in order.
72-
#[must_use]
73-
fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult {
74-
match (left, right) {
75-
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
76-
NeverLoopResult::MayContinueMainLoop
70+
NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
71+
first
7772
},
78-
(NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
79-
(NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
73+
NeverLoopResult::Otherwise => second,
8074
}
8175
}
8276

8377
// Combine two results where only one of the part may have been executed.
8478
#[must_use]
85-
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
79+
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
8680
match (b1, b2) {
87-
(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+
},
8892
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
8993
NeverLoopResult::MayContinueMainLoop
9094
},
@@ -103,7 +107,7 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id
103107
let e = never_loop_expr(e, ignore_ids, main_loop_id);
104108
// els is an else block in a let...else binding
105109
els.map_or(e, |els| {
106-
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)
107111
})
108112
})
109113
.fold(NeverLoopResult::Otherwise, combine_seq)
@@ -139,7 +143,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
139143
ExprKind::Struct(_, fields, base) => {
140144
let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
141145
if let Some(base) = base {
142-
combine_both(fields, never_loop_expr(base, ignore_ids, main_loop_id))
146+
combine_seq(fields, never_loop_expr(base, ignore_ids, main_loop_id))
143147
} else {
144148
fields
145149
}
@@ -159,7 +163,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
159163
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
160164
never_loop_expr(e, ignore_ids, main_loop_id)
161165
});
162-
combine_seq(e1, combine_branches(e2, e3))
166+
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
163167
},
164168
ExprKind::Match(e, arms, _) => {
165169
let e = never_loop_expr(e, ignore_ids, main_loop_id);
@@ -176,7 +180,10 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
176180
}
177181
let ret = never_loop_block(b, ignore_ids, main_loop_id);
178182
ignore_ids.pop();
179-
ret
183+
match ret {
184+
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
185+
_ => ret,
186+
}
180187
},
181188
ExprKind::Continue(d) => {
182189
let id = d
@@ -190,7 +197,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
190197
},
191198
// checks if break targets a block instead of a loop
192199
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
193-
.map_or(NeverLoopResult::Otherwise, |e| {
200+
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
194201
never_loop_expr(e, ignore_ids, main_loop_id)
195202
}),
196203
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
@@ -218,7 +225,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
218225
| InlineAsmOperand::SymFn { .. }
219226
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
220227
})
221-
.fold(NeverLoopResult::Otherwise, combine_both),
228+
.fold(NeverLoopResult::Otherwise, combine_seq),
222229
ExprKind::Yield(_, _)
223230
| ExprKind::Closure { .. }
224231
| ExprKind::Path(_)
@@ -234,16 +241,19 @@ fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
234241
main_loop_id: HirId,
235242
) -> NeverLoopResult {
236243
es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
237-
.fold(NeverLoopResult::Otherwise, combine_both)
244+
.fold(NeverLoopResult::Otherwise, combine_seq)
238245
}
239246

240247
fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
241248
e: &mut T,
242249
ignore_ids: &mut Vec<HirId>,
243250
main_loop_id: HirId,
244251
) -> NeverLoopResult {
252+
let ignore_ids_saved = ignore_ids.clone();
245253
e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
246-
.fold(NeverLoopResult::AlwaysBreak, combine_branches)
254+
.fold(NeverLoopResult::AlwaysBreak, |a, b| {
255+
combine_branches(a, b, &ignore_ids_saved)
256+
})
247257
}
248258

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

tests/ui/never_loop.rs

Lines changed: 26 additions & 0 deletions
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

Lines changed: 14 additions & 1 deletion
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)