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