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