Author: maskray Date: Tue Sep 24 02:06:31 2019 New Revision: 372706 URL: http://llvm.org/viewvc/llvm-project?rev=372706&view=rev Log: [clang-tidy] Add missing InfiniteLoopCheck.h, InfiniteLoopCheck.cpp and test from D64736
Added: clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.cpp clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.h clang-tools-extra/trunk/test/clang-tidy/bugprone-infinite-loop.cpp Modified: clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt clang-tools-extra/trunk/docs/ReleaseNotes.rst clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Modified: clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp?rev=372706&r1=372705&r2=372706&view=diff ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp (original) +++ clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp Tue Sep 24 02:06:31 2019 @@ -23,6 +23,7 @@ #include "ForwardingReferenceOverloadCheck.h" #include "InaccurateEraseCheck.h" #include "IncorrectRoundingsCheck.h" +#include "InfiniteLoopCheck.h" #include "IntegerDivisionCheck.h" #include "LambdaFunctionNameCheck.h" #include "MacroParenthesesCheck.h" @@ -88,6 +89,8 @@ public: "bugprone-inaccurate-erase"); CheckFactories.registerCheck<IncorrectRoundingsCheck>( "bugprone-incorrect-roundings"); + CheckFactories.registerCheck<InfiniteLoopCheck>( + "bugprone-infinite-loop"); CheckFactories.registerCheck<IntegerDivisionCheck>( "bugprone-integer-division"); CheckFactories.registerCheck<LambdaFunctionNameCheck>( Modified: clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt?rev=372706&r1=372705&r2=372706&view=diff ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt (original) +++ clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt Tue Sep 24 02:06:31 2019 @@ -15,6 +15,7 @@ add_clang_library(clangTidyBugproneModul ForwardingReferenceOverloadCheck.cpp InaccurateEraseCheck.cpp IncorrectRoundingsCheck.cpp + InfiniteLoopCheck.cpp IntegerDivisionCheck.cpp LambdaFunctionNameCheck.cpp MacroParenthesesCheck.cpp Added: clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.cpp?rev=372706&view=auto ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.cpp (added) +++ clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.cpp Tue Sep 24 02:06:31 2019 @@ -0,0 +1,189 @@ +//===--- InfiniteLoopCheck.cpp - clang-tidy -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "InfiniteLoopCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +static internal::Matcher<Stmt> +loopEndingStmt(internal::Matcher<Stmt> Internal) { + return stmt(anyOf(breakStmt(Internal), returnStmt(Internal), + gotoStmt(Internal), cxxThrowExpr(Internal), + callExpr(Internal, callee(functionDecl(isNoReturn()))))); +} + +/// \brief Return whether `S` is a reference to the declaration of `Var`. +static bool isAccessForVar(const Stmt *S, const VarDecl *Var) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) + return DRE->getDecl() == Var; + + return false; +} + +/// \brief Return whether `Var` has a pointer of reference in `S`. +static bool isPtrOrReferenceForVar(const Stmt *S, const VarDecl *Var) { + if (const auto *DS = dyn_cast<DeclStmt>(S)) { + for (const Decl *D : DS->getDeclGroup()) { + if (const auto *LeftVar = dyn_cast<VarDecl>(D)) { + if (LeftVar->hasInit() && LeftVar->getType()->isReferenceType()) { + return isAccessForVar(LeftVar->getInit(), Var); + } + } + } + } else if (const auto *UnOp = dyn_cast<UnaryOperator>(S)) { + if (UnOp->getOpcode() == UO_AddrOf) + return isAccessForVar(UnOp->getSubExpr(), Var); + } + + return false; +} + +/// \brief Return whether `Var` has a pointer of reference in `S`. +static bool hasPtrOrReferenceInStmt(const Stmt *S, const VarDecl *Var) { + if (isPtrOrReferenceForVar(S, Var)) + return true; + + for (const Stmt *Child : S->children()) { + if (!Child) + continue; + + if (hasPtrOrReferenceInStmt(Child, Var)) + return true; + } + + return false; +} + +/// \brief Return whether `Var` has a pointer of reference in `Func`. +static bool hasPtrOrReferenceInFunc(const FunctionDecl *Func, + const VarDecl *Var) { + return hasPtrOrReferenceInStmt(Func->getBody(), Var); +} + +/// \brief Return whether `Var` was changed in `LoopStmt`. +static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var, + ASTContext *Context) { + if (const auto *ForLoop = dyn_cast<ForStmt>(LoopStmt)) + return (ForLoop->getInc() && + ExprMutationAnalyzer(*ForLoop->getInc(), *Context) + .isMutated(Var)) || + (ForLoop->getBody() && + ExprMutationAnalyzer(*ForLoop->getBody(), *Context) + .isMutated(Var)) || + (ForLoop->getCond() && + ExprMutationAnalyzer(*ForLoop->getCond(), *Context).isMutated(Var)); + + return ExprMutationAnalyzer(*LoopStmt, *Context).isMutated(Var); +} + +/// \brief Return whether `Cond` is a variable that is possibly changed in +/// `LoopStmt`. +static bool isVarThatIsPossiblyChanged(const FunctionDecl *Func, + const Stmt *LoopStmt, const Stmt *Cond, + ASTContext *Context) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) { + if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) { + if (!Var->isLocalVarDeclOrParm()) + return true; + + if (Var->getType().isVolatileQualified()) + return true; + + if (!Var->getType().getTypePtr()->isIntegerType()) + return true; + + return hasPtrOrReferenceInFunc(Func, Var) || + isChanged(LoopStmt, Var, Context); + // FIXME: Track references. + } + } else if (isa<MemberExpr>(Cond) || isa<CallExpr>(Cond)) { + // FIXME: Handle MemberExpr. + return true; + } + + return false; +} + +/// \brief Return whether at least one variable of `Cond` changed in `LoopStmt`. +static bool isAtLeastOneCondVarChanged(const FunctionDecl *Func, + const Stmt *LoopStmt, const Stmt *Cond, + ASTContext *Context) { + if (isVarThatIsPossiblyChanged(Func, LoopStmt, Cond, Context)) + return true; + + for (const Stmt *Child : Cond->children()) { + if (!Child) + continue; + + if (isAtLeastOneCondVarChanged(Func, LoopStmt, Child, Context)) + return true; + } + return false; +} + +/// \brief Return the variable names in `Cond`. +static std::string getCondVarNames(const Stmt *Cond) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) { + if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) + return Var->getName(); + } + + std::string Result; + for (const Stmt *Child : Cond->children()) { + if (!Child) + continue; + + std::string NewNames = getCondVarNames(Child); + if (!Result.empty() && !NewNames.empty()) + Result += ", "; + Result += NewNames; + } + return Result; +} + +void InfiniteLoopCheck::registerMatchers(MatchFinder *Finder) { + const auto LoopCondition = allOf( + hasCondition( + expr(forFunction(functionDecl().bind("func"))).bind("condition")), + unless(hasBody(hasDescendant( + loopEndingStmt(forFunction(equalsBoundNode("func"))))))); + + Finder->addMatcher(stmt(anyOf(whileStmt(LoopCondition), doStmt(LoopCondition), + forStmt(LoopCondition))) + .bind("loop-stmt"), + this); +} + +void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Cond = Result.Nodes.getNodeAs<Expr>("condition"); + const auto *LoopStmt = Result.Nodes.getNodeAs<Stmt>("loop-stmt"); + const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); + + if (isAtLeastOneCondVarChanged(Func, LoopStmt, Cond, Result.Context)) + return; + + std::string CondVarNames = getCondVarNames(Cond); + if (CondVarNames.empty()) + return; + + diag(LoopStmt->getBeginLoc(), + "this loop is infinite; none of its condition variables (%0)" + " are updated in the loop body") + << CondVarNames; +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Added: clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.h URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.h?rev=372706&view=auto ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.h (added) +++ clang-tools-extra/trunk/clang-tidy/bugprone/InfiniteLoopCheck.h Tue Sep 24 02:06:31 2019 @@ -0,0 +1,35 @@ +//===--- InfiniteLoopCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INFINITELOOPCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INFINITELOOPCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds obvious infinite loops (loops where the condition variable is +/// not changed at all). +/// +/// 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 Modified: clang-tools-extra/trunk/docs/ReleaseNotes.rst URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/ReleaseNotes.rst?rev=372706&r1=372705&r2=372706&view=diff ============================================================================== --- clang-tools-extra/trunk/docs/ReleaseNotes.rst (original) +++ clang-tools-extra/trunk/docs/ReleaseNotes.rst Tue Sep 24 02:06:31 2019 @@ -73,6 +73,12 @@ Improvements to clang-tidy Finds instances where variables with static storage are initialized dynamically in header files. +- New :doc:`bugprone-infinite-loop + <clang-tidy/checks/bugprone-infinite-loop>` check. + + Finds obvious infinite loops (loops where the condition variable is not + changed at all). + - New :doc:`linuxkernel-must-use-errs <clang-tidy/checks/linuxkernel-must-use-errs>` check. Modified: clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst?rev=372706&r1=372705&r2=372706&view=diff ============================================================================== --- clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst (original) +++ clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Tue Sep 24 02:06:31 2019 @@ -51,6 +51,7 @@ Clang-Tidy Checks bugprone-forwarding-reference-overload bugprone-inaccurate-erase bugprone-incorrect-roundings + bugprone-infinite-loop bugprone-integer-division bugprone-lambda-function-name bugprone-macro-parentheses Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-infinite-loop.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-infinite-loop.cpp?rev=372706&view=auto ============================================================================== --- clang-tools-extra/trunk/test/clang-tidy/bugprone-infinite-loop.cpp (added) +++ clang-tools-extra/trunk/test/clang-tidy/bugprone-infinite-loop.cpp Tue Sep 24 02:06:31 2019 @@ -0,0 +1,298 @@ +// 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: this loop is infinite; none of its condition variables (i) are updated in the loop body [bugprone-infinite-loop] + j++; + } + + do { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i) are updated in the loop body [bugprone-infinite-loop] + j++; + } while (i < 10); + + for (i = 0; i < 10; ++j) { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i) are updated in the loop body [bugprone-infinite-loop] + } +} + +void simple_infinite_loop2() { + int i = 0; + int j = 0; + int Limit = 10; + while (i < Limit) { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i, Limit) are updated in the loop body [bugprone-infinite-loop] + j++; + } + + do { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i, Limit) are updated in the loop body [bugprone-infinite-loop] + j++; + } while (i < Limit); + + for (i = 0; i < Limit; ++j) { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i, Limit) are updated in the loop body [bugprone-infinite-loop] + } +} + +void simple_not_infinite1() { + 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 simple_not_infinite2() { + for (int i = 10; i-- > 0;) { + // Not an error, since loop variable is modified in its condition part. + } +} + +int unknown_function(); + +void function_call() { + int i = 0; + while (i < unknown_function()) { + // Not an error, since the function may return different values. + } + + do { + // Not an error, since the function may return different values. + } while (i < unknown_function()); + + for (i = 0; i < unknown_function();) { + // Not an error, since the function may return different values. + } +} + +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 &ii = i; + while (i < Limit) { + // Not an error, since ii is alias of i. + ii++; + } + + do { + ii++; + } while (i < Limit); + + for (i = 0; i < Limit; ++ii) { + } +} + +void escape_inside1() { + int i = 0; + int Limit = 100; + int *p = &i; + while (i < Limit) { + // Not an error, since *p is alias of i. + int *p = &i; + (*p)++; + } + + do { + int *p = &i; + (*p)++; + } while (i < Limit); +} + +void escape_inside2() { + int i = 0; + int Limit = 100; + while (i < Limit) { + // Not an error, since ii is alias of i. + int &ii = i; + ii++; + } + + do { + int &ii = i; + ii++; + } while (i < Limit); +} + +int glob; + +void global1(int &x) { + int i = 0, Limit = 100; + while (x < Limit) { + // Not an error since 'x' can be an alias of 'glob'. + glob++; + } +} + +void global2() { + int i = 0, Limit = 100; + while (glob < Limit) { + // Since 'glob' is declared out of the function we do not warn. + i++; + } +} + +struct X { + int m; + + void change_m(); + + void member_expr1(int i) { + while (i < m) { + // False negative: No warning, since skipping the case where a struct or + // class can be found in its condition. + ; + } + } + + void member_expr2(int i) { + while (i < m) { + --m; + } + } + + void member_expr3(int i) { + while (i < m) { + change_m(); + } + } +}; + +void array_index() { + int i = 0; + int v[10]; + while (i < 10) { + v[i++] = 0; + } + + i = 0; + do { + v[i++] = 0; + } while (i < 9); + + for (i = 0; i < 10;) { + v[i++] = 0; + } + + for (i = 0; i < 10; v[i++] = 0) { + } +} + +void no_loop_variable() { + while (0) + ; +} + +void volatile_in_condition() { + volatile int cond = 0; + while (!cond) { + } +} + +namespace std { +template<typename T> class atomic { + T val; +public: + atomic(T v): val(v) {}; + operator T() { return val; }; +}; +} + +void atomic_in_condition() { + std::atomic<int> cond = 0; + while (!cond) { + } +} + +void loop_exit1() { + int i = 0; + while (i) { + if (unknown_function()) + break; + } +} + +void loop_exit2() { + int i = 0; + while (i) { + if (unknown_function()) + return; + } +} + +void loop_exit3() { + int i = 0; + while (i) { + if (unknown_function()) + goto end; + } + end: + ; +} + +void loop_exit4() { + int i = 0; + while (i) { + if (unknown_function()) + throw 1; + } +} + +[[noreturn]] void exit(int); + +void loop_exit5() { + int i = 0; + while (i) { + if (unknown_function()) + exit(1); + } +} + +void loop_exit_in_lambda() { + int i = 0; + while (i) { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i) are updated in the loop body [bugprone-infinite-loop] + auto l = []() { return 0; }; + } +} + +void lambda_capture() { + int i = 0; + int Limit = 100; + int *p = &i; + while (i < Limit) { + // Not an error, since i is captured by reference in a lambda. + auto l = [&i]() { ++i; }; + } + + do { + int *p = &i; + (*p)++; + } while (i < Limit); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits