parent
a8b5245ea3
commit
68011893d8
@ -1,6 +1,5 @@
|
||||
use super::utils::make_iterator_snippet;
|
||||
use super::NEVER_LOOP;
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::higher::ForLoop;
|
||||
use clippy_utils::source::snippet;
|
||||
@ -18,7 +17,7 @@ pub(super) fn check<'tcx>(
|
||||
for_loop: Option<&ForLoop<'_>>,
|
||||
) {
|
||||
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
|
||||
NeverLoopResult::AlwaysBreak => {
|
||||
NeverLoopResult::Diverging => {
|
||||
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
|
||||
if let Some(ForLoop {
|
||||
arg: iterator,
|
||||
@ -39,67 +38,76 @@ pub(super) fn check<'tcx>(
|
||||
}
|
||||
});
|
||||
},
|
||||
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
|
||||
NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(),
|
||||
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Normal => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// The `never_loop` analysis keeps track of three things:
|
||||
///
|
||||
/// * Has any (reachable) code path hit a `continue` of the main loop?
|
||||
/// * Is the current code path diverging (that is, the next expression is not reachable)
|
||||
/// * For each block label `'a` inside the main loop, has any (reachable) code path encountered a
|
||||
/// `break 'a`?
|
||||
///
|
||||
/// The first two bits of information are in this enum, and the last part is in the
|
||||
/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
|
||||
/// scope.
|
||||
#[derive(Copy, Clone)]
|
||||
enum NeverLoopResult {
|
||||
// A break/return always get triggered but not necessarily for the main loop.
|
||||
AlwaysBreak,
|
||||
// A continue may occur for the main loop.
|
||||
/// A continue may occur for the main loop.
|
||||
MayContinueMainLoop,
|
||||
// Ignore everything until the end of the block with this id
|
||||
IgnoreUntilEnd(HirId),
|
||||
Otherwise,
|
||||
/// We have not encountered any main loop continue,
|
||||
/// but we are diverging (subsequent control flow is not reachable)
|
||||
Diverging,
|
||||
/// We have not encountered any main loop continue,
|
||||
/// and subsequent control flow is (possibly) reachable
|
||||
Normal,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
|
||||
match arg {
|
||||
NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
|
||||
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
|
||||
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
|
||||
NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id),
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two results for parts that are called in order.
|
||||
#[must_use]
|
||||
fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
|
||||
fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult {
|
||||
match first {
|
||||
NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
|
||||
first
|
||||
},
|
||||
NeverLoopResult::Otherwise => second,
|
||||
NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first,
|
||||
NeverLoopResult::Normal => second(),
|
||||
}
|
||||
}
|
||||
|
||||
// Combine an iterator of results for parts that are called in order.
|
||||
#[must_use]
|
||||
fn combine_seq_many(iter: impl IntoIterator<Item = NeverLoopResult>) -> NeverLoopResult {
|
||||
for e in iter {
|
||||
if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
NeverLoopResult::Normal
|
||||
}
|
||||
|
||||
// Combine two results where only one of the part may have been executed.
|
||||
#[must_use]
|
||||
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
|
||||
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
|
||||
match (b1, b2) {
|
||||
(NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
|
||||
if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
|
||||
NeverLoopResult::IgnoreUntilEnd(b)
|
||||
} else {
|
||||
NeverLoopResult::IgnoreUntilEnd(a)
|
||||
}
|
||||
},
|
||||
(i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
|
||||
| (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i,
|
||||
(NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
|
||||
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
|
||||
NeverLoopResult::MayContinueMainLoop
|
||||
},
|
||||
(NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
|
||||
(NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal,
|
||||
(NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging,
|
||||
}
|
||||
}
|
||||
|
||||
fn never_loop_block<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
block: &Block<'tcx>,
|
||||
ignore_ids: &mut Vec<HirId>,
|
||||
local_labels: &mut Vec<(HirId, bool)>,
|
||||
main_loop_id: HirId,
|
||||
) -> NeverLoopResult {
|
||||
let iter = block
|
||||
@ -107,15 +115,21 @@ fn never_loop_block<'tcx>(
|
||||
.iter()
|
||||
.filter_map(stmt_to_expr)
|
||||
.chain(block.expr.map(|expr| (expr, None)));
|
||||
|
||||
iter.map(|(e, els)| {
|
||||
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
|
||||
combine_seq_many(iter.map(|(e, els)| {
|
||||
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
// els is an else block in a let...else binding
|
||||
els.map_or(e, |els| {
|
||||
combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
|
||||
combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) {
|
||||
// Returning MayContinueMainLoop here means that
|
||||
// we will not evaluate the rest of the body
|
||||
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
|
||||
// An else block always diverges, so the Normal case should not happen,
|
||||
// but the analysis is approximate so it might return Normal anyway.
|
||||
// Returning Normal here says that nothing more happens on the main path
|
||||
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
|
||||
})
|
||||
})
|
||||
})
|
||||
.fold(NeverLoopResult::Otherwise, combine_seq)
|
||||
}))
|
||||
}
|
||||
|
||||
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
|
||||
fn never_loop_expr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &Expr<'tcx>,
|
||||
ignore_ids: &mut Vec<HirId>,
|
||||
local_labels: &mut Vec<(HirId, bool)>,
|
||||
main_loop_id: HirId,
|
||||
) -> NeverLoopResult {
|
||||
match expr.kind {
|
||||
@ -141,66 +155,61 @@ fn never_loop_expr<'tcx>(
|
||||
| ExprKind::Field(e, _)
|
||||
| ExprKind::AddrOf(_, _, e)
|
||||
| ExprKind::Repeat(e, _)
|
||||
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
|
||||
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
|
||||
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
|
||||
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, local_labels, main_loop_id),
|
||||
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, local_labels, main_loop_id),
|
||||
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, es.iter(), local_labels, main_loop_id),
|
||||
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
|
||||
cx,
|
||||
&mut std::iter::once(receiver).chain(es.iter()),
|
||||
ignore_ids,
|
||||
std::iter::once(receiver).chain(es.iter()),
|
||||
local_labels,
|
||||
main_loop_id,
|
||||
),
|
||||
ExprKind::Struct(_, fields, base) => {
|
||||
let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
|
||||
let fields = never_loop_expr_all(cx, fields.iter().map(|f| f.expr), local_labels, main_loop_id);
|
||||
if let Some(base) = base {
|
||||
combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
|
||||
combine_seq(fields, || never_loop_expr(cx, base, local_labels, main_loop_id))
|
||||
} else {
|
||||
fields
|
||||
}
|
||||
},
|
||||
ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
|
||||
ExprKind::Call(e, es) => never_loop_expr_all(cx, once(e).chain(es.iter()), local_labels, main_loop_id),
|
||||
ExprKind::Binary(_, e1, e2)
|
||||
| ExprKind::Assign(e1, e2, _)
|
||||
| ExprKind::AssignOp(_, e1, e2)
|
||||
| ExprKind::Index(e1, e2, _) => {
|
||||
never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id)
|
||||
},
|
||||
| ExprKind::Index(e1, e2, _) => never_loop_expr_all(cx, [e1, e2].iter().copied(), local_labels, main_loop_id),
|
||||
ExprKind::Loop(b, _, _, _) => {
|
||||
// Break can come from the inner loop so remove them.
|
||||
absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
|
||||
// We don't attempt to track reachability after a loop,
|
||||
// just assume there may have been a break somewhere
|
||||
absorb_break(never_loop_block(cx, b, local_labels, main_loop_id))
|
||||
},
|
||||
ExprKind::If(e, e2, e3) => {
|
||||
let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
|
||||
let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
|
||||
// If we know the `if` condition evaluates to `true`, don't check everything past it; it
|
||||
// should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
|
||||
if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
|
||||
return combine_seq(e1, e2);
|
||||
}
|
||||
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
|
||||
never_loop_expr(cx, e, ignore_ids, main_loop_id)
|
||||
});
|
||||
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
|
||||
let e1 = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
combine_seq(e1, || {
|
||||
let e2 = never_loop_expr(cx, e2, local_labels, main_loop_id);
|
||||
let e3 = e3.as_ref().map_or(NeverLoopResult::Normal, |e| {
|
||||
never_loop_expr(cx, e, local_labels, main_loop_id)
|
||||
});
|
||||
combine_branches(e2, e3)
|
||||
})
|
||||
},
|
||||
ExprKind::Match(e, arms, _) => {
|
||||
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
|
||||
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
if arms.is_empty() {
|
||||
e
|
||||
} else {
|
||||
let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
|
||||
combine_seq(e, arms)
|
||||
combine_seq(e, || {
|
||||
never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), local_labels, main_loop_id)
|
||||
})
|
||||
}
|
||||
},
|
||||
ExprKind::Block(b, l) => {
|
||||
if l.is_some() {
|
||||
ignore_ids.push(b.hir_id);
|
||||
}
|
||||
let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
|
||||
if l.is_some() {
|
||||
ignore_ids.pop();
|
||||
local_labels.push((b.hir_id, false));
|
||||
}
|
||||
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
|
||||
let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
|
||||
match ret {
|
||||
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
|
||||
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
|
||||
_ => ret,
|
||||
}
|
||||
},
|
||||
@ -211,73 +220,70 @@ fn never_loop_expr<'tcx>(
|
||||
if id == main_loop_id {
|
||||
NeverLoopResult::MayContinueMainLoop
|
||||
} else {
|
||||
NeverLoopResult::AlwaysBreak
|
||||
NeverLoopResult::Diverging
|
||||
}
|
||||
},
|
||||
// checks if break targets a block instead of a loop
|
||||
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
|
||||
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
|
||||
never_loop_expr(cx, e, ignore_ids, main_loop_id)
|
||||
}),
|
||||
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
|
||||
combine_seq(
|
||||
never_loop_expr(cx, e, ignore_ids, main_loop_id),
|
||||
NeverLoopResult::AlwaysBreak,
|
||||
)
|
||||
}),
|
||||
ExprKind::Become(e) => combine_seq(
|
||||
never_loop_expr(cx, e, ignore_ids, main_loop_id),
|
||||
NeverLoopResult::AlwaysBreak,
|
||||
),
|
||||
ExprKind::InlineAsm(asm) => asm
|
||||
.operands
|
||||
.iter()
|
||||
.map(|(o, _)| match o {
|
||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||
never_loop_expr(cx, expr, ignore_ids, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::Out { expr, .. } => {
|
||||
never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
|
||||
cx,
|
||||
&mut once(*in_expr).chain(out_expr.iter().copied()),
|
||||
ignore_ids,
|
||||
main_loop_id,
|
||||
),
|
||||
InlineAsmOperand::Const { .. }
|
||||
| InlineAsmOperand::SymFn { .. }
|
||||
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
|
||||
ExprKind::Break(_, e) | ExprKind::Ret(e) => {
|
||||
let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| {
|
||||
never_loop_expr(cx, e, local_labels, main_loop_id)
|
||||
});
|
||||
combine_seq(first, || {
|
||||
// checks if break targets a block instead of a loop
|
||||
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind {
|
||||
if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) {
|
||||
*reachable = true;
|
||||
}
|
||||
}
|
||||
NeverLoopResult::Diverging
|
||||
})
|
||||
.fold(NeverLoopResult::Otherwise, combine_seq),
|
||||
},
|
||||
ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
|
||||
NeverLoopResult::Diverging
|
||||
}),
|
||||
ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
|
||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||
never_loop_expr(cx, expr, local_labels, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::Out { expr, .. } => {
|
||||
never_loop_expr_all(cx, expr.iter().copied(), local_labels, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
|
||||
cx,
|
||||
once(*in_expr).chain(out_expr.iter().copied()),
|
||||
local_labels,
|
||||
main_loop_id,
|
||||
),
|
||||
InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
|
||||
NeverLoopResult::Normal
|
||||
},
|
||||
})),
|
||||
ExprKind::OffsetOf(_, _)
|
||||
| ExprKind::Yield(_, _)
|
||||
| ExprKind::Closure { .. }
|
||||
| ExprKind::Path(_)
|
||||
| ExprKind::ConstBlock(_)
|
||||
| ExprKind::Lit(_)
|
||||
| ExprKind::Err(_) => NeverLoopResult::Otherwise,
|
||||
| ExprKind::Err(_) => NeverLoopResult::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
|
||||
cx: &LateContext<'tcx>,
|
||||
es: &mut T,
|
||||
ignore_ids: &mut Vec<HirId>,
|
||||
es: T,
|
||||
local_labels: &mut Vec<(HirId, bool)>,
|
||||
main_loop_id: HirId,
|
||||
) -> NeverLoopResult {
|
||||
es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
|
||||
.fold(NeverLoopResult::Otherwise, combine_seq)
|
||||
combine_seq_many(es.map(|e| never_loop_expr(cx, e, local_labels, main_loop_id)))
|
||||
}
|
||||
|
||||
fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &mut T,
|
||||
ignore_ids: &mut Vec<HirId>,
|
||||
local_labels: &mut Vec<(HirId, bool)>,
|
||||
main_loop_id: HirId,
|
||||
) -> NeverLoopResult {
|
||||
e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
|
||||
combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
|
||||
e.fold(NeverLoopResult::Diverging, |a, b| {
|
||||
combine_branches(a, never_loop_expr(cx, b, local_labels, main_loop_id))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -337,10 +337,8 @@ pub fn test26() {
|
||||
|
||||
pub fn test27() {
|
||||
loop {
|
||||
//~^ ERROR: this loop never actually loops
|
||||
'label: {
|
||||
let x = true;
|
||||
// Lints because we cannot prove it's always `true`
|
||||
if x {
|
||||
break 'label;
|
||||
}
|
||||
@ -349,6 +347,44 @@ pub fn test27() {
|
||||
}
|
||||
}
|
||||
|
||||
// issue 11004
|
||||
pub fn test29() {
|
||||
loop {
|
||||
'label: {
|
||||
if true {
|
||||
break 'label;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test30() {
|
||||
'a: loop {
|
||||
'b: {
|
||||
for j in 0..2 {
|
||||
if j == 1 {
|
||||
break 'b;
|
||||
}
|
||||
}
|
||||
break 'a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test31(b: bool) {
|
||||
'a: loop {
|
||||
'b: {
|
||||
'c: loop {
|
||||
//~^ ERROR: this loop never actually loops
|
||||
if b { break 'c } else { break 'b }
|
||||
}
|
||||
continue 'a;
|
||||
}
|
||||
break 'a;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test1();
|
||||
test2();
|
||||
|
@ -153,16 +153,13 @@ LL | if let Some(_) = (0..20).next() {
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: this loop never actually loops
|
||||
--> $DIR/never_loop.rs:339:5
|
||||
--> $DIR/never_loop.rs:378:13
|
||||
|
|
||||
LL | / loop {
|
||||
LL | / 'c: loop {
|
||||
LL | |
|
||||
LL | | 'label: {
|
||||
LL | | let x = true;
|
||||
... |
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_____^
|
||||
LL | | if b { break 'c } else { break 'b }
|
||||
LL | | }
|
||||
| |_____________^
|
||||
|
||||
error: aborting due to 14 previous errors
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user