@@ -3,6 +3,7 @@ mod explicit_counter_loop;
33mod explicit_into_iter_loop;
44mod explicit_iter_loop;
55mod for_kv_map;
6+ mod infinite_loops;
67mod iter_next_loop;
78mod manual_find;
89mod manual_flatten;
@@ -22,7 +23,7 @@ mod while_let_on_iterator;
2223
2324use clippy_config:: msrvs:: Msrv ;
2425use clippy_utils:: higher;
25- use rustc_hir:: { Expr , ExprKind , LoopSource , Pat } ;
26+ use rustc_hir:: { self as hir , Expr , ExprKind , LoopSource , Pat } ;
2627use rustc_lint:: { LateContext , LateLintPass } ;
2728use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
2829use rustc_span:: source_map:: Span ;
@@ -635,20 +636,64 @@ declare_clippy_lint! {
635636 "checking for emptiness of a `Vec` in the loop condition and popping an element in the body"
636637}
637638
638- pub struct Loops {
639+ declare_clippy_lint ! {
640+ /// ### What it does
641+ /// Checks for infinite loops in a function where the return type is not `!`
642+ /// and lint accordingly.
643+ ///
644+ /// ### Why is this bad?
645+ /// A loop should be gently exited somewhere, or at lease mark its parent function as
646+ /// never return (`!`).
647+ ///
648+ /// ### Example
649+ /// ```no_run,ignore
650+ /// fn run_forever() {
651+ /// loop {
652+ /// // do something
653+ /// }
654+ /// }
655+ /// ```
656+ /// If infinite loops are as intended:
657+ /// ```no_run,ignore
658+ /// fn run_forever() -> ! {
659+ /// loop {
660+ /// // do something
661+ /// }
662+ /// }
663+ /// ```
664+ /// Otherwise add a `break` or `return` condition:
665+ /// ```no_run,ignore
666+ /// fn run_forever() {
667+ /// loop {
668+ /// // do something
669+ /// if condition {
670+ /// break;
671+ /// }
672+ /// }
673+ /// }
674+ /// ```
675+ #[ clippy:: version = "1.75.0" ]
676+ pub INFINITE_LOOPS ,
677+ restriction,
678+ "possibly unintended infinite loops"
679+ }
680+
681+ pub struct Loops < ' tcx > {
639682 msrv : Msrv ,
640683 enforce_iter_loop_reborrow : bool ,
684+ parent_fn_ret_ty : Option < hir:: FnRetTy < ' tcx > > ,
641685}
642- impl Loops {
686+ impl < ' tcx > Loops < ' tcx > {
643687 pub fn new ( msrv : Msrv , enforce_iter_loop_reborrow : bool ) -> Self {
644688 Self {
645689 msrv,
646690 enforce_iter_loop_reborrow,
691+ parent_fn_ret_ty : None ,
647692 }
648693 }
649694}
650695
651- impl_lint_pass ! ( Loops => [
696+ impl_lint_pass ! ( Loops < ' _> => [
652697 MANUAL_MEMCPY ,
653698 MANUAL_FLATTEN ,
654699 NEEDLESS_RANGE_LOOP ,
@@ -669,9 +714,10 @@ impl_lint_pass!(Loops => [
669714 MANUAL_FIND ,
670715 MANUAL_WHILE_LET_SOME ,
671716 UNUSED_ENUMERATE_INDEX ,
717+ INFINITE_LOOPS ,
672718] ) ;
673719
674- impl < ' tcx > LateLintPass < ' tcx > for Loops {
720+ impl < ' tcx > LateLintPass < ' tcx > for Loops < ' tcx > {
675721 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
676722 let for_loop = higher:: ForLoop :: hir ( expr) ;
677723 if let Some ( higher:: ForLoop {
@@ -707,10 +753,13 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
707753 // check for `loop { if let {} else break }` that could be `while let`
708754 // (also matches an explicit "match" instead of "if let")
709755 // (even if the "match" or "if let" is used for declaration)
710- if let ExprKind :: Loop ( block, _ , LoopSource :: Loop , _) = expr. kind {
756+ if let ExprKind :: Loop ( block, label , LoopSource :: Loop , _) = expr. kind {
711757 // also check for empty `loop {}` statements, skipping those in #[panic_handler]
712758 empty_loop:: check ( cx, expr, block) ;
713759 while_let_loop:: check ( cx, expr, block) ;
760+ if let Some ( parent_fn_ret_ty) = self . parent_fn_ret_ty {
761+ infinite_loops:: check ( cx, expr, block, label, parent_fn_ret_ty) ;
762+ }
714763 }
715764
716765 while_let_on_iterator:: check ( cx, expr) ;
@@ -722,11 +771,25 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
722771 }
723772 }
724773
774+ fn check_fn (
775+ & mut self ,
776+ _: & LateContext < ' tcx > ,
777+ kind : hir:: intravisit:: FnKind < ' tcx > ,
778+ decl : & ' tcx hir:: FnDecl < ' tcx > ,
779+ _: & ' tcx hir:: Body < ' tcx > ,
780+ _: Span ,
781+ _: rustc_span:: def_id:: LocalDefId ,
782+ ) {
783+ if let hir:: intravisit:: FnKind :: ItemFn ( ..) = kind {
784+ self . parent_fn_ret_ty = Some ( decl. output ) ;
785+ }
786+ }
787+
725788 extract_msrv_attr ! ( LateContext ) ;
726789}
727790
728- impl Loops {
729- fn check_for_loop < ' tcx > (
791+ impl < ' tcx > Loops < ' tcx > {
792+ fn check_for_loop (
730793 & self ,
731794 cx : & LateContext < ' tcx > ,
732795 pat : & ' tcx Pat < ' _ > ,
0 commit comments