[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clang-tidy / readability / FunctionSizeCheck.cpp
blob3313bcb39b7f351004630633f24bfc43aeb01644
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 //===----------------------------------------------------------------------===//
9 #include "FunctionSizeCheck.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "llvm/ADT/BitVector.h"
14 using namespace clang::ast_matchers;
16 namespace clang::tidy::readability {
17 namespace {
19 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
20 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
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;
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;
38 bool TraverseStmt(Stmt *Node) {
39 if (!Node)
40 return Base::TraverseStmt(Node);
42 if (TrackedParent.back() && !isa<CompoundStmt>(Node))
43 ++Info.Statements;
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;
62 Base::TraverseStmt(Node);
64 TrackedParent.pop_back();
66 return true;
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());
76 ++CurrentNestingLevel;
77 Base::TraverseCompoundStmt(Node);
78 --CurrentNestingLevel;
80 return true;
83 bool TraverseDecl(Decl *Node) {
84 TrackedParent.push_back(false);
85 Base::TraverseDecl(Node);
86 TrackedParent.pop_back();
87 return true;
90 bool TraverseLambdaExpr(LambdaExpr *Node) {
91 ++StructNesting;
92 Base::TraverseLambdaExpr(Node);
93 --StructNesting;
94 return true;
97 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
98 ++StructNesting;
99 Base::TraverseCXXRecordDecl(Node);
100 --StructNesting;
101 return true;
104 bool TraverseStmtExpr(StmtExpr *SE) {
105 ++StructNesting;
106 Base::TraverseStmtExpr(SE);
107 --StructNesting;
108 return true;
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;
119 FunctionInfo Info;
120 llvm::BitVector TrackedParent;
121 unsigned StructNesting = 0;
122 unsigned CurrentNestingLevel = 0;
125 } // namespace
127 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
128 : ClangTidyCheck(Name, Context),
129 LineThreshold(Options.get("LineThreshold", DefaultLineThreshold)),
130 StatementThreshold(
131 Options.get("StatementThreshold", DefaultStatementThreshold)),
132 BranchThreshold(Options.get("BranchThreshold", DefaultBranchThreshold)),
133 ParameterThreshold(
134 Options.get("ParameterThreshold", DefaultParameterThreshold)),
135 NestingThreshold(
136 Options.get("NestingThreshold", DefaultNestingThreshold)),
137 VariableThreshold(
138 Options.get("VariableThreshold", DefaultVariableThreshold)) {}
140 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
141 Options.store(Opts, "LineThreshold", LineThreshold);
142 Options.store(Opts, "StatementThreshold", StatementThreshold);
143 Options.store(Opts, "BranchThreshold", BranchThreshold);
144 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
145 Options.store(Opts, "NestingThreshold", NestingThreshold);
146 Options.store(Opts, "VariableThreshold", VariableThreshold);
149 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
150 // Lambdas ignored - historically considered part of enclosing function.
151 // FIXME: include them instead? Top-level lambdas are currently never counted.
152 Finder->addMatcher(functionDecl(unless(isInstantiated()),
153 unless(cxxMethodDecl(ofClass(isLambda()))))
154 .bind("func"),
155 this);
158 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
159 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
161 FunctionASTVisitor Visitor;
162 Visitor.Info.NestingThreshold = NestingThreshold.value_or(-1);
163 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
164 auto &FI = Visitor.Info;
166 if (FI.Statements == 0)
167 return;
169 // Count the lines including whitespace and comments. Really simple.
170 if (const Stmt *Body = Func->getBody()) {
171 SourceManager *SM = Result.SourceManager;
172 if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
173 FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
174 SM->getSpellingLineNumber(Body->getBeginLoc());
178 unsigned ActualNumberParameters = Func->getNumParams();
180 if ((LineThreshold && FI.Lines > LineThreshold) ||
181 (StatementThreshold && FI.Statements > StatementThreshold) ||
182 (BranchThreshold && FI.Branches > BranchThreshold) ||
183 (ParameterThreshold && ActualNumberParameters > ParameterThreshold) ||
184 !FI.NestingThresholders.empty() ||
185 (VariableThreshold && FI.Variables > VariableThreshold)) {
186 diag(Func->getLocation(),
187 "function %0 exceeds recommended size/complexity thresholds")
188 << Func;
191 if (LineThreshold && FI.Lines > LineThreshold) {
192 diag(Func->getLocation(),
193 "%0 lines including whitespace and comments (threshold %1)",
194 DiagnosticIDs::Note)
195 << FI.Lines << LineThreshold.value();
198 if (StatementThreshold && FI.Statements > StatementThreshold) {
199 diag(Func->getLocation(), "%0 statements (threshold %1)",
200 DiagnosticIDs::Note)
201 << FI.Statements << StatementThreshold.value();
204 if (BranchThreshold && FI.Branches > BranchThreshold) {
205 diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
206 << FI.Branches << BranchThreshold.value();
209 if (ParameterThreshold && ActualNumberParameters > ParameterThreshold) {
210 diag(Func->getLocation(), "%0 parameters (threshold %1)",
211 DiagnosticIDs::Note)
212 << ActualNumberParameters << ParameterThreshold.value();
215 for (const auto &CSPos : FI.NestingThresholders) {
216 diag(CSPos, "nesting level %0 starts here (threshold %1)",
217 DiagnosticIDs::Note)
218 << NestingThreshold.value() + 1 << NestingThreshold.value();
221 if (VariableThreshold && FI.Variables > VariableThreshold) {
222 diag(Func->getLocation(), "%0 variables (threshold %1)",
223 DiagnosticIDs::Note)
224 << FI.Variables << VariableThreshold.value();
228 } // namespace clang::tidy::readability