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