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 LLVM_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 52 return true; 53 } 54 55 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 69 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 { 77 unsigned Lines = 0; 78 unsigned Statements = 0; 79 unsigned Branches = 0; 80 unsigned NestingThreshold = 0; 81 std::vector<SourceLocation> NestingThresholders; 82 }; 83 FunctionInfo Info; 84 std::vector<bool> TrackedParent; 85 unsigned CurrentNestingLevel = 0; 86 }; 87 88 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) 89 : ClangTidyCheck(Name, Context), 90 LineThreshold(Options.get("LineThreshold", -1U)), 91 StatementThreshold(Options.get("StatementThreshold", 800U)), 92 BranchThreshold(Options.get("BranchThreshold", -1U)), 93 ParameterThreshold(Options.get("ParameterThreshold", -1U)), 94 NestingThreshold(Options.get("NestingThreshold", -1U)) {} 95 96 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 97 Options.store(Opts, "LineThreshold", LineThreshold); 98 Options.store(Opts, "StatementThreshold", StatementThreshold); 99 Options.store(Opts, "BranchThreshold", BranchThreshold); 100 Options.store(Opts, "ParameterThreshold", ParameterThreshold); 101 Options.store(Opts, "NestingThreshold", NestingThreshold); 102 } 103 104 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { 105 Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this); 106 } 107 108 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { 109 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); 110 111 FunctionASTVisitor Visitor; 112 Visitor.Info.NestingThreshold = NestingThreshold; 113 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func)); 114 auto &FI = Visitor.Info; 115 116 if (FI.Statements == 0) 117 return; 118 119 // Count the lines including whitespace and comments. Really simple. 120 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()); 125 } 126 } 127 128 unsigned ActualNumberParameters = Func->getNumParams(); 129 130 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || 131 FI.Branches > BranchThreshold || 132 ActualNumberParameters > ParameterThreshold || 133 !FI.NestingThresholders.empty()) { 134 diag(Func->getLocation(), 135 "function %0 exceeds recommended size/complexity thresholds") 136 << Func; 137 } 138 139 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 } 156 157 if (ActualNumberParameters > ParameterThreshold) { 158 diag(Func->getLocation(), "%0 parameters (threshold %1)", 159 DiagnosticIDs::Note) 160 << ActualNumberParameters << ParameterThreshold; 161 } 162 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 } 168 } 169 170 } // namespace readability 171 } // namespace tidy 172 } // namespace clang 173