clang-tools 18.0.0git
FunctionSizeCheck.cpp
Go to the documentation of this file.
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
17namespace {
18
19class FunctionASTVisitor : public 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) {
92 Base::TraverseLambdaExpr(Node);
94 return true;
95 }
96
97 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
99 Base::TraverseCXXRecordDecl(Node);
101 return true;
102 }
103
104 bool TraverseStmtExpr(StmtExpr *SE) {
106 Base::TraverseStmtExpr(SE);
108 return true;
109 }
110
111 struct FunctionInfo {
112 unsigned Lines = 0;
113 unsigned Statements = 0;
114 unsigned Branches = 0;
115 unsigned NestingThreshold = 0;
116 unsigned Variables = 0;
117 std::vector<SourceLocation> NestingThresholders;
118 };
119 FunctionInfo Info;
120 llvm::BitVector TrackedParent;
121 unsigned StructNesting = 0;
122 unsigned CurrentNestingLevel = 0;
123};
124
125} // namespace
126
128 : ClangTidyCheck(Name, Context),
129 LineThreshold(Options.get("LineThreshold", -1U)),
130 StatementThreshold(Options.get("StatementThreshold", 800U)),
131 BranchThreshold(Options.get("BranchThreshold", -1U)),
132 ParameterThreshold(Options.get("ParameterThreshold", -1U)),
133 NestingThreshold(Options.get("NestingThreshold", -1U)),
134 VariableThreshold(Options.get("VariableThreshold", -1U)) {}
135
137 Options.store(Opts, "LineThreshold", LineThreshold);
138 Options.store(Opts, "StatementThreshold", StatementThreshold);
139 Options.store(Opts, "BranchThreshold", BranchThreshold);
140 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
141 Options.store(Opts, "NestingThreshold", NestingThreshold);
142 Options.store(Opts, "VariableThreshold", VariableThreshold);
143}
144
145void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
146 // Lambdas ignored - historically considered part of enclosing function.
147 // FIXME: include them instead? Top-level lambdas are currently never counted.
148 Finder->addMatcher(functionDecl(unless(isInstantiated()),
149 unless(cxxMethodDecl(ofClass(isLambda()))))
150 .bind("func"),
151 this);
152}
153
154void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
155 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
156
157 FunctionASTVisitor Visitor;
158 Visitor.Info.NestingThreshold = NestingThreshold;
159 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
160 auto &FI = Visitor.Info;
161
162 if (FI.Statements == 0)
163 return;
164
165 // Count the lines including whitespace and comments. Really simple.
166 if (const Stmt *Body = Func->getBody()) {
167 SourceManager *SM = Result.SourceManager;
168 if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
169 FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
170 SM->getSpellingLineNumber(Body->getBeginLoc());
171 }
172 }
173
174 unsigned ActualNumberParameters = Func->getNumParams();
175
176 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
177 FI.Branches > BranchThreshold ||
178 ActualNumberParameters > ParameterThreshold ||
179 !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
180 diag(Func->getLocation(),
181 "function %0 exceeds recommended size/complexity thresholds")
182 << Func;
183 }
184
185 if (FI.Lines > LineThreshold) {
186 diag(Func->getLocation(),
187 "%0 lines including whitespace and comments (threshold %1)",
188 DiagnosticIDs::Note)
189 << FI.Lines << LineThreshold;
190 }
191
192 if (FI.Statements > StatementThreshold) {
193 diag(Func->getLocation(), "%0 statements (threshold %1)",
194 DiagnosticIDs::Note)
195 << FI.Statements << StatementThreshold;
196 }
197
198 if (FI.Branches > BranchThreshold) {
199 diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
200 << FI.Branches << BranchThreshold;
201 }
202
203 if (ActualNumberParameters > ParameterThreshold) {
204 diag(Func->getLocation(), "%0 parameters (threshold %1)",
205 DiagnosticIDs::Note)
206 << ActualNumberParameters << ParameterThreshold;
207 }
208
209 for (const auto &CSPos : FI.NestingThresholders) {
210 diag(CSPos, "nesting level %0 starts here (threshold %1)",
211 DiagnosticIDs::Note)
212 << NestingThreshold + 1 << NestingThreshold;
213 }
214
215 if (FI.Variables > VariableThreshold) {
216 diag(Func->getLocation(), "%0 variables (threshold %1)",
217 DiagnosticIDs::Note)
218 << FI.Variables << VariableThreshold;
219 }
220}
221
222} // namespace clang::tidy::readability
const FunctionDecl * Decl
unsigned Lines
unsigned NestingThreshold
unsigned Statements
std::vector< SourceLocation > NestingThresholders
unsigned Branches
unsigned Variables
unsigned StructNesting
llvm::BitVector TrackedParent
FunctionInfo Info
llvm::StringRef Name
::clang::DynTypedNode Node
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.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
llvm::StringMap< ClangTidyValue > OptionMap