1 //===--- 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 
10 #include "FunctionSizeCheck.h"
11 #include "clang/AST/RecursiveASTVisitor.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.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:
25   bool TraverseStmt(Stmt *Node) {
26     if (!Node)
27       return Base::TraverseStmt(Node);
28 
29     if (TrackedParent.back() && !isa<CompoundStmt>(Node))
30       ++Info.Statements;
31 
32     switch (Node->getStmtClass()) {
33     case Stmt::IfStmtClass:
34     case Stmt::WhileStmtClass:
35     case Stmt::DoStmtClass:
36     case Stmt::CXXForRangeStmtClass:
37     case Stmt::ForStmtClass:
38     case Stmt::SwitchStmtClass:
39       ++Info.Branches;
40       LLVM_FALLTHROUGH;
41     case Stmt::CompoundStmtClass:
42       TrackedParent.push_back(true);
43       break;
44     default:
45       TrackedParent.push_back(false);
46       break;
47     }
48 
49     Base::TraverseStmt(Node);
50 
51     TrackedParent.pop_back();
52 
53     return true;
54   }
55 
56   bool TraverseCompoundStmt(CompoundStmt *Node) {
57     // If this new compound statement is located in a compound statement, which
58     // is already nested NestingThreshold levels deep, record the start location
59     // of this new compound statement.
60     if (CurrentNestingLevel == Info.NestingThreshold)
61       Info.NestingThresholders.push_back(Node->getLocStart());
62 
63     ++CurrentNestingLevel;
64     Base::TraverseCompoundStmt(Node);
65     --CurrentNestingLevel;
66 
67     return true;
68   }
69 
70   bool TraverseDecl(Decl *Node) {
71     TrackedParent.push_back(false);
72     Base::TraverseDecl(Node);
73     TrackedParent.pop_back();
74     return true;
75   }
76 
77   struct FunctionInfo {
78     unsigned Lines = 0;
79     unsigned Statements = 0;
80     unsigned Branches = 0;
81     unsigned NestingThreshold = 0;
82     std::vector<SourceLocation> NestingThresholders;
83   };
84   FunctionInfo Info;
85   std::vector<bool> TrackedParent;
86   unsigned CurrentNestingLevel = 0;
87 };
88 
89 } // namespace
90 
91 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
92     : ClangTidyCheck(Name, Context),
93       LineThreshold(Options.get("LineThreshold", -1U)),
94       StatementThreshold(Options.get("StatementThreshold", 800U)),
95       BranchThreshold(Options.get("BranchThreshold", -1U)),
96       ParameterThreshold(Options.get("ParameterThreshold", -1U)),
97       NestingThreshold(Options.get("NestingThreshold", -1U)) {}
98 
99 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
100   Options.store(Opts, "LineThreshold", LineThreshold);
101   Options.store(Opts, "StatementThreshold", StatementThreshold);
102   Options.store(Opts, "BranchThreshold", BranchThreshold);
103   Options.store(Opts, "ParameterThreshold", ParameterThreshold);
104   Options.store(Opts, "NestingThreshold", NestingThreshold);
105 }
106 
107 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
108   Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this);
109 }
110 
111 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
112   const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
113 
114   FunctionASTVisitor Visitor;
115   Visitor.Info.NestingThreshold = NestingThreshold;
116   Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
117   auto &FI = Visitor.Info;
118 
119   if (FI.Statements == 0)
120     return;
121 
122   // Count the lines including whitespace and comments. Really simple.
123   if (const Stmt *Body = Func->getBody()) {
124     SourceManager *SM = Result.SourceManager;
125     if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
126       FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) -
127                  SM->getSpellingLineNumber(Body->getLocStart());
128     }
129   }
130 
131   unsigned ActualNumberParameters = Func->getNumParams();
132 
133   if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
134       FI.Branches > BranchThreshold ||
135       ActualNumberParameters > ParameterThreshold ||
136       !FI.NestingThresholders.empty()) {
137     diag(Func->getLocation(),
138          "function %0 exceeds recommended size/complexity thresholds")
139         << Func;
140   }
141 
142   if (FI.Lines > LineThreshold) {
143     diag(Func->getLocation(),
144          "%0 lines including whitespace and comments (threshold %1)",
145          DiagnosticIDs::Note)
146         << FI.Lines << LineThreshold;
147   }
148 
149   if (FI.Statements > StatementThreshold) {
150     diag(Func->getLocation(), "%0 statements (threshold %1)",
151          DiagnosticIDs::Note)
152         << FI.Statements << StatementThreshold;
153   }
154 
155   if (FI.Branches > BranchThreshold) {
156     diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
157         << FI.Branches << BranchThreshold;
158   }
159 
160   if (ActualNumberParameters > ParameterThreshold) {
161     diag(Func->getLocation(), "%0 parameters (threshold %1)",
162          DiagnosticIDs::Note)
163         << ActualNumberParameters << ParameterThreshold;
164   }
165 
166   for (const auto &CSPos : FI.NestingThresholders) {
167     diag(CSPos, "nesting level %0 starts here (threshold %1)",
168          DiagnosticIDs::Note)
169         << NestingThreshold + 1 << NestingThreshold;
170   }
171 }
172 
173 } // namespace readability
174 } // namespace tidy
175 } // namespace clang
176