brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.8 KiB · 2f0949c Raw
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