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