clang-tools  7.0.0
FunctionSizeCheck.cpp
Go to the documentation of this file.
1 //===--- FunctionSize.cpp - clang-tidy ------------------------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "FunctionSizeCheck.h"
11 #include "clang/AST/RecursiveASTVisitor.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace readability {
19 namespace {
20 
21 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
22  using Base = RecursiveASTVisitor<FunctionASTVisitor>;
23 
24 public:
25  bool VisitVarDecl(VarDecl *VD) {
26  // Do not count function params.
27  // Do not count decomposition declarations (C++17's structured bindings).
28  if (StructNesting == 0 &&
29  !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD)))
30  ++Info.Variables;
31  return true;
32  }
33  bool VisitBindingDecl(BindingDecl *BD) {
34  // Do count each of the bindings (in the decomposition declaration).
35  if (StructNesting == 0)
36  ++Info.Variables;
37  return true;
38  }
39 
40  bool TraverseStmt(Stmt *Node) {
41  if (!Node)
42  return Base::TraverseStmt(Node);
43 
44  if (TrackedParent.back() && !isa<CompoundStmt>(Node))
45  ++Info.Statements;
46 
47  switch (Node->getStmtClass()) {
48  case Stmt::IfStmtClass:
49  case Stmt::WhileStmtClass:
50  case Stmt::DoStmtClass:
51  case Stmt::CXXForRangeStmtClass:
52  case Stmt::ForStmtClass:
53  case Stmt::SwitchStmtClass:
54  ++Info.Branches;
55  LLVM_FALLTHROUGH;
56  case Stmt::CompoundStmtClass:
57  TrackedParent.push_back(true);
58  break;
59  default:
60  TrackedParent.push_back(false);
61  break;
62  }
63 
64  Base::TraverseStmt(Node);
65 
66  TrackedParent.pop_back();
67 
68  return true;
69  }
70 
71  bool TraverseCompoundStmt(CompoundStmt *Node) {
72  // If this new compound statement is located in a compound statement, which
73  // is already nested NestingThreshold levels deep, record the start location
74  // of this new compound statement.
75  if (CurrentNestingLevel == Info.NestingThreshold)
76  Info.NestingThresholders.push_back(Node->getLocStart());
77 
79  Base::TraverseCompoundStmt(Node);
81 
82  return true;
83  }
84 
85  bool TraverseDecl(Decl *Node) {
86  TrackedParent.push_back(false);
87  Base::TraverseDecl(Node);
88  TrackedParent.pop_back();
89  return true;
90  }
91 
92  bool TraverseLambdaExpr(LambdaExpr *Node) {
93  ++StructNesting;
94  Base::TraverseLambdaExpr(Node);
95  --StructNesting;
96  return true;
97  }
98 
99  bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
100  ++StructNesting;
101  Base::TraverseCXXRecordDecl(Node);
102  --StructNesting;
103  return true;
104  }
105 
106  bool TraverseStmtExpr(StmtExpr *SE) {
107  ++StructNesting;
108  Base::TraverseStmtExpr(SE);
109  --StructNesting;
110  return true;
111  }
112 
113  struct FunctionInfo {
114  unsigned Lines = 0;
115  unsigned Statements = 0;
116  unsigned Branches = 0;
117  unsigned NestingThreshold = 0;
118  unsigned Variables = 0;
119  std::vector<SourceLocation> NestingThresholders;
120  };
121  FunctionInfo Info;
122  std::vector<bool> TrackedParent;
123  unsigned StructNesting = 0;
124  unsigned CurrentNestingLevel = 0;
125 };
126 
127 } // namespace
128 
129 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
130  : ClangTidyCheck(Name, Context),
131  LineThreshold(Options.get("LineThreshold", -1U)),
132  StatementThreshold(Options.get("StatementThreshold", 800U)),
133  BranchThreshold(Options.get("BranchThreshold", -1U)),
134  ParameterThreshold(Options.get("ParameterThreshold", -1U)),
135  NestingThreshold(Options.get("NestingThreshold", -1U)),
136  VariableThreshold(Options.get("VariableThreshold", -1U)) {}
137 
139  Options.store(Opts, "LineThreshold", LineThreshold);
140  Options.store(Opts, "StatementThreshold", StatementThreshold);
141  Options.store(Opts, "BranchThreshold", BranchThreshold);
142  Options.store(Opts, "ParameterThreshold", ParameterThreshold);
143  Options.store(Opts, "NestingThreshold", NestingThreshold);
144  Options.store(Opts, "VariableThreshold", VariableThreshold);
145 }
146 
147 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
148  Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this);
149 }
150 
151 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
152  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
153 
154  FunctionASTVisitor Visitor;
155  Visitor.Info.NestingThreshold = NestingThreshold;
156  Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
157  auto &FI = Visitor.Info;
158 
159  if (FI.Statements == 0)
160  return;
161 
162  // Count the lines including whitespace and comments. Really simple.
163  if (const Stmt *Body = Func->getBody()) {
164  SourceManager *SM = Result.SourceManager;
165  if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
166  FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) -
167  SM->getSpellingLineNumber(Body->getLocStart());
168  }
169  }
170 
171  unsigned ActualNumberParameters = Func->getNumParams();
172 
173  if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
174  FI.Branches > BranchThreshold ||
175  ActualNumberParameters > ParameterThreshold ||
176  !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
177  diag(Func->getLocation(),
178  "function %0 exceeds recommended size/complexity thresholds")
179  << Func;
180  }
181 
182  if (FI.Lines > LineThreshold) {
183  diag(Func->getLocation(),
184  "%0 lines including whitespace and comments (threshold %1)",
185  DiagnosticIDs::Note)
186  << FI.Lines << LineThreshold;
187  }
188 
189  if (FI.Statements > StatementThreshold) {
190  diag(Func->getLocation(), "%0 statements (threshold %1)",
191  DiagnosticIDs::Note)
192  << FI.Statements << StatementThreshold;
193  }
194 
195  if (FI.Branches > BranchThreshold) {
196  diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
197  << FI.Branches << BranchThreshold;
198  }
199 
200  if (ActualNumberParameters > ParameterThreshold) {
201  diag(Func->getLocation(), "%0 parameters (threshold %1)",
202  DiagnosticIDs::Note)
203  << ActualNumberParameters << ParameterThreshold;
204  }
205 
206  for (const auto &CSPos : FI.NestingThresholders) {
207  diag(CSPos, "nesting level %0 starts here (threshold %1)",
208  DiagnosticIDs::Note)
209  << NestingThreshold + 1 << NestingThreshold;
210  }
211 
212  if (FI.Variables > VariableThreshold) {
213  diag(Func->getLocation(), "%0 variables (threshold %1)",
214  DiagnosticIDs::Note)
215  << FI.Variables << VariableThreshold;
216  }
217 }
218 
219 } // namespace readability
220 } // namespace tidy
221 } // namespace clang
llvm::StringRef Name
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.
Definition: ClangTidy.cpp:460
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.
Definition: ClangTidy.h:127
std::vector< bool > TrackedParent
std::vector< SourceLocation > NestingThresholders
unsigned Branches
unsigned Variables
unsigned StructNesting
std::map< std::string, std::string > OptionMap
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
unsigned NestingThreshold
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.
Definition: ClangTidy.cpp:427
unsigned Lines