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 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 return true; 52 } 53 54 bool TraverseDecl(Decl *Node) { 55 TrackedParent.push_back(false); 56 Base::TraverseDecl(Node); 57 TrackedParent.pop_back(); 58 return true; 59 } 60 61 struct FunctionInfo { 62 FunctionInfo() : Lines(0), Statements(0), Branches(0) {} 63 unsigned Lines; 64 unsigned Statements; 65 unsigned Branches; 66 }; 67 FunctionInfo Info; 68 std::vector<bool> TrackedParent; 69 }; 70 71 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) 72 : ClangTidyCheck(Name, Context), 73 LineThreshold(Options.get("LineThreshold", -1U)), 74 StatementThreshold(Options.get("StatementThreshold", 800U)), 75 BranchThreshold(Options.get("BranchThreshold", -1U)), 76 ParameterThreshold(Options.get("ParameterThreshold", 6)) {} 77 78 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 79 Options.store(Opts, "LineThreshold", LineThreshold); 80 Options.store(Opts, "StatementThreshold", StatementThreshold); 81 Options.store(Opts, "BranchThreshold", BranchThreshold); 82 Options.store(Opts, "ParameterThreshold", ParameterThreshold); 83 } 84 85 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { 86 Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this); 87 } 88 89 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { 90 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); 91 92 FunctionASTVisitor Visitor; 93 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func)); 94 auto &FI = Visitor.Info; 95 96 if (FI.Statements == 0) 97 return; 98 99 // Count the lines including whitespace and comments. Really simple. 100 if (const Stmt *Body = Func->getBody()) { 101 SourceManager *SM = Result.SourceManager; 102 if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) { 103 FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) - 104 SM->getSpellingLineNumber(Body->getLocStart()); 105 } 106 } 107 108 unsigned ActualNumberParameters = Func->getNumParams(); 109 110 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || 111 FI.Branches > BranchThreshold || 112 ActualNumberParameters > ParameterThreshold) { 113 diag(Func->getLocation(), 114 "function %0 exceeds recommended size/complexity thresholds") 115 << Func; 116 } 117 118 if (FI.Lines > LineThreshold) { 119 diag(Func->getLocation(), 120 "%0 lines including whitespace and comments (threshold %1)", 121 DiagnosticIDs::Note) 122 << FI.Lines << LineThreshold; 123 } 124 125 if (FI.Statements > StatementThreshold) { 126 diag(Func->getLocation(), "%0 statements (threshold %1)", 127 DiagnosticIDs::Note) 128 << FI.Statements << StatementThreshold; 129 } 130 131 if (FI.Branches > BranchThreshold) { 132 diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note) 133 << FI.Branches << BranchThreshold; 134 } 135 136 if (ActualNumberParameters > ParameterThreshold) { 137 diag(Func->getLocation(), "%0 parameters (threshold %1)", 138 DiagnosticIDs::Note) 139 << ActualNumberParameters << ParameterThreshold; 140 } 141 } 142 143 } // namespace readability 144 } // namespace tidy 145 } // namespace clang 146