blob: ea5f7505b69ff31bba3ce5b3a4653d443c72f52c [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 {
Benjamin Kramer6e195422014-09-15 12:48:25 +000019
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000020class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
21 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
22
23public:
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;
Roman Lebedeveb6d8212017-06-16 13:07:47 +000039 LLVM_FALLTHROUGH;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000040 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();
Roman Lebedeva1cee292017-06-09 14:22:10 +000051
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000052 return true;
53 }
54
Roman Lebedeveb6d8212017-06-16 13:07:47 +000055 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
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000069 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 {
Roman Lebedeva1cee292017-06-09 14:22:10 +000077 unsigned Lines = 0;
78 unsigned Statements = 0;
79 unsigned Branches = 0;
80 unsigned NestingThreshold = 0;
81 std::vector<SourceLocation> NestingThresholders;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000082 };
83 FunctionInfo Info;
84 std::vector<bool> TrackedParent;
Roman Lebedeva1cee292017-06-09 14:22:10 +000085 unsigned CurrentNestingLevel = 0;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +000086};
87
Benjamin Kramer6e195422014-09-15 12:48:25 +000088FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
89 : ClangTidyCheck(Name, Context),
90 LineThreshold(Options.get("LineThreshold", -1U)),
91 StatementThreshold(Options.get("StatementThreshold", 800U)),
Alexander Kornienko91086442017-03-01 10:17:32 +000092 BranchThreshold(Options.get("BranchThreshold", -1U)),
Roman Lebedeva1cee292017-06-09 14:22:10 +000093 ParameterThreshold(Options.get("ParameterThreshold", -1U)),
94 NestingThreshold(Options.get("NestingThreshold", -1U)) {}
Benjamin Kramer6e195422014-09-15 12:48:25 +000095
96void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
97 Options.store(Opts, "LineThreshold", LineThreshold);
98 Options.store(Opts, "StatementThreshold", StatementThreshold);
99 Options.store(Opts, "BranchThreshold", BranchThreshold);
Alexander Kornienko91086442017-03-01 10:17:32 +0000100 Options.store(Opts, "ParameterThreshold", ParameterThreshold);
Roman Lebedeva1cee292017-06-09 14:22:10 +0000101 Options.store(Opts, "NestingThreshold", NestingThreshold);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000102}
103
104void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000105 Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this);
Benjamin Kramer6e195422014-09-15 12:48:25 +0000106}
107
108void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
109 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
110
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000111 FunctionASTVisitor Visitor;
Roman Lebedeva1cee292017-06-09 14:22:10 +0000112 Visitor.Info.NestingThreshold = NestingThreshold;
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000113 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
114 auto &FI = Visitor.Info;
115
116 if (FI.Statements == 0)
117 return;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000118
119 // Count the lines including whitespace and comments. Really simple.
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000120 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());
Benjamin Kramer6e195422014-09-15 12:48:25 +0000125 }
126 }
127
Alexander Kornienko91086442017-03-01 10:17:32 +0000128 unsigned ActualNumberParameters = Func->getNumParams();
129
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000130 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
Alexander Kornienko91086442017-03-01 10:17:32 +0000131 FI.Branches > BranchThreshold ||
Roman Lebedeva1cee292017-06-09 14:22:10 +0000132 ActualNumberParameters > ParameterThreshold ||
133 !FI.NestingThresholders.empty()) {
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000134 diag(Func->getLocation(),
135 "function %0 exceeds recommended size/complexity thresholds")
136 << Func;
Benjamin Kramer6e195422014-09-15 12:48:25 +0000137 }
138
Samuel Benzaquen7663d3b2016-05-25 16:19:23 +0000139 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 }
Alexander Kornienko91086442017-03-01 10:17:32 +0000156
157 if (ActualNumberParameters > ParameterThreshold) {
158 diag(Func->getLocation(), "%0 parameters (threshold %1)",
159 DiagnosticIDs::Note)
160 << ActualNumberParameters << ParameterThreshold;
161 }
Roman Lebedeva1cee292017-06-09 14:22:10 +0000162
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 }
Benjamin Kramer6e195422014-09-15 12:48:25 +0000168}
169
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000170} // namespace readability
Benjamin Kramer6e195422014-09-15 12:48:25 +0000171} // namespace tidy
172} // namespace clang