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