1 //===-- FunctionSizeCheck.cpp - clang-tidy --------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "FunctionSizeCheck.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "llvm/ADT/BitVector.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::readability
{
19 class FunctionASTVisitor
: public RecursiveASTVisitor
<FunctionASTVisitor
> {
20 using Base
= RecursiveASTVisitor
<FunctionASTVisitor
>;
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
)))
31 bool VisitBindingDecl(BindingDecl
*BD
) {
32 // Do count each of the bindings (in the decomposition declaration).
33 if (StructNesting
== 0)
38 bool TraverseStmt(Stmt
*Node
) {
40 return Base::TraverseStmt(Node
);
42 if (TrackedParent
.back() && !isa
<CompoundStmt
>(Node
))
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
:
54 case Stmt::CompoundStmtClass
:
55 TrackedParent
.push_back(true);
58 TrackedParent
.push_back(false);
62 Base::TraverseStmt(Node
);
64 TrackedParent
.pop_back();
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());
76 ++CurrentNestingLevel
;
77 Base::TraverseCompoundStmt(Node
);
78 --CurrentNestingLevel
;
83 bool TraverseDecl(Decl
*Node
) {
84 TrackedParent
.push_back(false);
85 Base::TraverseDecl(Node
);
86 TrackedParent
.pop_back();
90 bool TraverseLambdaExpr(LambdaExpr
*Node
) {
92 Base::TraverseLambdaExpr(Node
);
97 bool TraverseCXXRecordDecl(CXXRecordDecl
*Node
) {
99 Base::TraverseCXXRecordDecl(Node
);
104 bool TraverseStmtExpr(StmtExpr
*SE
) {
106 Base::TraverseStmtExpr(SE
);
111 struct FunctionInfo
{
113 unsigned Statements
= 0;
114 unsigned Branches
= 0;
115 unsigned NestingThreshold
= 0;
116 unsigned Variables
= 0;
117 std::vector
<SourceLocation
> NestingThresholders
;
120 llvm::BitVector TrackedParent
;
121 unsigned StructNesting
= 0;
122 unsigned CurrentNestingLevel
= 0;
127 FunctionSizeCheck::FunctionSizeCheck(StringRef Name
, ClangTidyContext
*Context
)
128 : ClangTidyCheck(Name
, Context
),
129 LineThreshold(Options
.get("LineThreshold", DefaultLineThreshold
)),
131 Options
.get("StatementThreshold", DefaultStatementThreshold
)),
132 BranchThreshold(Options
.get("BranchThreshold", DefaultBranchThreshold
)),
134 Options
.get("ParameterThreshold", DefaultParameterThreshold
)),
136 Options
.get("NestingThreshold", DefaultNestingThreshold
)),
138 Options
.get("VariableThreshold", DefaultVariableThreshold
)) {}
140 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
141 Options
.store(Opts
, "LineThreshold", LineThreshold
);
142 Options
.store(Opts
, "StatementThreshold", StatementThreshold
);
143 Options
.store(Opts
, "BranchThreshold", BranchThreshold
);
144 Options
.store(Opts
, "ParameterThreshold", ParameterThreshold
);
145 Options
.store(Opts
, "NestingThreshold", NestingThreshold
);
146 Options
.store(Opts
, "VariableThreshold", VariableThreshold
);
149 void FunctionSizeCheck::registerMatchers(MatchFinder
*Finder
) {
150 // Lambdas ignored - historically considered part of enclosing function.
151 // FIXME: include them instead? Top-level lambdas are currently never counted.
152 Finder
->addMatcher(functionDecl(unless(isInstantiated()),
153 unless(cxxMethodDecl(ofClass(isLambda()))))
158 void FunctionSizeCheck::check(const MatchFinder::MatchResult
&Result
) {
159 const auto *Func
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("func");
161 FunctionASTVisitor Visitor
;
162 Visitor
.Info
.NestingThreshold
= NestingThreshold
.value_or(-1);
163 Visitor
.TraverseDecl(const_cast<FunctionDecl
*>(Func
));
164 auto &FI
= Visitor
.Info
;
166 if (FI
.Statements
== 0)
169 // Count the lines including whitespace and comments. Really simple.
170 if (const Stmt
*Body
= Func
->getBody()) {
171 SourceManager
*SM
= Result
.SourceManager
;
172 if (SM
->isWrittenInSameFile(Body
->getBeginLoc(), Body
->getEndLoc())) {
173 FI
.Lines
= SM
->getSpellingLineNumber(Body
->getEndLoc()) -
174 SM
->getSpellingLineNumber(Body
->getBeginLoc());
178 unsigned ActualNumberParameters
= Func
->getNumParams();
180 if ((LineThreshold
&& FI
.Lines
> LineThreshold
) ||
181 (StatementThreshold
&& FI
.Statements
> StatementThreshold
) ||
182 (BranchThreshold
&& FI
.Branches
> BranchThreshold
) ||
183 (ParameterThreshold
&& ActualNumberParameters
> ParameterThreshold
) ||
184 !FI
.NestingThresholders
.empty() ||
185 (VariableThreshold
&& FI
.Variables
> VariableThreshold
)) {
186 diag(Func
->getLocation(),
187 "function %0 exceeds recommended size/complexity thresholds")
191 if (LineThreshold
&& FI
.Lines
> LineThreshold
) {
192 diag(Func
->getLocation(),
193 "%0 lines including whitespace and comments (threshold %1)",
195 << FI
.Lines
<< LineThreshold
.value();
198 if (StatementThreshold
&& FI
.Statements
> StatementThreshold
) {
199 diag(Func
->getLocation(), "%0 statements (threshold %1)",
201 << FI
.Statements
<< StatementThreshold
.value();
204 if (BranchThreshold
&& FI
.Branches
> BranchThreshold
) {
205 diag(Func
->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note
)
206 << FI
.Branches
<< BranchThreshold
.value();
209 if (ParameterThreshold
&& ActualNumberParameters
> ParameterThreshold
) {
210 diag(Func
->getLocation(), "%0 parameters (threshold %1)",
212 << ActualNumberParameters
<< ParameterThreshold
.value();
215 for (const auto &CSPos
: FI
.NestingThresholders
) {
216 diag(CSPos
, "nesting level %0 starts here (threshold %1)",
218 << NestingThreshold
.value() + 1 << NestingThreshold
.value();
221 if (VariableThreshold
&& FI
.Variables
> VariableThreshold
) {
222 diag(Func
->getLocation(), "%0 variables (threshold %1)",
224 << FI
.Variables
<< VariableThreshold
.value();
228 } // namespace clang::tidy::readability