blob: 8ed6b90773a03b96d6dd97529edb746af1627859 [file] [log] [blame]
Benjamin Kramer6e195422014-09-15 12:48:25 +00001//===--- FunctionSize.cpp - clang-tidy ------------------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
Alexander Kornienko1b677db2015-03-09 12:18:39 +000010#include "FunctionSizeCheck.h"
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000011#include "clang/AST/RecursiveASTVisitor.h"
Benjamin Kramer6e195422014-09-15 12:48:25 +000012#include "clang/ASTMatchers/ASTMatchFinder.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang {
17namespace tidy {
Alexander Kornienko35ddae42014-10-15 10:51:57 +000018namespace readability {
Roman Lebedev728284d2017-09-11 13:12:31 +000019namespace {
Benjamin Kramer6e195422014-09-15 12:48:25 +000020
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000021class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
22 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
23
24public:
Roman Lebedev4d37af02018-04-12 12:06:42 +000025 bool VisitVarDecl(VarDecl *VD) {
26 // Do not count function params.
27 // Do not count decomposition declarations (C++17's structured bindings).
28 if (StructNesting == 0 &&
29 !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD)))
30 ++Info.Variables;
31 return true;
32 }
33 bool VisitBindingDecl(BindingDecl *BD) {
34 // Do count each of the bindings (in the decomposition declaration).
35 if (StructNesting == 0)
36 ++Info.Variables;
37 return true;
38 }
39
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000040 bool TraverseStmt(Stmt *Node) {
41 if (!Node)
42 return Base::TraverseStmt(Node);
43
44 if (TrackedParent.back() && !isa<CompoundStmt>(Node))
45 ++Info.Statements;
46
47 switch (Node->getStmtClass()) {
48 case Stmt::IfStmtClass:
49 case Stmt::WhileStmtClass:
50 case Stmt::DoStmtClass:
51 case Stmt::CXXForRangeStmtClass:
52 case Stmt::ForStmtClass:
53 case Stmt::SwitchStmtClass:
54 ++Info.Branches;
Roman Lebedeveb6d8212017-06-16 13:07:47 +000055 LLVM_FALLTHROUGH;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000056 case Stmt::CompoundStmtClass:
57 TrackedParent.push_back(true);
58 break;
59 default:
60 TrackedParent.push_back(false);
61 break;
62 }
63
64 Base::TraverseStmt(Node);
65
66 TrackedParent.pop_back();
Roman Lebedeva1cee292017-06-09 14:22:10 +000067
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000068 return true;
69 }
70
Roman Lebedeveb6d8212017-06-16 13:07:47 +000071 bool TraverseCompoundStmt(CompoundStmt *Node) {
72 // If this new compound statement is located in a compound statement, which
73 // is already nested NestingThreshold levels deep, record the start location
74 // of this new compound statement.
75 if (CurrentNestingLevel == Info.NestingThreshold)
76 Info.NestingThresholders.push_back(Node->getLocStart());
77
78 ++CurrentNestingLevel;
79 Base::TraverseCompoundStmt(Node);
80 --CurrentNestingLevel;
81
82 return true;
83 }
84
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000085 bool TraverseDecl(Decl *Node) {
86 TrackedParent.push_back(false);
87 Base::TraverseDecl(Node);
88 TrackedParent.pop_back();
89 return true;
90 }
91
Roman Lebedev4d37af02018-04-12 12:06:42 +000092 bool TraverseLambdaExpr(LambdaExpr *Node) {
93 ++StructNesting;
94 Base::TraverseLambdaExpr(Node);
95 --StructNesting;
96 return true;
97 }
98
99 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
100 ++StructNesting;
101 Base::TraverseCXXRecordDecl(Node);
102 --StructNesting;
103 return true;
104 }
105
106 bool TraverseStmtExpr(StmtExpr *SE) {
107 ++StructNesting;
108 Base::TraverseStmtExpr(SE);
109 --StructNesting;
110 return true;
111 }
112
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000113 struct FunctionInfo {
Roman Lebedeva1cee292017-06-09 14:22:10 +0000114 unsigned Lines = 0;
115 unsigned Statements = 0;
116 unsigned Branches = 0;
117 unsigned NestingThreshold = 0;
Roman Lebedev4d37af02018-04-12 12:06:42 +0000118 unsigned Variables = 0;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000119 std::vector<SourceLocation> NestingThresholders;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000120 };
121 FunctionInfo Info;
122 std::vector<bool> TrackedParent;
Roman Lebedev4d37af02018-04-12 12:06:42 +0000123 unsigned StructNesting = 0;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000124 unsigned CurrentNestingLevel = 0;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000125};
126
Roman Lebedev728284d2017-09-11 13:12:31 +0000127} // namespace
128
Benjamin Kramer6e195422014-09-15 12:48:25 +0000129FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
130 : ClangTidyCheck(Name, Context),
131 LineThreshold(Options.get("LineThreshold", -1U)),
132 StatementThreshold(Options.get("StatementThreshold", 800U)),
Alexander Kornienko91086442017-03-01 10:17:32 +0000133 BranchThreshold(Options.get("BranchThreshold", -1U)),
Roman Lebedeva1cee292017-06-09 14:22:10 +0000134 ParameterThreshold(Options.get("ParameterThreshold", -1U)),
Roman Lebedev4d37af02018-04-12 12:06:42 +0000135 NestingThreshold(Options.get("NestingThreshold", -1U)),
136 VariableThreshold(Options.get("VariableThreshold", -1U)) {}
Benjamin Kramer6e195422014-09-15 12:48:25 +0000137
138void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
139 Options.store(Opts, "LineThreshold", LineThreshold);
140 Options.store(Opts, "StatementThreshold", StatementThreshold);
141 Options.store(Opts, "BranchThreshold", BranchThreshold);
Alexander Kornienko91086442017-03-01 10:17:32 +0000142 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
Roman Lebedeva1cee292017-06-09 14:22:10 +0000143 Options.store(Opts, "NestingThreshold", NestingThreshold);
Roman Lebedev4d37af02018-04-12 12:06:42 +0000144 Options.store(Opts, "VariableThreshold", VariableThreshold);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000145}
146
147void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000148 Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000149}
150
151void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
152 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
153
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000154 FunctionASTVisitor Visitor;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000155 Visitor.Info.NestingThreshold = NestingThreshold;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000156 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
157 auto &FI = Visitor.Info;
158
159 if (FI.Statements == 0)
160 return;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000161
162 // Count the lines including whitespace and comments. Really simple.
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000163 if (const Stmt *Body = Func->getBody()) {
164 SourceManager *SM = Result.SourceManager;
165 if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
166 FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) -
167 SM->getSpellingLineNumber(Body->getLocStart());
Benjamin Kramer6e195422014-09-15 12:48:25 +0000168 }
169 }
170
Alexander Kornienko91086442017-03-01 10:17:32 +0000171 unsigned ActualNumberParameters = Func->getNumParams();
172
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000173 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
Alexander Kornienko91086442017-03-01 10:17:32 +0000174 FI.Branches > BranchThreshold ||
Roman Lebedeva1cee292017-06-09 14:22:10 +0000175 ActualNumberParameters > ParameterThreshold ||
Roman Lebedev4d37af02018-04-12 12:06:42 +0000176 !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000177 diag(Func->getLocation(),
178 "function %0 exceeds recommended size/complexity thresholds")
179 << Func;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000180 }
181
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000182 if (FI.Lines > LineThreshold) {
183 diag(Func->getLocation(),
184 "%0 lines including whitespace and comments (threshold %1)",
185 DiagnosticIDs::Note)
186 << FI.Lines << LineThreshold;
187 }
188
189 if (FI.Statements > StatementThreshold) {
190 diag(Func->getLocation(), "%0 statements (threshold %1)",
191 DiagnosticIDs::Note)
192 << FI.Statements << StatementThreshold;
193 }
194
195 if (FI.Branches > BranchThreshold) {
196 diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
197 << FI.Branches << BranchThreshold;
198 }
Alexander Kornienko91086442017-03-01 10:17:32 +0000199
200 if (ActualNumberParameters > ParameterThreshold) {
201 diag(Func->getLocation(), "%0 parameters (threshold %1)",
202 DiagnosticIDs::Note)
203 << ActualNumberParameters << ParameterThreshold;
204 }
Roman Lebedeva1cee292017-06-09 14:22:10 +0000205
206 for (const auto &CSPos : FI.NestingThresholders) {
207 diag(CSPos, "nesting level %0 starts here (threshold %1)",
208 DiagnosticIDs::Note)
209 << NestingThreshold + 1 << NestingThreshold;
210 }
Roman Lebedev4d37af02018-04-12 12:06:42 +0000211
212 if (FI.Variables > VariableThreshold) {
213 diag(Func->getLocation(), "%0 variables (threshold %1)",
214 DiagnosticIDs::Note)
215 << FI.Variables << VariableThreshold;
216 }
Benjamin Kramer6e195422014-09-15 12:48:25 +0000217}
218
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000219} // namespace readability
Benjamin Kramer6e195422014-09-15 12:48:25 +0000220} // namespace tidy
221} // namespace clang