szepet updated this revision to Diff 141132.
szepet marked 3 inline comments as done.
szepet added a comment.

Removed to bugprone category,
skipping memberExpr cases for now in order to avoid false positives,
other small changes based on review comments.


https://reviews.llvm.org/D40937

Files:
  clang-tidy/bugprone/BugproneTidyModule.cpp
  clang-tidy/bugprone/CMakeLists.txt
  clang-tidy/bugprone/InfiniteLoopCheck.cpp
  clang-tidy/bugprone/InfiniteLoopCheck.h
  docs/ReleaseNotes.rst
  docs/clang-tidy/checks/bugprone-infinite-loop.rst
  docs/clang-tidy/checks/list.rst
  test/clang-tidy/bugprone-infinite-loop.cpp

Index: test/clang-tidy/bugprone-infinite-loop.cpp
===================================================================
--- /dev/null
+++ test/clang-tidy/bugprone-infinite-loop.cpp
@@ -0,0 +1,121 @@
+// RUN: %check_clang_tidy %s bugprone-infinite-loop %t
+
+void simple_infinite_loop1() {
+  int i = 0;
+  int j = 0;
+  while (i < 10) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: The condition variable (i) is not updated in the loop body [bugprone-infinite-loop]
+    j++;
+  }
+
+  do {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: The condition variable (i) is not updated in the loop body
+    j++;
+  } while (i < 10);
+
+  for (i = 0; i < 10; ++j) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: The condition variable (i) is not updated in the loop body
+  }
+}
+
+void simple_infinite_loop2() {
+  int i = 0;
+  int j = 0;
+  int Limit = 10;
+  while (i < Limit) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: None of the condition variables (i, Limit) are updated in the loop body [bugprone-infinite-loop]
+    j++;
+  }
+
+  do {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: None of the condition variables (i, Limit) are updated in the loop body
+    j++;
+  } while (i < Limit);
+
+  for (i = 0; i < Limit; ++j) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: None of the condition variables (i, Limit) are updated in the loop body
+  }
+}
+
+void simple_not_infinite() {
+  int i = 0;
+  int Limit = 100;
+  while (i < Limit) { // Not an error since 'Limit' is updated
+    Limit--;
+  }
+  do {
+    Limit--;
+  } while (i < Limit);
+
+  for (i = 0; i < Limit; Limit--) {
+  }
+}
+
+void escape_before1() {
+  int i = 0;
+  int Limit = 100;
+  int *p = &i;
+  while (i < Limit) { // Not an error, since p is alias of i.
+    *++p;
+  }
+
+  do {
+    *++p;
+  } while (i < Limit);
+
+  for (i = 0; i < Limit; *++p) {
+    ;
+  }
+}
+
+void escape_before2() {
+  int i = 0;
+  int Limit = 100;
+  int *p = &i;
+  while (i < Limit) { // We do not warn since the var 'i' is escaped but it is
+                      // an actual error, since the pointer 'p' is increased.
+    *(p++);
+  }
+}
+
+void escape_after() {
+  int i = 0;
+  int j = 0;
+  int Limit = 10;
+
+  while (i < Limit) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: None of the condition variables (i, Limit) are updated in the loop body
+  }
+  int *p = &i;
+}
+
+int glob;
+void glob_var(int &x) {
+  int i = 0, Limit = 100;
+  while (x < Limit) { // Not an error since 'x' can be an alias of glob.
+    glob++;
+  }
+}
+
+void glob_var2() {
+  int i = 0, Limit = 100;
+  while (glob < Limit) { // Since 'glob' is declared out of the function we do not warn.
+    i++;
+  }
+}
+
+struct X {
+  void memberExpr_test(int i) {
+    while(i < m) { // False negative: No warning, since skipping the case where
+                   // a memberExpr can be found in the condition.
+      ;
+    }
+  }
+
+  void memberExpr_test2(int i) {
+    while(i < m) {
+      --m;
+    }
+  }
+  int m;
+};
Index: docs/clang-tidy/checks/list.rst
===================================================================
--- docs/clang-tidy/checks/list.rst
+++ docs/clang-tidy/checks/list.rst
@@ -28,6 +28,7 @@
    bugprone-forwarding-reference-overload
    bugprone-inaccurate-erase
    bugprone-incorrect-roundings
+   bugprone-infinite-loop
    bugprone-integer-division
    bugprone-lambda-function-name
    bugprone-macro-parentheses
Index: docs/clang-tidy/checks/bugprone-infinite-loop.rst
===================================================================
--- /dev/null
+++ docs/clang-tidy/checks/bugprone-infinite-loop.rst
@@ -0,0 +1,30 @@
+.. title:: clang-tidy - bugprone-infinite-loop
+
+bugprone-infinite-loop
+======================
+
+Finds loops where none of the condition variables are updated in the body. This
+performs a very conservative check in order to avoid false positives and work
+only on integer types at the moment.
+
+Examples:
+
+.. code-block:: c++
+
+  void simple_infinite_loop() {
+    int i = 0;
+    int j = 0;
+    int Limit = 10;
+    while (i < Limit) { // Error, since none of the variables are updated.
+      j++;
+    }
+  }
+
+  void escape_before() {
+    int i = 0;
+    int Limit = 100;
+    int *p = &i;
+    while (i < Limit) { // Not an error, since p is alias of i.
+      *++p;
+    }
+  }
Index: docs/ReleaseNotes.rst
===================================================================
--- docs/ReleaseNotes.rst
+++ docs/ReleaseNotes.rst
@@ -64,6 +64,12 @@
 
 - New module ``zircon`` for checks related to Fuchsia's Zircon kernel.
 
+- New :doc:`bugprone-infinite-loop
+  <http://clang.llvm.org/extra/clang-tidy/checks/bugprone-infinite-loop.html>` check
+
+  Warns on loops where none of the condition variables are updated in the
+  body.
+
 - New :doc:`bugprone-throw-keyword-missing
   <clang-tidy/checks/bugprone-throw-keyword-missing>` check
 
Index: clang-tidy/bugprone/InfiniteLoopCheck.h
===================================================================
--- /dev/null
+++ clang-tidy/bugprone/InfiniteLoopCheck.h
@@ -0,0 +1,37 @@
+//===--- InfiniteLoopCheck.h - clang-tidy------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INFINITELOOPCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INFINITELOOPCHECK_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+/// The check aims to find loops where none of the condition variables are
+/// updated in the body. This performs a very conservative check in order to
+/// avoid false positives and work only on integer types at the moment.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-infinite-loop.html
+class InfiniteLoopCheck : public ClangTidyCheck {
+public:
+  InfiniteLoopCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INFINITELOOPCHECK_H
Index: clang-tidy/bugprone/InfiniteLoopCheck.cpp
===================================================================
--- /dev/null
+++ clang-tidy/bugprone/InfiniteLoopCheck.cpp
@@ -0,0 +1,193 @@
+//===--- InfiniteLoopCheck.cpp - clang-tidy -------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "InfiniteLoopCheck.h"
+#include "../utils/ExprSequence.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/CFG.h"
+
+using namespace clang::ast_matchers;
+using namespace clang::tidy::utils;
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+static internal::Matcher<Stmt> loopEndingStmt() {
+  return stmt(anyOf(breakStmt(), returnStmt(), gotoStmt(), cxxThrowExpr()));
+}
+
+static internal::Matcher<Expr> exprAccessToVar(const VarDecl *VD) {
+  return ignoringParenImpCasts(declRefExpr(hasDeclaration(equalsNode(VD))));
+}
+
+void InfiniteLoopCheck::registerMatchers(MatchFinder *Finder) {
+  const auto loopCondition = allOf(hasCondition(expr().bind("condition")),
+              anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")),
+                    hasAncestor(functionDecl().bind("containing-func"))),
+              unless(hasBody(hasDescendant(loopEndingStmt()))));
+
+  Finder->addMatcher(
+      stmt(anyOf(whileStmt(loopCondition), doStmt(loopCondition),
+                 forStmt(loopCondition)))
+          .bind("loop-stmt"),
+      this);
+}
+
+static internal::Matcher<Stmt>
+changeByOperator(internal::Matcher<Expr> ExprNodeMatcher) {
+  return anyOf(
+      unaryOperator(anyOf(hasOperatorName("--"), hasOperatorName("++")),
+                    hasUnaryOperand(ignoringParenImpCasts(
+                        ExprNodeMatcher))),
+                    binaryOperator(isAssignmentOperator(),
+                     hasLHS(ExprNodeMatcher)));
+}
+
+static internal::Matcher<Stmt>
+callByRef(internal::Matcher<Expr> ExprNodeMatcher) {
+  return callExpr(forEachArgumentWithParam(
+      ExprNodeMatcher,
+      parmVarDecl(hasType(references(qualType(unless(isConstQualified())))))));
+}
+
+static internal::Matcher<Stmt>
+assignedToRef(internal::Matcher<Expr> ExprNodeMatcher) {
+  return declStmt(hasDescendant(varDecl(
+      allOf(hasType(referenceType()),
+            hasInitializer(anyOf(
+                initListExpr(has(ExprNodeMatcher)),
+                ExprNodeMatcher))))));
+}
+
+static internal::Matcher<Stmt>
+getAddrTo(internal::Matcher<Expr> ExprNodeMatcher) {
+  return unaryOperator(
+      hasOperatorName("&"),
+      hasUnaryOperand(ExprNodeMatcher));
+}
+
+static internal::Matcher<Stmt> escapeStmt(const VarDecl *VD) {
+  // Escaping is covered as address-of operators, pass-by-ref function calls and
+  // reference initialization on the variable body.
+  return stmt(anyOf(callByRef(exprAccessToVar(VD)), getAddrTo(exprAccessToVar(VD)),
+                    assignedToRef(exprAccessToVar(VD))));
+}
+
+static internal::Matcher<Stmt> hasDescendantOrIsItselfStmt(internal::Matcher<Stmt> VarNodeMatcher) {
+  return anyOf(VarNodeMatcher, hasDescendant(VarNodeMatcher));
+}
+
+static internal::Matcher<Stmt> potentiallyModifyVarStmt(const VarDecl *VD) {
+  return hasDescendantOrIsItselfStmt(anyOf(changeByOperator(exprAccessToVar(VD)),
+                                           escapeStmt(VD)));
+}
+
+
+std::unique_ptr<ExprSequence> createSequence(Stmt *FunctionBody,
+                                             ASTContext &ASTCtx) {
+  CFG::BuildOptions Options;
+  Options.AddImplicitDtors = true;
+  Options.AddTemporaryDtors = true;
+  std::unique_ptr<CFG> TheCFG =
+      CFG::buildCFG(nullptr, FunctionBody, &ASTCtx, Options);
+  if (!TheCFG)
+    return std::unique_ptr<ExprSequence>();
+
+  return llvm::make_unique<ExprSequence>(
+      *(new ExprSequence(TheCFG.get(), &ASTCtx)));
+}
+
+void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *ContainingLambda =
+      Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda");
+  const auto *ContainingFunc =
+      Result.Nodes.getNodeAs<FunctionDecl>("containing-func");
+  const auto *Cond = Result.Nodes.getNodeAs<Expr>("condition");
+  const auto *LoopStmt = Result.Nodes.getNodeAs<Stmt>("loop-stmt");
+  auto &ASTCtx = *Result.Context;
+
+  Stmt *FunctionBody = nullptr;
+  if (ContainingLambda)
+    FunctionBody = ContainingLambda->getBody();
+  else if (ContainingFunc)
+    FunctionBody = ContainingFunc->getBody();
+  else
+    return;
+
+  auto CondVarMatches =
+      match(findAll(declRefExpr(to(varDecl().bind("condvar")))), *Cond, ASTCtx);
+
+  // Since MemberExprs can be modified by outer functions this case is excluded right now.
+  auto MemberExprsInCond = match(hasDescendantOrIsItselfStmt(memberExpr()), *Cond, ASTCtx);
+  if (!MemberExprsInCond.empty())
+    return;
+  std::unique_ptr<ExprSequence> Sequence = createSequence(FunctionBody, ASTCtx);
+
+  for (auto &E : CondVarMatches) {
+    auto CondVar = E.getNodeAs<VarDecl>("condvar");
+    // TODO: handle cases with non-integer condition variables
+    if (!CondVar->getType().getTypePtr()->isIntegerType())
+      return;
+
+    // In case the loop potentially changes any of the condition variables we
+    // assume the loop is not infinite.
+    SmallVector<BoundNodes, 1> Match;
+    // In case of for loop we check the increment stmt and the body for changes
+    // (excluding the init stmt).
+    if (auto ForLoop = dyn_cast<ForStmt>(LoopStmt)) {
+      if (ForLoop->getInc())
+        Match = match(potentiallyModifyVarStmt(CondVar), *ForLoop->getInc(),
+                      ASTCtx);
+      if (Match.empty() && ForLoop->getBody())
+        Match = match(potentiallyModifyVarStmt(CondVar), *ForLoop->getBody(),
+                      ASTCtx);
+    } else {
+      // In cases of while and do-while we can match the whole loop.
+      Match = match(potentiallyModifyVarStmt(CondVar), *LoopStmt, ASTCtx);
+    }
+    if (!Match.empty())
+      return;
+
+    // Skip the cases where any of the condition variables come from outside
+    // of the function in order to avoid false positives.
+    Match = match(stmt(hasDescendant(varDecl(equalsNode(CondVar)))),
+                  *FunctionBody, ASTCtx);
+    if (Match.empty())
+      return;
+
+    // When a condition variable is escaped before the loop we skip since we
+    // have no precise pointer analysis and want to avoid false positives.
+    Match = match(
+        stmt(forEachDescendant(stmt(escapeStmt(CondVar)).bind("escStmt"))),
+        *FunctionBody, ASTCtx);
+
+    for (auto &ES : Match) {
+      if (Sequence->potentiallyAfter(LoopStmt, ES.getNodeAs<Stmt>("escStmt")))
+        return;
+    }
+  }
+
+  // Creating the string containing the name of condition variables.
+  std::string CondVarNames;
+  for (auto &CVM : CondVarMatches) {
+    CondVarNames += CVM.getNodeAs<VarDecl>("condvar")->getNameAsString() + ", ";
+  }
+  CondVarNames.resize(CondVarNames.size() - 2);
+
+  diag(LoopStmt->getLocStart(),
+       "%plural{1:The condition variable|:None of the condition variables}0 "
+       "(%1) %plural{1:is not|:are}0 updated in the loop body")
+      << (unsigned)CondVarMatches.size() << CondVarNames;
+}
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
Index: clang-tidy/bugprone/CMakeLists.txt
===================================================================
--- clang-tidy/bugprone/CMakeLists.txt
+++ clang-tidy/bugprone/CMakeLists.txt
@@ -12,6 +12,7 @@
   ForwardingReferenceOverloadCheck.cpp
   InaccurateEraseCheck.cpp
   IncorrectRoundingsCheck.cpp
+  InfiniteLoopCheck.cpp
   IntegerDivisionCheck.cpp
   LambdaFunctionNameCheck.cpp
   MacroParenthesesCheck.cpp
Index: clang-tidy/bugprone/BugproneTidyModule.cpp
===================================================================
--- clang-tidy/bugprone/BugproneTidyModule.cpp
+++ clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -20,6 +20,7 @@
 #include "ForwardingReferenceOverloadCheck.h"
 #include "InaccurateEraseCheck.h"
 #include "IncorrectRoundingsCheck.h"
+#include "InfiniteLoopCheck.h"
 #include "IntegerDivisionCheck.h"
 #include "LambdaFunctionNameCheck.h"
 #include "MacroParenthesesCheck.h"
@@ -74,6 +75,8 @@
         "bugprone-inaccurate-erase");
     CheckFactories.registerCheck<IncorrectRoundingsCheck>(
         "bugprone-incorrect-roundings");
+    CheckFactories.registerCheck<InfiniteLoopCheck>(
+        "bugprone-infinite-loop");
     CheckFactories.registerCheck<IntegerDivisionCheck>(
         "bugprone-integer-division");
     CheckFactories.registerCheck<LambdaFunctionNameCheck>(
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to