blob: e6410ce2134a6f661c8469dd32887b6bce25eb5d [file] [log] [blame]
Benjamin Kramer6e195422014-09-15 12:48:25 +00001//===--- 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
Alexander Kornienko1b677db2015-03-09 12:18:39 +000010#include "FunctionSizeCheck.h"
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000011#include "clang/AST/RecursiveASTVisitor.h"
Benjamin Kramer6e195422014-09-15 12:48:25 +000012#include "clang/ASTMatchers/ASTMatchFinder.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang {
17namespace tidy {
Alexander Kornienko35ddae42014-10-15 10:51:57 +000018namespace readability {
Roman Lebedev728284d2017-09-11 13:12:31 +000019namespace {
Benjamin Kramer6e195422014-09-15 12:48:25 +000020
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000021class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
22 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
23
24public:
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;
Roman Lebedeveb6d8212017-06-16 13:07:47 +000040 LLVM_FALLTHROUGH;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000041 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();
Roman Lebedeva1cee292017-06-09 14:22:10 +000052
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000053 return true;
54 }
55
Roman Lebedeveb6d8212017-06-16 13:07:47 +000056 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
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000070 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 {
Roman Lebedeva1cee292017-06-09 14:22:10 +000078 unsigned Lines = 0;
79 unsigned Statements = 0;
80 unsigned Branches = 0;
81 unsigned NestingThreshold = 0;
82 std::vector<SourceLocation> NestingThresholders;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000083 };
84 FunctionInfo Info;
85 std::vector<bool> TrackedParent;
Roman Lebedeva1cee292017-06-09 14:22:10 +000086 unsigned CurrentNestingLevel = 0;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000087};
88
Roman Lebedev728284d2017-09-11 13:12:31 +000089} // namespace
90
Benjamin Kramer6e195422014-09-15 12:48:25 +000091FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
92 : ClangTidyCheck(Name, Context),
93 LineThreshold(Options.get("LineThreshold", -1U)),
94 StatementThreshold(Options.get("StatementThreshold", 800U)),
Alexander Kornienko91086442017-03-01 10:17:32 +000095 BranchThreshold(Options.get("BranchThreshold", -1U)),
Roman Lebedeva1cee292017-06-09 14:22:10 +000096 ParameterThreshold(Options.get("ParameterThreshold", -1U)),
97 NestingThreshold(Options.get("NestingThreshold", -1U)) {}
Benjamin Kramer6e195422014-09-15 12:48:25 +000098
99void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
100 Options.store(Opts, "LineThreshold", LineThreshold);
101 Options.store(Opts, "StatementThreshold", StatementThreshold);
102 Options.store(Opts, "BranchThreshold", BranchThreshold);
Alexander Kornienko91086442017-03-01 10:17:32 +0000103 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
Roman Lebedeva1cee292017-06-09 14:22:10 +0000104 Options.store(Opts, "NestingThreshold", NestingThreshold);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000105}
106
107void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000108 Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000109}
110
111void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
112 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
113
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000114 FunctionASTVisitor Visitor;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000115 Visitor.Info.NestingThreshold = NestingThreshold;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000116 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
117 auto &FI = Visitor.Info;
118
119 if (FI.Statements == 0)
120 return;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000121
122 // Count the lines including whitespace and comments. Really simple.
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000123 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());
Benjamin Kramer6e195422014-09-15 12:48:25 +0000128 }
129 }
130
Alexander Kornienko91086442017-03-01 10:17:32 +0000131 unsigned ActualNumberParameters = Func->getNumParams();
132
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000133 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
Alexander Kornienko91086442017-03-01 10:17:32 +0000134 FI.Branches > BranchThreshold ||
Roman Lebedeva1cee292017-06-09 14:22:10 +0000135 ActualNumberParameters > ParameterThreshold ||
136 !FI.NestingThresholders.empty()) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000137 diag(Func->getLocation(),
138 "function %0 exceeds recommended size/complexity thresholds")
139 << Func;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000140 }
141
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000142 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 }
Alexander Kornienko91086442017-03-01 10:17:32 +0000159
160 if (ActualNumberParameters > ParameterThreshold) {
161 diag(Func->getLocation(), "%0 parameters (threshold %1)",
162 DiagnosticIDs::Note)
163 << ActualNumberParameters << ParameterThreshold;
164 }
Roman Lebedeva1cee292017-06-09 14:22:10 +0000165
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 }
Benjamin Kramer6e195422014-09-15 12:48:25 +0000171}
172
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000173} // namespace readability
Benjamin Kramer6e195422014-09-15 12:48:25 +0000174} // namespace tidy
175} // namespace clang