1 //===-- FunctionSizeCheck.cpp - clang-tidy --------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "FunctionSizeCheck.h" 10 #include "clang/AST/RecursiveASTVisitor.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "llvm/ADT/BitVector.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 VisitVarDecl(VarDecl *VD) { 26 // Do not count function params. 27 // Do not count decomposition declarations (C++17's structured bindings). 28 if (StructNesting == 0 && 29 !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD))) 30 ++Info.Variables; 31 return true; 32 } 33 bool VisitBindingDecl(BindingDecl *BD) { 34 // Do count each of the bindings (in the decomposition declaration). 35 if (StructNesting == 0) 36 ++Info.Variables; 37 return true; 38 } 39 40 bool TraverseStmt(Stmt *Node) { 41 if (!Node) 42 return Base::TraverseStmt(Node); 43 44 if (TrackedParent.back() && !isa<CompoundStmt>(Node)) 45 ++Info.Statements; 46 47 switch (Node->getStmtClass()) { 48 case Stmt::IfStmtClass: 49 case Stmt::WhileStmtClass: 50 case Stmt::DoStmtClass: 51 case Stmt::CXXForRangeStmtClass: 52 case Stmt::ForStmtClass: 53 case Stmt::SwitchStmtClass: 54 ++Info.Branches; 55 LLVM_FALLTHROUGH; 56 case Stmt::CompoundStmtClass: 57 TrackedParent.push_back(true); 58 break; 59 default: 60 TrackedParent.push_back(false); 61 break; 62 } 63 64 Base::TraverseStmt(Node); 65 66 TrackedParent.pop_back(); 67 68 return true; 69 } 70 71 bool TraverseCompoundStmt(CompoundStmt *Node) { 72 // If this new compound statement is located in a compound statement, which 73 // is already nested NestingThreshold levels deep, record the start location 74 // of this new compound statement. 75 if (CurrentNestingLevel == Info.NestingThreshold) 76 Info.NestingThresholders.push_back(Node->getBeginLoc()); 77 78 ++CurrentNestingLevel; 79 Base::TraverseCompoundStmt(Node); 80 --CurrentNestingLevel; 81 82 return true; 83 } 84 85 bool TraverseDecl(Decl *Node) { 86 TrackedParent.push_back(false); 87 Base::TraverseDecl(Node); 88 TrackedParent.pop_back(); 89 return true; 90 } 91 92 bool TraverseLambdaExpr(LambdaExpr *Node) { 93 ++StructNesting; 94 Base::TraverseLambdaExpr(Node); 95 --StructNesting; 96 return true; 97 } 98 99 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) { 100 ++StructNesting; 101 Base::TraverseCXXRecordDecl(Node); 102 --StructNesting; 103 return true; 104 } 105 106 bool TraverseStmtExpr(StmtExpr *SE) { 107 ++StructNesting; 108 Base::TraverseStmtExpr(SE); 109 --StructNesting; 110 return true; 111 } 112 113 struct FunctionInfo { 114 unsigned Lines = 0; 115 unsigned Statements = 0; 116 unsigned Branches = 0; 117 unsigned NestingThreshold = 0; 118 unsigned Variables = 0; 119 std::vector<SourceLocation> NestingThresholders; 120 }; 121 FunctionInfo Info; 122 llvm::BitVector TrackedParent; 123 unsigned StructNesting = 0; 124 unsigned CurrentNestingLevel = 0; 125 }; 126 127 } // namespace 128 129 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) 130 : ClangTidyCheck(Name, Context), 131 LineThreshold(Options.get("LineThreshold", -1U)), 132 StatementThreshold(Options.get("StatementThreshold", 800U)), 133 BranchThreshold(Options.get("BranchThreshold", -1U)), 134 ParameterThreshold(Options.get("ParameterThreshold", -1U)), 135 NestingThreshold(Options.get("NestingThreshold", -1U)), 136 VariableThreshold(Options.get("VariableThreshold", -1U)) {} 137 138 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 139 Options.store(Opts, "LineThreshold", LineThreshold); 140 Options.store(Opts, "StatementThreshold", StatementThreshold); 141 Options.store(Opts, "BranchThreshold", BranchThreshold); 142 Options.store(Opts, "ParameterThreshold", ParameterThreshold); 143 Options.store(Opts, "NestingThreshold", NestingThreshold); 144 Options.store(Opts, "VariableThreshold", VariableThreshold); 145 } 146 147 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { 148 // Lambdas ignored - historically considered part of enclosing function. 149 // FIXME: include them instead? Top-level lambdas are currently never counted. 150 Finder->addMatcher(functionDecl(unless(isInstantiated()), 151 unless(cxxMethodDecl(ofClass(isLambda())))) 152 .bind("func"), 153 this); 154 } 155 156 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { 157 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); 158 159 FunctionASTVisitor Visitor; 160 Visitor.Info.NestingThreshold = NestingThreshold; 161 Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func)); 162 auto &FI = Visitor.Info; 163 164 if (FI.Statements == 0) 165 return; 166 167 // Count the lines including whitespace and comments. Really simple. 168 if (const Stmt *Body = Func->getBody()) { 169 SourceManager *SM = Result.SourceManager; 170 if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) { 171 FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) - 172 SM->getSpellingLineNumber(Body->getBeginLoc()); 173 } 174 } 175 176 unsigned ActualNumberParameters = Func->getNumParams(); 177 178 if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || 179 FI.Branches > BranchThreshold || 180 ActualNumberParameters > ParameterThreshold || 181 !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) { 182 diag(Func->getLocation(), 183 "function %0 exceeds recommended size/complexity thresholds") 184 << Func; 185 } 186 187 if (FI.Lines > LineThreshold) { 188 diag(Func->getLocation(), 189 "%0 lines including whitespace and comments (threshold %1)", 190 DiagnosticIDs::Note) 191 << FI.Lines << LineThreshold; 192 } 193 194 if (FI.Statements > StatementThreshold) { 195 diag(Func->getLocation(), "%0 statements (threshold %1)", 196 DiagnosticIDs::Note) 197 << FI.Statements << StatementThreshold; 198 } 199 200 if (FI.Branches > BranchThreshold) { 201 diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note) 202 << FI.Branches << BranchThreshold; 203 } 204 205 if (ActualNumberParameters > ParameterThreshold) { 206 diag(Func->getLocation(), "%0 parameters (threshold %1)", 207 DiagnosticIDs::Note) 208 << ActualNumberParameters << ParameterThreshold; 209 } 210 211 for (const auto &CSPos : FI.NestingThresholders) { 212 diag(CSPos, "nesting level %0 starts here (threshold %1)", 213 DiagnosticIDs::Note) 214 << NestingThreshold + 1 << NestingThreshold; 215 } 216 217 if (FI.Variables > VariableThreshold) { 218 diag(Func->getLocation(), "%0 variables (threshold %1)", 219 DiagnosticIDs::Note) 220 << FI.Variables << VariableThreshold; 221 } 222 } 223 224 } // namespace readability 225 } // namespace tidy 226 } // namespace clang 227