FunctionSizeCheck.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. //===-- FunctionSizeCheck.cpp - clang-tidy --------------------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "FunctionSizeCheck.h"
  9. #include "clang/AST/RecursiveASTVisitor.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "llvm/ADT/BitVector.h"
  12. using namespace clang::ast_matchers;
  13. namespace clang::tidy::readability {
  14. namespace {
  15. class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
  16. using Base = RecursiveASTVisitor<FunctionASTVisitor>;
  17. public:
  18. bool VisitVarDecl(VarDecl *VD) {
  19. // Do not count function params.
  20. // Do not count decomposition declarations (C++17's structured bindings).
  21. if (StructNesting == 0 &&
  22. !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD)))
  23. ++Info.Variables;
  24. return true;
  25. }
  26. bool VisitBindingDecl(BindingDecl *BD) {
  27. // Do count each of the bindings (in the decomposition declaration).
  28. if (StructNesting == 0)
  29. ++Info.Variables;
  30. return true;
  31. }
  32. bool TraverseStmt(Stmt *Node) {
  33. if (!Node)
  34. return Base::TraverseStmt(Node);
  35. if (TrackedParent.back() && !isa<CompoundStmt>(Node))
  36. ++Info.Statements;
  37. switch (Node->getStmtClass()) {
  38. case Stmt::IfStmtClass:
  39. case Stmt::WhileStmtClass:
  40. case Stmt::DoStmtClass:
  41. case Stmt::CXXForRangeStmtClass:
  42. case Stmt::ForStmtClass:
  43. case Stmt::SwitchStmtClass:
  44. ++Info.Branches;
  45. [[fallthrough]];
  46. case Stmt::CompoundStmtClass:
  47. TrackedParent.push_back(true);
  48. break;
  49. default:
  50. TrackedParent.push_back(false);
  51. break;
  52. }
  53. Base::TraverseStmt(Node);
  54. TrackedParent.pop_back();
  55. return true;
  56. }
  57. bool TraverseCompoundStmt(CompoundStmt *Node) {
  58. // If this new compound statement is located in a compound statement, which
  59. // is already nested NestingThreshold levels deep, record the start location
  60. // of this new compound statement.
  61. if (CurrentNestingLevel == Info.NestingThreshold)
  62. Info.NestingThresholders.push_back(Node->getBeginLoc());
  63. ++CurrentNestingLevel;
  64. Base::TraverseCompoundStmt(Node);
  65. --CurrentNestingLevel;
  66. return true;
  67. }
  68. bool TraverseDecl(Decl *Node) {
  69. TrackedParent.push_back(false);
  70. Base::TraverseDecl(Node);
  71. TrackedParent.pop_back();
  72. return true;
  73. }
  74. bool TraverseLambdaExpr(LambdaExpr *Node) {
  75. ++StructNesting;
  76. Base::TraverseLambdaExpr(Node);
  77. --StructNesting;
  78. return true;
  79. }
  80. bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
  81. ++StructNesting;
  82. Base::TraverseCXXRecordDecl(Node);
  83. --StructNesting;
  84. return true;
  85. }
  86. bool TraverseStmtExpr(StmtExpr *SE) {
  87. ++StructNesting;
  88. Base::TraverseStmtExpr(SE);
  89. --StructNesting;
  90. return true;
  91. }
  92. struct FunctionInfo {
  93. unsigned Lines = 0;
  94. unsigned Statements = 0;
  95. unsigned Branches = 0;
  96. unsigned NestingThreshold = 0;
  97. unsigned Variables = 0;
  98. std::vector<SourceLocation> NestingThresholders;
  99. };
  100. FunctionInfo Info;
  101. llvm::BitVector TrackedParent;
  102. unsigned StructNesting = 0;
  103. unsigned CurrentNestingLevel = 0;
  104. };
  105. } // namespace
  106. FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
  107. : ClangTidyCheck(Name, Context),
  108. LineThreshold(Options.get("LineThreshold", -1U)),
  109. StatementThreshold(Options.get("StatementThreshold", 800U)),
  110. BranchThreshold(Options.get("BranchThreshold", -1U)),
  111. ParameterThreshold(Options.get("ParameterThreshold", -1U)),
  112. NestingThreshold(Options.get("NestingThreshold", -1U)),
  113. VariableThreshold(Options.get("VariableThreshold", -1U)) {}
  114. void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  115. Options.store(Opts, "LineThreshold", LineThreshold);
  116. Options.store(Opts, "StatementThreshold", StatementThreshold);
  117. Options.store(Opts, "BranchThreshold", BranchThreshold);
  118. Options.store(Opts, "ParameterThreshold", ParameterThreshold);
  119. Options.store(Opts, "NestingThreshold", NestingThreshold);
  120. Options.store(Opts, "VariableThreshold", VariableThreshold);
  121. }
  122. void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
  123. // Lambdas ignored - historically considered part of enclosing function.
  124. // FIXME: include them instead? Top-level lambdas are currently never counted.
  125. Finder->addMatcher(functionDecl(unless(isInstantiated()),
  126. unless(cxxMethodDecl(ofClass(isLambda()))))
  127. .bind("func"),
  128. this);
  129. }
  130. void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
  131. const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
  132. FunctionASTVisitor Visitor;
  133. Visitor.Info.NestingThreshold = NestingThreshold;
  134. Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
  135. auto &FI = Visitor.Info;
  136. if (FI.Statements == 0)
  137. return;
  138. // Count the lines including whitespace and comments. Really simple.
  139. if (const Stmt *Body = Func->getBody()) {
  140. SourceManager *SM = Result.SourceManager;
  141. if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
  142. FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
  143. SM->getSpellingLineNumber(Body->getBeginLoc());
  144. }
  145. }
  146. unsigned ActualNumberParameters = Func->getNumParams();
  147. if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
  148. FI.Branches > BranchThreshold ||
  149. ActualNumberParameters > ParameterThreshold ||
  150. !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
  151. diag(Func->getLocation(),
  152. "function %0 exceeds recommended size/complexity thresholds")
  153. << Func;
  154. }
  155. if (FI.Lines > LineThreshold) {
  156. diag(Func->getLocation(),
  157. "%0 lines including whitespace and comments (threshold %1)",
  158. DiagnosticIDs::Note)
  159. << FI.Lines << LineThreshold;
  160. }
  161. if (FI.Statements > StatementThreshold) {
  162. diag(Func->getLocation(), "%0 statements (threshold %1)",
  163. DiagnosticIDs::Note)
  164. << FI.Statements << StatementThreshold;
  165. }
  166. if (FI.Branches > BranchThreshold) {
  167. diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
  168. << FI.Branches << BranchThreshold;
  169. }
  170. if (ActualNumberParameters > ParameterThreshold) {
  171. diag(Func->getLocation(), "%0 parameters (threshold %1)",
  172. DiagnosticIDs::Note)
  173. << ActualNumberParameters << ParameterThreshold;
  174. }
  175. for (const auto &CSPos : FI.NestingThresholders) {
  176. diag(CSPos, "nesting level %0 starts here (threshold %1)",
  177. DiagnosticIDs::Note)
  178. << NestingThreshold + 1 << NestingThreshold;
  179. }
  180. if (FI.Variables > VariableThreshold) {
  181. diag(Func->getLocation(), "%0 variables (threshold %1)",
  182. DiagnosticIDs::Note)
  183. << FI.Variables << VariableThreshold;
  184. }
  185. }
  186. } // namespace clang::tidy::readability