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 #include "llvm/ADT/BitVector.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace readability {
19 namespace {
20 
21 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
22   using Base = RecursiveASTVisitor<FunctionASTVisitor>;
23 
24 public:
VisitVarDecl(VarDecl * VD)25   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   }
VisitBindingDecl(BindingDecl * BD)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 
TraverseStmt(Stmt * Node)40   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;
55       LLVM_FALLTHROUGH;
56     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();
67 
68     return true;
69   }
70 
TraverseCompoundStmt(CompoundStmt * Node)71   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->getBeginLoc());
77 
78     ++CurrentNestingLevel;
79     Base::TraverseCompoundStmt(Node);
80     --CurrentNestingLevel;
81 
82     return true;
83   }
84 
TraverseDecl(Decl * Node)85   bool TraverseDecl(Decl *Node) {
86     TrackedParent.push_back(false);
87     Base::TraverseDecl(Node);
88     TrackedParent.pop_back();
89     return true;
90   }
91 
TraverseLambdaExpr(LambdaExpr * Node)92   bool TraverseLambdaExpr(LambdaExpr *Node) {
93     ++StructNesting;
94     Base::TraverseLambdaExpr(Node);
95     --StructNesting;
96     return true;
97   }
98 
TraverseCXXRecordDecl(CXXRecordDecl * Node)99   bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
100     ++StructNesting;
101     Base::TraverseCXXRecordDecl(Node);
102     --StructNesting;
103     return true;
104   }
105 
TraverseStmtExpr(StmtExpr * SE)106   bool TraverseStmtExpr(StmtExpr *SE) {
107     ++StructNesting;
108     Base::TraverseStmtExpr(SE);
109     --StructNesting;
110     return true;
111   }
112 
113   struct FunctionInfo {
114     unsigned Lines = 0;
115     unsigned Statements = 0;
116     unsigned Branches = 0;
117     unsigned NestingThreshold = 0;
118     unsigned Variables = 0;
119     std::vector<SourceLocation> NestingThresholders;
120   };
121   FunctionInfo Info;
122   llvm::BitVector TrackedParent;
123   unsigned StructNesting = 0;
124   unsigned CurrentNestingLevel = 0;
125 };
126 
127 } // namespace
128 
FunctionSizeCheck(StringRef Name,ClangTidyContext * Context)129 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
130     : ClangTidyCheck(Name, Context),
131       LineThreshold(Options.get("LineThreshold", -1U)),
132       StatementThreshold(Options.get("StatementThreshold", 800U)),
133       BranchThreshold(Options.get("BranchThreshold", -1U)),
134       ParameterThreshold(Options.get("ParameterThreshold", -1U)),
135       NestingThreshold(Options.get("NestingThreshold", -1U)),
136       VariableThreshold(Options.get("VariableThreshold", -1U)) {}
137 
storeOptions(ClangTidyOptions::OptionMap & Opts)138 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
139   Options.store(Opts, "LineThreshold", LineThreshold);
140   Options.store(Opts, "StatementThreshold", StatementThreshold);
141   Options.store(Opts, "BranchThreshold", BranchThreshold);
142   Options.store(Opts, "ParameterThreshold", ParameterThreshold);
143   Options.store(Opts, "NestingThreshold", NestingThreshold);
144   Options.store(Opts, "VariableThreshold", VariableThreshold);
145 }
146 
registerMatchers(MatchFinder * Finder)147 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
148   // Lambdas ignored - historically considered part of enclosing function.
149   // FIXME: include them instead? Top-level lambdas are currently never counted.
150   Finder->addMatcher(functionDecl(unless(isInstantiated()),
151                                   unless(cxxMethodDecl(ofClass(isLambda()))))
152                          .bind("func"),
153                      this);
154 }
155 
check(const MatchFinder::MatchResult & Result)156 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
157   const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
158 
159   FunctionASTVisitor Visitor;
160   Visitor.Info.NestingThreshold = NestingThreshold;
161   Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
162   auto &FI = Visitor.Info;
163 
164   if (FI.Statements == 0)
165     return;
166 
167   // Count the lines including whitespace and comments. Really simple.
168   if (const Stmt *Body = Func->getBody()) {
169     SourceManager *SM = Result.SourceManager;
170     if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
171       FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
172                  SM->getSpellingLineNumber(Body->getBeginLoc());
173     }
174   }
175 
176   unsigned ActualNumberParameters = Func->getNumParams();
177 
178   if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
179       FI.Branches > BranchThreshold ||
180       ActualNumberParameters > ParameterThreshold ||
181       !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
182     diag(Func->getLocation(),
183          "function %0 exceeds recommended size/complexity thresholds")
184         << Func;
185   }
186 
187   if (FI.Lines > LineThreshold) {
188     diag(Func->getLocation(),
189          "%0 lines including whitespace and comments (threshold %1)",
190          DiagnosticIDs::Note)
191         << FI.Lines << LineThreshold;
192   }
193 
194   if (FI.Statements > StatementThreshold) {
195     diag(Func->getLocation(), "%0 statements (threshold %1)",
196          DiagnosticIDs::Note)
197         << FI.Statements << StatementThreshold;
198   }
199 
200   if (FI.Branches > BranchThreshold) {
201     diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
202         << FI.Branches << BranchThreshold;
203   }
204 
205   if (ActualNumberParameters > ParameterThreshold) {
206     diag(Func->getLocation(), "%0 parameters (threshold %1)",
207          DiagnosticIDs::Note)
208         << ActualNumberParameters << ParameterThreshold;
209   }
210 
211   for (const auto &CSPos : FI.NestingThresholders) {
212     diag(CSPos, "nesting level %0 starts here (threshold %1)",
213          DiagnosticIDs::Note)
214         << NestingThreshold + 1 << NestingThreshold;
215   }
216 
217   if (FI.Variables > VariableThreshold) {
218     diag(Func->getLocation(), "%0 variables (threshold %1)",
219          DiagnosticIDs::Note)
220         << FI.Variables << VariableThreshold;
221   }
222 }
223 
224 } // namespace readability
225 } // namespace tidy
226 } // namespace clang
227