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