clang-tools  16.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 #include "llvm/ADT/BitVector.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  [[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->getBeginLoc());
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  llvm::BitVector 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  // Lambdas ignored - historically considered part of enclosing function.
149  // FIXME: include them instead? Top-level lambdas are currently never counted.
150  Finder->addMatcher(functionDecl(unless(isInstantiated()),
151  unless(cxxMethodDecl(ofClass(isLambda()))))
152  .bind("func"),
153  this);
154 }
155 
156 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
157  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
158 
159  FunctionASTVisitor Visitor;
160  Visitor.Info.NestingThreshold = NestingThreshold;
161  Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
162  auto &FI = Visitor.Info;
163 
164  if (FI.Statements == 0)
165  return;
166 
167  // Count the lines including whitespace and comments. Really simple.
168  if (const Stmt *Body = Func->getBody()) {
169  SourceManager *SM = Result.SourceManager;
170  if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
171  FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
172  SM->getSpellingLineNumber(Body->getBeginLoc());
173  }
174  }
175 
176  unsigned ActualNumberParameters = Func->getNumParams();
177 
178  if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
179  FI.Branches > BranchThreshold ||
180  ActualNumberParameters > ParameterThreshold ||
181  !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
182  diag(Func->getLocation(),
183  "function %0 exceeds recommended size/complexity thresholds")
184  << Func;
185  }
186 
187  if (FI.Lines > LineThreshold) {
188  diag(Func->getLocation(),
189  "%0 lines including whitespace and comments (threshold %1)",
190  DiagnosticIDs::Note)
191  << FI.Lines << LineThreshold;
192  }
193 
194  if (FI.Statements > StatementThreshold) {
195  diag(Func->getLocation(), "%0 statements (threshold %1)",
196  DiagnosticIDs::Note)
197  << FI.Statements << StatementThreshold;
198  }
199 
200  if (FI.Branches > BranchThreshold) {
201  diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
202  << FI.Branches << BranchThreshold;
203  }
204 
205  if (ActualNumberParameters > ParameterThreshold) {
206  diag(Func->getLocation(), "%0 parameters (threshold %1)",
207  DiagnosticIDs::Note)
208  << ActualNumberParameters << ParameterThreshold;
209  }
210 
211  for (const auto &CSPos : FI.NestingThresholders) {
212  diag(CSPos, "nesting level %0 starts here (threshold %1)",
213  DiagnosticIDs::Note)
214  << NestingThreshold + 1 << NestingThreshold;
215  }
216 
217  if (FI.Variables > VariableThreshold) {
218  diag(Func->getLocation(), "%0 variables (threshold %1)",
219  DiagnosticIDs::Note)
220  << FI.Variables << VariableThreshold;
221  }
222 }
223 
224 } // namespace readability
225 } // namespace tidy
226 } // namespace clang
Base
std::unique_ptr< GlobalCompilationDatabase > Base
Definition: GlobalCompilationDatabaseTests.cpp:85
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
NestingThreshold
unsigned NestingThreshold
Definition: FunctionSizeCheck.cpp:117
Branches
unsigned Branches
Definition: FunctionSizeCheck.cpp:116
clang::tidy::readability::FunctionSizeCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: FunctionSizeCheck.cpp:138
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:53
Variables
unsigned Variables
Definition: FunctionSizeCheck.cpp:118
NestingThresholders
std::vector< SourceLocation > NestingThresholders
Definition: FunctionSizeCheck.cpp:119
clang::ast_matchers
Definition: AbseilMatcher.h:14
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:415
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:67
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
TrackedParent
llvm::BitVector TrackedParent
Definition: FunctionSizeCheck.cpp:122
Name
Token Name
Definition: MacroToEnumCheck.cpp:89
clang::tidy::readability::FunctionSizeCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: FunctionSizeCheck.cpp:156
StructNesting
unsigned StructNesting
Definition: FunctionSizeCheck.cpp:123
Info
FunctionInfo Info
Definition: FunctionSizeCheck.cpp:121
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::ClangTidyCheck::OptionsView::store
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: ClangTidyCheck.cpp:129
clang::tidy::readability::FunctionSizeCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: FunctionSizeCheck.cpp:147
CurrentNestingLevel
unsigned CurrentNestingLevel
Definition: FunctionSizeCheck.cpp:124
Lines
unsigned Lines
Definition: FunctionSizeCheck.cpp:114
Statements
unsigned Statements
Definition: FunctionSizeCheck.cpp:115
FunctionSizeCheck.h