150
|
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"
|
236
|
12 #include "llvm/ADT/BitVector.h"
|
150
|
13
|
|
14 using namespace clang::ast_matchers;
|
|
15
|
252
|
16 namespace clang::tidy::readability {
|
150
|
17 namespace {
|
|
18
|
|
19 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
|
|
20 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
|
|
21
|
|
22 public:
|
|
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;
|
236
|
53 [[fallthrough]];
|
150
|
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 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;
|
236
|
120 llvm::BitVector TrackedParent;
|
150
|
121 unsigned StructNesting = 0;
|
|
122 unsigned CurrentNestingLevel = 0;
|
|
123 };
|
|
124
|
|
125 } // namespace
|
|
126
|
|
127 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
|
|
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
|
|
136 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
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
|
|
145 void 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
|
|
154 void 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
|
252
|
222 } // namespace clang::tidy::readability
|