blob: d98bec2ebdf1fc3ab91df96293ed08ba8d125113 [file] [log] [blame]
Fangrui Songffe9f002019-03-01 09:52:53 +00001//===-- FunctionSizeCheck.cpp - clang-tidy --------------------------------===//
Benjamin Kramer6e195422014-09-15 12:48:25 +00002//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Benjamin Kramer6e195422014-09-15 12:48:25 +00006//
7//===----------------------------------------------------------------------===//
8
Alexander Kornienko1b677db2015-03-09 12:18:39 +00009#include "FunctionSizeCheck.h"
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000010#include "clang/AST/RecursiveASTVisitor.h"
Benjamin Kramer6e195422014-09-15 12:48:25 +000011#include "clang/ASTMatchers/ASTMatchFinder.h"
12
13using namespace clang::ast_matchers;
14
15namespace clang {
16namespace tidy {
Alexander Kornienko35ddae42014-10-15 10:51:57 +000017namespace readability {
Roman Lebedev728284d2017-09-11 13:12:31 +000018namespace {
Benjamin Kramer6e195422014-09-15 12:48:25 +000019
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000020class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
21 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
22
23public:
Roman Lebedev4d37af02018-04-12 12:06:42 +000024 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 }
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
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000039 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;
Roman Lebedeveb6d8212017-06-16 13:07:47 +000054 LLVM_FALLTHROUGH;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000055 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();
Roman Lebedeva1cee292017-06-09 14:22:10 +000066
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000067 return true;
68 }
69
Roman Lebedeveb6d8212017-06-16 13:07:47 +000070 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)
Stephen Kelly43465bf2018-08-09 22:42:26 +000075 Info.NestingThresholders.push_back(Node->getBeginLoc());
Roman Lebedeveb6d8212017-06-16 13:07:47 +000076
77 ++CurrentNestingLevel;
78 Base::TraverseCompoundStmt(Node);
79 --CurrentNestingLevel;
80
81 return true;
82 }
83
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000084 bool TraverseDecl(Decl *Node) {
85 TrackedParent.push_back(false);
86 Base::TraverseDecl(Node);
87 TrackedParent.pop_back();
88 return true;
89 }
90
Roman Lebedev4d37af02018-04-12 12:06:42 +000091 bool TraverseLambdaExpr(LambdaExpr *Node) {
92 ++StructNesting;
93 Base::TraverseLambdaExpr(Node);
94 --StructNesting;
95 return true;
96 }
97
98 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
99 ++StructNesting;
100 Base::TraverseCXXRecordDecl(Node);
101 --StructNesting;
102 return true;
103 }
104
105 bool TraverseStmtExpr(StmtExpr *SE) {
106 ++StructNesting;
107 Base::TraverseStmtExpr(SE);
108 --StructNesting;
109 return true;
110 }
111
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000112 struct FunctionInfo {
Roman Lebedeva1cee292017-06-09 14:22:10 +0000113 unsigned Lines = 0;
114 unsigned Statements = 0;
115 unsigned Branches = 0;
116 unsigned NestingThreshold = 0;
Roman Lebedev4d37af02018-04-12 12:06:42 +0000117 unsigned Variables = 0;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000118 std::vector<SourceLocation> NestingThresholders;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000119 };
120 FunctionInfo Info;
121 std::vector<bool> TrackedParent;
Roman Lebedev4d37af02018-04-12 12:06:42 +0000122 unsigned StructNesting = 0;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000123 unsigned CurrentNestingLevel = 0;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000124};
125
Roman Lebedev728284d2017-09-11 13:12:31 +0000126} // namespace
127
Benjamin Kramer6e195422014-09-15 12:48:25 +0000128FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
129 : ClangTidyCheck(Name, Context),
130 LineThreshold(Options.get("LineThreshold", -1U)),
131 StatementThreshold(Options.get("StatementThreshold", 800U)),
Alexander Kornienko91086442017-03-01 10:17:32 +0000132 BranchThreshold(Options.get("BranchThreshold", -1U)),
Roman Lebedeva1cee292017-06-09 14:22:10 +0000133 ParameterThreshold(Options.get("ParameterThreshold", -1U)),
Roman Lebedev4d37af02018-04-12 12:06:42 +0000134 NestingThreshold(Options.get("NestingThreshold", -1U)),
135 VariableThreshold(Options.get("VariableThreshold", -1U)) {}
Benjamin Kramer6e195422014-09-15 12:48:25 +0000136
137void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
138 Options.store(Opts, "LineThreshold", LineThreshold);
139 Options.store(Opts, "StatementThreshold", StatementThreshold);
140 Options.store(Opts, "BranchThreshold", BranchThreshold);
Alexander Kornienko91086442017-03-01 10:17:32 +0000141 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
Roman Lebedeva1cee292017-06-09 14:22:10 +0000142 Options.store(Opts, "NestingThreshold", NestingThreshold);
Roman Lebedev4d37af02018-04-12 12:06:42 +0000143 Options.store(Opts, "VariableThreshold", VariableThreshold);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000144}
145
146void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
Sam McCalld37be4b2019-01-14 10:40:41 +0000147 // 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);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000153}
154
155void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
156 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
157
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000158 FunctionASTVisitor Visitor;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000159 Visitor.Info.NestingThreshold = NestingThreshold;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000160 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
161 auto &FI = Visitor.Info;
162
163 if (FI.Statements == 0)
164 return;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000165
166 // Count the lines including whitespace and comments. Really simple.
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000167 if (const Stmt *Body = Func->getBody()) {
168 SourceManager *SM = Result.SourceManager;
Stephen Kellyc09197e2018-08-09 22:43:02 +0000169 if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
170 FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
Stephen Kelly43465bf2018-08-09 22:42:26 +0000171 SM->getSpellingLineNumber(Body->getBeginLoc());
Benjamin Kramer6e195422014-09-15 12:48:25 +0000172 }
173 }
174
Alexander Kornienko91086442017-03-01 10:17:32 +0000175 unsigned ActualNumberParameters = Func->getNumParams();
176
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000177 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
Alexander Kornienko91086442017-03-01 10:17:32 +0000178 FI.Branches > BranchThreshold ||
Roman Lebedeva1cee292017-06-09 14:22:10 +0000179 ActualNumberParameters > ParameterThreshold ||
Roman Lebedev4d37af02018-04-12 12:06:42 +0000180 !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000181 diag(Func->getLocation(),
182 "function %0 exceeds recommended size/complexity thresholds")
183 << Func;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000184 }
185
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000186 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 }
Alexander Kornienko91086442017-03-01 10:17:32 +0000203
204 if (ActualNumberParameters > ParameterThreshold) {
205 diag(Func->getLocation(), "%0 parameters (threshold %1)",
206 DiagnosticIDs::Note)
207 << ActualNumberParameters << ParameterThreshold;
208 }
Roman Lebedeva1cee292017-06-09 14:22:10 +0000209
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 }
Roman Lebedev4d37af02018-04-12 12:06:42 +0000215
216 if (FI.Variables > VariableThreshold) {
217 diag(Func->getLocation(), "%0 variables (threshold %1)",
218 DiagnosticIDs::Note)
219 << FI.Variables << VariableThreshold;
220 }
Benjamin Kramer6e195422014-09-15 12:48:25 +0000221}
222
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000223} // namespace readability
Benjamin Kramer6e195422014-09-15 12:48:25 +0000224} // namespace tidy
225} // namespace clang