clang-tools 23.0.0git
FunctionSizeCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
14using namespace clang::ast_matchers;
15
17namespace {
18
19class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
20 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
21
22public:
23 explicit FunctionASTVisitor(bool IgnoreMacros) : IgnoreMacros(IgnoreMacros) {}
24
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 shouldCountLocation(VD->getBeginLoc()))
31 ++Info.Variables;
32 return true;
33 }
34 bool VisitBindingDecl(BindingDecl *BD) {
35 // Do count each of the bindings (in the decomposition declaration).
36 if (StructNesting == 0 && shouldCountLocation(BD->getBeginLoc()))
37 ++Info.Variables;
38 return true;
39 }
40
41 bool TraverseStmt(Stmt *Node) {
42 if (!Node)
43 return Base::TraverseStmt(Node);
44
45 if (TrackedParent.back() && !isa<CompoundStmt>(Node) &&
46 shouldCountLocation(Node->getBeginLoc()))
47 ++Info.Statements;
48
49 switch (Node->getStmtClass()) {
50 case Stmt::IfStmtClass:
51 case Stmt::WhileStmtClass:
52 case Stmt::DoStmtClass:
53 case Stmt::CXXForRangeStmtClass:
54 case Stmt::ForStmtClass:
55 case Stmt::SwitchStmtClass:
56 if (shouldCountLocation(Node->getBeginLoc()))
57 ++Info.Branches;
58 [[fallthrough]];
59 case Stmt::CompoundStmtClass:
60 TrackedParent.push_back(true);
61 break;
62 default:
63 TrackedParent.push_back(false);
64 break;
65 }
66
67 Base::TraverseStmt(Node);
68
69 TrackedParent.pop_back();
70
71 return true;
72 }
73
74 bool TraverseCompoundStmt(CompoundStmt *Node) {
75 const bool CountThisCompound = shouldCountLocation(Node->getBeginLoc());
76
77 // If this new compound statement is located in a compound statement, which
78 // is already nested NestingThreshold levels deep, record the start location
79 // of this new compound statement.
80 if (CountThisCompound && (CurrentNestingLevel == Info.NestingThreshold))
81 Info.NestingThresholders.push_back(Node->getBeginLoc());
82
83 if (CountThisCompound)
84 ++CurrentNestingLevel;
85 Base::TraverseCompoundStmt(Node);
86 if (CountThisCompound)
87 --CurrentNestingLevel;
88
89 return true;
90 }
91
92 bool TraverseDecl(Decl *Node) {
93 TrackedParent.push_back(false);
94 Base::TraverseDecl(Node);
95 TrackedParent.pop_back();
96 return true;
97 }
98
99 bool TraverseLambdaExpr(LambdaExpr *Node) {
100 ++StructNesting;
101 Base::TraverseLambdaExpr(Node);
102 --StructNesting;
103 return true;
104 }
105
106 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
107 ++StructNesting;
108 Base::TraverseCXXRecordDecl(Node);
109 --StructNesting;
110 return true;
111 }
112
113 bool TraverseStmtExpr(StmtExpr *SE) {
114 ++StructNesting;
115 Base::TraverseStmtExpr(SE);
116 --StructNesting;
117 return true;
118 }
119
120 bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
121 if (CountMemberInitAsStmt && shouldCountLocation(Init->getSourceLocation()))
122 ++Info.Statements;
123
124 Base::TraverseConstructorInitializer(Init);
125 return true;
126 }
127
128 struct FunctionInfo {
129 unsigned Lines = 0;
130 unsigned Statements = 0;
131 unsigned Branches = 0;
132 unsigned NestingThreshold = 0;
133 unsigned Variables = 0;
134 std::vector<SourceLocation> NestingThresholders;
135 };
136 FunctionInfo Info;
137 llvm::BitVector TrackedParent;
138 unsigned StructNesting = 0;
139 unsigned CurrentNestingLevel = 0;
140 bool CountMemberInitAsStmt;
141 const bool IgnoreMacros;
142
143private:
144 bool shouldCountLocation(SourceLocation Loc) const {
145 return !IgnoreMacros || !Loc.isMacroID();
146 }
147};
148
149} // namespace
150
152 : ClangTidyCheck(Name, Context),
153 LineThreshold(Options.get("LineThreshold", DefaultLineThreshold)),
154 StatementThreshold(
155 Options.get("StatementThreshold", DefaultStatementThreshold)),
156 BranchThreshold(Options.get("BranchThreshold", DefaultBranchThreshold)),
157 ParameterThreshold(
158 Options.get("ParameterThreshold", DefaultParameterThreshold)),
159 NestingThreshold(
160 Options.get("NestingThreshold", DefaultNestingThreshold)),
161 VariableThreshold(
162 Options.get("VariableThreshold", DefaultVariableThreshold)),
163 CountMemberInitAsStmt(
164 Options.get("CountMemberInitAsStmt", DefaultCountMemberInitAsStmt)),
165 IgnoreMacros(Options.get("IgnoreMacros", DefaultIgnoreMacros)) {}
166
168 Options.store(Opts, "LineThreshold", LineThreshold);
169 Options.store(Opts, "StatementThreshold", StatementThreshold);
170 Options.store(Opts, "BranchThreshold", BranchThreshold);
171 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
172 Options.store(Opts, "NestingThreshold", NestingThreshold);
173 Options.store(Opts, "VariableThreshold", VariableThreshold);
174 Options.store(Opts, "CountMemberInitAsStmt", CountMemberInitAsStmt);
175 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
176}
177
178void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
179 // Lambdas ignored - historically considered part of enclosing function.
180 // FIXME: include them instead? Top-level lambdas are currently never counted.
181 Finder->addMatcher(functionDecl(unless(isInstantiated()),
182 unless(cxxMethodDecl(ofClass(isLambda()))))
183 .bind("func"),
184 this);
185}
186
187void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
188 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
189
190 FunctionASTVisitor Visitor(IgnoreMacros);
191 Visitor.Info.NestingThreshold = NestingThreshold.value_or(-1);
192 Visitor.CountMemberInitAsStmt = CountMemberInitAsStmt;
193 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
194 auto &FI = Visitor.Info;
195
196 if (FI.Statements == 0)
197 return;
198
199 // Count the lines including whitespace and comments. Really simple.
200 if (const Stmt *Body = Func->getBody()) {
201 const SourceManager *SM = Result.SourceManager;
202 if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
203 FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
204 SM->getSpellingLineNumber(Body->getBeginLoc());
205 }
206 }
207
208 const unsigned ActualNumberParameters = Func->getNumParams();
209
210 if ((LineThreshold && FI.Lines > LineThreshold) ||
211 (StatementThreshold && FI.Statements > StatementThreshold) ||
212 (BranchThreshold && FI.Branches > BranchThreshold) ||
213 (ParameterThreshold && ActualNumberParameters > ParameterThreshold) ||
214 !FI.NestingThresholders.empty() ||
215 (VariableThreshold && FI.Variables > VariableThreshold)) {
216 diag(Func->getLocation(),
217 "function %0 exceeds recommended size/complexity thresholds")
218 << Func;
219 }
220
221 if (LineThreshold && FI.Lines > LineThreshold) {
222 diag(Func->getLocation(),
223 "%0 lines including whitespace and comments (threshold %1)",
224 DiagnosticIDs::Note)
225 << FI.Lines << LineThreshold.value();
226 }
227
228 if (StatementThreshold && FI.Statements > StatementThreshold) {
229 diag(Func->getLocation(), "%0 statements (threshold %1)",
230 DiagnosticIDs::Note)
231 << FI.Statements << StatementThreshold.value();
232 }
233
234 if (BranchThreshold && FI.Branches > BranchThreshold) {
235 diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
236 << FI.Branches << BranchThreshold.value();
237 }
238
239 if (ParameterThreshold && ActualNumberParameters > ParameterThreshold) {
240 diag(Func->getLocation(), "%0 parameters (threshold %1)",
241 DiagnosticIDs::Note)
242 << ActualNumberParameters << ParameterThreshold.value();
243 }
244
245 if (NestingThreshold) {
246 for (const auto &CSPos : FI.NestingThresholders) {
247 diag(CSPos, "nesting level %0 starts here (threshold %1)",
248 DiagnosticIDs::Note)
249 << *NestingThreshold + 1 << *NestingThreshold;
250 }
251 }
252
253 if (VariableThreshold && FI.Variables > VariableThreshold) {
254 diag(Func->getLocation(), "%0 variables (threshold %1)",
255 DiagnosticIDs::Note)
256 << FI.Variables << VariableThreshold.value();
257 }
258}
259
260} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
@ Info
An information message.
Definition Protocol.h:755
llvm::StringMap< ClangTidyValue > OptionMap