clang-tools  12.0.0git
FunctionSizeCheck.cpp
Go to the documentation of this file.
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 
9 #include "FunctionSizeCheck.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace readability {
18 namespace {
19 
20 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
21  using Base = RecursiveASTVisitor<FunctionASTVisitor>;
22 
23 public:
24  bool VisitVarDecl(VarDecl *VD) {
25  // Do not count function params.
26  // Do not count decomposition declarations (C++17's structured bindings).
27  if (StructNesting == 0 &&
28  !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD)))
29  ++Info.Variables;
30  return true;
31  }
32  bool VisitBindingDecl(BindingDecl *BD) {
33  // Do count each of the bindings (in the decomposition declaration).
34  if (StructNesting == 0)
35  ++Info.Variables;
36  return true;
37  }
38 
39  bool TraverseStmt(Stmt *Node) {
40  if (!Node)
41  return Base::TraverseStmt(Node);
42 
43  if (TrackedParent.back() && !isa<CompoundStmt>(Node))
44  ++Info.Statements;
45 
46  switch (Node->getStmtClass()) {
47  case Stmt::IfStmtClass:
48  case Stmt::WhileStmtClass:
49  case Stmt::DoStmtClass:
50  case Stmt::CXXForRangeStmtClass:
51  case Stmt::ForStmtClass:
52  case Stmt::SwitchStmtClass:
53  ++Info.Branches;
54  LLVM_FALLTHROUGH;
55  case Stmt::CompoundStmtClass:
56  TrackedParent.push_back(true);
57  break;
58  default:
59  TrackedParent.push_back(false);
60  break;
61  }
62 
63  Base::TraverseStmt(Node);
64 
65  TrackedParent.pop_back();
66 
67  return true;
68  }
69 
70  bool TraverseCompoundStmt(CompoundStmt *Node) {
71  // If this new compound statement is located in a compound statement, which
72  // is already nested NestingThreshold levels deep, record the start location
73  // of this new compound statement.
74  if (CurrentNestingLevel == Info.NestingThreshold)
75  Info.NestingThresholders.push_back(Node->getBeginLoc());
76 
78  Base::TraverseCompoundStmt(Node);
80 
81  return true;
82  }
83 
84  bool TraverseDecl(Decl *Node) {
85  TrackedParent.push_back(false);
86  Base::TraverseDecl(Node);
87  TrackedParent.pop_back();
88  return true;
89  }
90 
91  bool TraverseLambdaExpr(LambdaExpr *Node) {
92  ++StructNesting;
93  Base::TraverseLambdaExpr(Node);
94  --StructNesting;
95  return true;
96  }
97 
98  bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
99  ++StructNesting;
100  Base::TraverseCXXRecordDecl(Node);
101  --StructNesting;
102  return true;
103  }
104 
105  bool TraverseStmtExpr(StmtExpr *SE) {
106  ++StructNesting;
107  Base::TraverseStmtExpr(SE);
108  --StructNesting;
109  return true;
110  }
111 
112  struct FunctionInfo {
113  unsigned Lines = 0;
114  unsigned Statements = 0;
115  unsigned Branches = 0;
116  unsigned NestingThreshold = 0;
117  unsigned Variables = 0;
118  std::vector<SourceLocation> NestingThresholders;
119  };
120  FunctionInfo Info;
121  std::vector<bool> TrackedParent;
122  unsigned StructNesting = 0;
123  unsigned CurrentNestingLevel = 0;
124 };
125 
126 } // namespace
127 
128 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
129  : ClangTidyCheck(Name, Context),
130  LineThreshold(Options.get("LineThreshold", -1U)),
131  StatementThreshold(Options.get("StatementThreshold", 800U)),
132  BranchThreshold(Options.get("BranchThreshold", -1U)),
133  ParameterThreshold(Options.get("ParameterThreshold", -1U)),
134  NestingThreshold(Options.get("NestingThreshold", -1U)),
135  VariableThreshold(Options.get("VariableThreshold", -1U)) {}
136 
138  Options.store(Opts, "LineThreshold", LineThreshold);
139  Options.store(Opts, "StatementThreshold", StatementThreshold);
140  Options.store(Opts, "BranchThreshold", BranchThreshold);
141  Options.store(Opts, "ParameterThreshold", ParameterThreshold);
142  Options.store(Opts, "NestingThreshold", NestingThreshold);
143  Options.store(Opts, "VariableThreshold", VariableThreshold);
144 }
145 
146 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
147  // Lambdas ignored - historically considered part of enclosing function.
148  // FIXME: include them instead? Top-level lambdas are currently never counted.
149  Finder->addMatcher(functionDecl(unless(isInstantiated()),
150  unless(cxxMethodDecl(ofClass(isLambda()))))
151  .bind("func"),
152  this);
153 }
154 
155 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
156  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
157 
158  FunctionASTVisitor Visitor;
159  Visitor.Info.NestingThreshold = NestingThreshold;
160  Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
161  auto &FI = Visitor.Info;
162 
163  if (FI.Statements == 0)
164  return;
165 
166  // Count the lines including whitespace and comments. Really simple.
167  if (const Stmt *Body = Func->getBody()) {
168  SourceManager *SM = Result.SourceManager;
169  if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
170  FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
171  SM->getSpellingLineNumber(Body->getBeginLoc());
172  }
173  }
174 
175  unsigned ActualNumberParameters = Func->getNumParams();
176 
177  if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
178  FI.Branches > BranchThreshold ||
179  ActualNumberParameters > ParameterThreshold ||
180  !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
181  diag(Func->getLocation(),
182  "function %0 exceeds recommended size/complexity thresholds")
183  << Func;
184  }
185 
186  if (FI.Lines > LineThreshold) {
187  diag(Func->getLocation(),
188  "%0 lines including whitespace and comments (threshold %1)",
189  DiagnosticIDs::Note)
190  << FI.Lines << LineThreshold;
191  }
192 
193  if (FI.Statements > StatementThreshold) {
194  diag(Func->getLocation(), "%0 statements (threshold %1)",
195  DiagnosticIDs::Note)
196  << FI.Statements << StatementThreshold;
197  }
198 
199  if (FI.Branches > BranchThreshold) {
200  diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
201  << FI.Branches << BranchThreshold;
202  }
203 
204  if (ActualNumberParameters > ParameterThreshold) {
205  diag(Func->getLocation(), "%0 parameters (threshold %1)",
206  DiagnosticIDs::Note)
207  << ActualNumberParameters << ParameterThreshold;
208  }
209 
210  for (const auto &CSPos : FI.NestingThresholders) {
211  diag(CSPos, "nesting level %0 starts here (threshold %1)",
212  DiagnosticIDs::Note)
213  << NestingThreshold + 1 << NestingThreshold;
214  }
215 
216  if (FI.Variables > VariableThreshold) {
217  diag(Func->getLocation(), "%0 variables (threshold %1)",
218  DiagnosticIDs::Note)
219  << FI.Variables << VariableThreshold;
220  }
221 }
222 
223 } // namespace readability
224 } // namespace tidy
225 } // namespace clang
const FunctionDecl * Decl
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Base class for all clang-tidy checks.
std::vector< bool > TrackedParent
std::vector< SourceLocation > NestingThresholders
unsigned Branches
unsigned Variables
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
unsigned StructNesting
static constexpr llvm::StringLiteral Name
FunctionInfo Info
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
unsigned CurrentNestingLevel
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
unsigned Statements
std::unique_ptr< GlobalCompilationDatabase > Base
unsigned NestingThreshold
std::map< std::string, ClangTidyValue > OptionMap
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
unsigned Lines