clang-tools 8.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->getBeginLoc());
77
78 ++CurrentNestingLevel;
79 Base::TraverseCompoundStmt(Node);
80 --CurrentNestingLevel;
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
138 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
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
clang::ast_matchers
Definition: AbseilMatcher.h:15
clang::tidy::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: ClangTidy.cpp:473
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: ClangTidy.h:127
TrackedParent
std::vector< bool > TrackedParent
Definition: FunctionSizeCheck.cpp:122
NestingThresholders
std::vector< SourceLocation > NestingThresholders
Definition: FunctionSizeCheck.cpp:119
Branches
unsigned Branches
Definition: FunctionSizeCheck.cpp:116
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidy.h:183
Variables
unsigned Variables
Definition: FunctionSizeCheck.cpp:118
StructNesting
unsigned StructNesting
Definition: FunctionSizeCheck.cpp:123
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:98
Result
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: ClangdServer.cpp:60
Info
FunctionInfo Info
Definition: FunctionSizeCheck.cpp:121
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ChangeNamespace.cpp:18
CurrentNestingLevel
unsigned CurrentNestingLevel
Definition: FunctionSizeCheck.cpp:124
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:100
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
Statements
unsigned Statements
Definition: FunctionSizeCheck.cpp:115
FunctionSizeCheck.h
NestingThreshold
unsigned NestingThreshold
Definition: FunctionSizeCheck.cpp:117
clang::tidy::readability::FunctionSizeCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: FunctionSizeCheck.cpp:147
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:438
Lines
unsigned Lines
Definition: FunctionSizeCheck.cpp:114