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
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::readability {
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>(Val: VD) || isa<DecompositionDecl>(Val: 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(S: Node);
41
42 if (TrackedParent.back() && !isa<CompoundStmt>(Val: 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(Val: true);
56 break;
57 default:
58 TrackedParent.push_back(Val: false);
59 break;
60 }
61
62 Base::TraverseStmt(S: 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(x: Node->getBeginLoc());
75
76 ++CurrentNestingLevel;
77 Base::TraverseCompoundStmt(S: Node);
78 --CurrentNestingLevel;
79
80 return true;
81 }
82
83 bool TraverseDecl(Decl *Node) {
84 TrackedParent.push_back(Val: false);
85 Base::TraverseDecl(D: Node);
86 TrackedParent.pop_back();
87 return true;
88 }
89
90 bool TraverseLambdaExpr(LambdaExpr *Node) {
91 ++StructNesting;
92 Base::TraverseLambdaExpr(S: Node);
93 --StructNesting;
94 return true;
95 }
96
97 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
98 ++StructNesting;
99 Base::TraverseCXXRecordDecl(D: Node);
100 --StructNesting;
101 return true;
102 }
103
104 bool TraverseStmtExpr(StmtExpr *SE) {
105 ++StructNesting;
106 Base::TraverseStmtExpr(S: 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
136FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
137 : ClangTidyCheck(Name, Context),
138 LineThreshold(Options.get(LocalName: "LineThreshold", Default: DefaultLineThreshold)),
139 StatementThreshold(
140 Options.get(LocalName: "StatementThreshold", Default: DefaultStatementThreshold)),
141 BranchThreshold(Options.get(LocalName: "BranchThreshold", Default: DefaultBranchThreshold)),
142 ParameterThreshold(
143 Options.get(LocalName: "ParameterThreshold", Default: DefaultParameterThreshold)),
144 NestingThreshold(
145 Options.get(LocalName: "NestingThreshold", Default: DefaultNestingThreshold)),
146 VariableThreshold(
147 Options.get(LocalName: "VariableThreshold", Default: DefaultVariableThreshold)),
148 CountMemberInitAsStmt(
149 Options.get(LocalName: "CountMemberInitAsStmt", Default: DefaultCountMemberInitAsStmt)) {}
150
151void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
152 Options.store(Options&: Opts, LocalName: "LineThreshold", Value: LineThreshold);
153 Options.store(Options&: Opts, LocalName: "StatementThreshold", Value: StatementThreshold);
154 Options.store(Options&: Opts, LocalName: "BranchThreshold", Value: BranchThreshold);
155 Options.store(Options&: Opts, LocalName: "ParameterThreshold", Value: ParameterThreshold);
156 Options.store(Options&: Opts, LocalName: "NestingThreshold", Value: NestingThreshold);
157 Options.store(Options&: Opts, LocalName: "VariableThreshold", Value: VariableThreshold);
158 Options.store(Options&: Opts, LocalName: "CountMemberInitAsStmt", Value: 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(NodeMatch: functionDecl(unless(isInstantiated()),
165 unless(cxxMethodDecl(ofClass(InnerMatcher: isLambda()))))
166 .bind(ID: "func"),
167 Action: this);
168}
169
170void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
171 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>(ID: "func");
172
173 FunctionASTVisitor Visitor;
174 Visitor.Info.NestingThreshold = NestingThreshold.value_or(u: -1);
175 Visitor.CountMemberInitAsStmt = CountMemberInitAsStmt;
176 Visitor.TraverseDecl(Node: 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(Loc1: Body->getBeginLoc(), Loc2: Body->getEndLoc())) {
186 FI.Lines = SM->getSpellingLineNumber(Loc: Body->getEndLoc()) -
187 SM->getSpellingLineNumber(Loc: 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(Loc: Func->getLocation(),
200 Description: "function %0 exceeds recommended size/complexity thresholds")
201 << Func;
202 }
203
204 if (LineThreshold && FI.Lines > LineThreshold) {
205 diag(Loc: Func->getLocation(),
206 Description: "%0 lines including whitespace and comments (threshold %1)",
207 Level: DiagnosticIDs::Note)
208 << FI.Lines << LineThreshold.value();
209 }
210
211 if (StatementThreshold && FI.Statements > StatementThreshold) {
212 diag(Loc: Func->getLocation(), Description: "%0 statements (threshold %1)",
213 Level: DiagnosticIDs::Note)
214 << FI.Statements << StatementThreshold.value();
215 }
216
217 if (BranchThreshold && FI.Branches > BranchThreshold) {
218 diag(Loc: Func->getLocation(), Description: "%0 branches (threshold %1)", Level: DiagnosticIDs::Note)
219 << FI.Branches << BranchThreshold.value();
220 }
221
222 if (ParameterThreshold && ActualNumberParameters > ParameterThreshold) {
223 diag(Loc: Func->getLocation(), Description: "%0 parameters (threshold %1)",
224 Level: DiagnosticIDs::Note)
225 << ActualNumberParameters << ParameterThreshold.value();
226 }
227
228 for (const auto &CSPos : FI.NestingThresholders) {
229 diag(Loc: CSPos, Description: "nesting level %0 starts here (threshold %1)",
230 Level: DiagnosticIDs::Note)
231 << NestingThreshold.value() + 1 << NestingThreshold.value();
232 }
233
234 if (VariableThreshold && FI.Variables > VariableThreshold) {
235 diag(Loc: Func->getLocation(), Description: "%0 variables (threshold %1)",
236 Level: DiagnosticIDs::Note)
237 << FI.Variables << VariableThreshold.value();
238 }
239}
240
241} // namespace clang::tidy::readability
242

source code of clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp