• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace readability {
18 namespace {
19 
20 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
21   using Base = RecursiveASTVisitor<FunctionASTVisitor>;
22 
23 public:
VisitVarDecl(VarDecl * VD)24   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   }
VisitBindingDecl(BindingDecl * BD)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 
TraverseStmt(Stmt * Node)39   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;
54       LLVM_FALLTHROUGH;
55     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();
66 
67     return true;
68   }
69 
TraverseCompoundStmt(CompoundStmt * Node)70   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)
75       Info.NestingThresholders.push_back(Node->getBeginLoc());
76 
77     ++CurrentNestingLevel;
78     Base::TraverseCompoundStmt(Node);
79     --CurrentNestingLevel;
80 
81     return true;
82   }
83 
TraverseDecl(Decl * Node)84   bool TraverseDecl(Decl *Node) {
85     TrackedParent.push_back(false);
86     Base::TraverseDecl(Node);
87     TrackedParent.pop_back();
88     return true;
89   }
90 
TraverseLambdaExpr(LambdaExpr * Node)91   bool TraverseLambdaExpr(LambdaExpr *Node) {
92     ++StructNesting;
93     Base::TraverseLambdaExpr(Node);
94     --StructNesting;
95     return true;
96   }
97 
TraverseCXXRecordDecl(CXXRecordDecl * Node)98   bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
99     ++StructNesting;
100     Base::TraverseCXXRecordDecl(Node);
101     --StructNesting;
102     return true;
103   }
104 
TraverseStmtExpr(StmtExpr * SE)105   bool TraverseStmtExpr(StmtExpr *SE) {
106     ++StructNesting;
107     Base::TraverseStmtExpr(SE);
108     --StructNesting;
109     return true;
110   }
111 
112   struct FunctionInfo {
113     unsigned Lines = 0;
114     unsigned Statements = 0;
115     unsigned Branches = 0;
116     unsigned NestingThreshold = 0;
117     unsigned Variables = 0;
118     std::vector<SourceLocation> NestingThresholders;
119   };
120   FunctionInfo Info;
121   std::vector<bool> TrackedParent;
122   unsigned StructNesting = 0;
123   unsigned CurrentNestingLevel = 0;
124 };
125 
126 } // namespace
127 
FunctionSizeCheck(StringRef Name,ClangTidyContext * Context)128 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
129     : ClangTidyCheck(Name, Context),
130       LineThreshold(Options.get("LineThreshold", -1U)),
131       StatementThreshold(Options.get("StatementThreshold", 800U)),
132       BranchThreshold(Options.get("BranchThreshold", -1U)),
133       ParameterThreshold(Options.get("ParameterThreshold", -1U)),
134       NestingThreshold(Options.get("NestingThreshold", -1U)),
135       VariableThreshold(Options.get("VariableThreshold", -1U)) {}
136 
storeOptions(ClangTidyOptions::OptionMap & Opts)137 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
138   Options.store(Opts, "LineThreshold", LineThreshold);
139   Options.store(Opts, "StatementThreshold", StatementThreshold);
140   Options.store(Opts, "BranchThreshold", BranchThreshold);
141   Options.store(Opts, "ParameterThreshold", ParameterThreshold);
142   Options.store(Opts, "NestingThreshold", NestingThreshold);
143   Options.store(Opts, "VariableThreshold", VariableThreshold);
144 }
145 
registerMatchers(MatchFinder * Finder)146 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
147   // 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);
153 }
154 
check(const MatchFinder::MatchResult & Result)155 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
156   const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
157 
158   FunctionASTVisitor Visitor;
159   Visitor.Info.NestingThreshold = NestingThreshold;
160   Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
161   auto &FI = Visitor.Info;
162 
163   if (FI.Statements == 0)
164     return;
165 
166   // Count the lines including whitespace and comments. Really simple.
167   if (const Stmt *Body = Func->getBody()) {
168     SourceManager *SM = Result.SourceManager;
169     if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
170       FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
171                  SM->getSpellingLineNumber(Body->getBeginLoc());
172     }
173   }
174 
175   unsigned ActualNumberParameters = Func->getNumParams();
176 
177   if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
178       FI.Branches > BranchThreshold ||
179       ActualNumberParameters > ParameterThreshold ||
180       !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
181     diag(Func->getLocation(),
182          "function %0 exceeds recommended size/complexity thresholds")
183         << Func;
184   }
185 
186   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   }
203 
204   if (ActualNumberParameters > ParameterThreshold) {
205     diag(Func->getLocation(), "%0 parameters (threshold %1)",
206          DiagnosticIDs::Note)
207         << ActualNumberParameters << ParameterThreshold;
208   }
209 
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   }
215 
216   if (FI.Variables > VariableThreshold) {
217     diag(Func->getLocation(), "%0 variables (threshold %1)",
218          DiagnosticIDs::Note)
219         << FI.Variables << VariableThreshold;
220   }
221 }
222 
223 } // namespace readability
224 } // namespace tidy
225 } // namespace clang
226