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