123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- //===-- FunctionSizeCheck.cpp - clang-tidy --------------------------------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- #include "FunctionSizeCheck.h"
- #include "clang/AST/RecursiveASTVisitor.h"
- #include "clang/ASTMatchers/ASTMatchFinder.h"
- #include "llvm/ADT/BitVector.h"
- using namespace clang::ast_matchers;
- namespace clang::tidy::readability {
- namespace {
- class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
- using Base = RecursiveASTVisitor<FunctionASTVisitor>;
- public:
- bool VisitVarDecl(VarDecl *VD) {
- // Do not count function params.
- // Do not count decomposition declarations (C++17's structured bindings).
- if (StructNesting == 0 &&
- !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD)))
- ++Info.Variables;
- return true;
- }
- bool VisitBindingDecl(BindingDecl *BD) {
- // Do count each of the bindings (in the decomposition declaration).
- if (StructNesting == 0)
- ++Info.Variables;
- return true;
- }
- bool TraverseStmt(Stmt *Node) {
- if (!Node)
- return Base::TraverseStmt(Node);
- if (TrackedParent.back() && !isa<CompoundStmt>(Node))
- ++Info.Statements;
- switch (Node->getStmtClass()) {
- case Stmt::IfStmtClass:
- case Stmt::WhileStmtClass:
- case Stmt::DoStmtClass:
- case Stmt::CXXForRangeStmtClass:
- case Stmt::ForStmtClass:
- case Stmt::SwitchStmtClass:
- ++Info.Branches;
- [[fallthrough]];
- case Stmt::CompoundStmtClass:
- TrackedParent.push_back(true);
- break;
- default:
- TrackedParent.push_back(false);
- break;
- }
- Base::TraverseStmt(Node);
- TrackedParent.pop_back();
- return true;
- }
- bool TraverseCompoundStmt(CompoundStmt *Node) {
- // If this new compound statement is located in a compound statement, which
- // is already nested NestingThreshold levels deep, record the start location
- // of this new compound statement.
- if (CurrentNestingLevel == Info.NestingThreshold)
- Info.NestingThresholders.push_back(Node->getBeginLoc());
- ++CurrentNestingLevel;
- Base::TraverseCompoundStmt(Node);
- --CurrentNestingLevel;
- return true;
- }
- bool TraverseDecl(Decl *Node) {
- TrackedParent.push_back(false);
- Base::TraverseDecl(Node);
- TrackedParent.pop_back();
- return true;
- }
- bool TraverseLambdaExpr(LambdaExpr *Node) {
- ++StructNesting;
- Base::TraverseLambdaExpr(Node);
- --StructNesting;
- return true;
- }
- bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
- ++StructNesting;
- Base::TraverseCXXRecordDecl(Node);
- --StructNesting;
- return true;
- }
- bool TraverseStmtExpr(StmtExpr *SE) {
- ++StructNesting;
- Base::TraverseStmtExpr(SE);
- --StructNesting;
- return true;
- }
- struct FunctionInfo {
- unsigned Lines = 0;
- unsigned Statements = 0;
- unsigned Branches = 0;
- unsigned NestingThreshold = 0;
- unsigned Variables = 0;
- std::vector<SourceLocation> NestingThresholders;
- };
- FunctionInfo Info;
- llvm::BitVector TrackedParent;
- unsigned StructNesting = 0;
- unsigned CurrentNestingLevel = 0;
- };
- } // namespace
- FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context),
- LineThreshold(Options.get("LineThreshold", -1U)),
- StatementThreshold(Options.get("StatementThreshold", 800U)),
- BranchThreshold(Options.get("BranchThreshold", -1U)),
- ParameterThreshold(Options.get("ParameterThreshold", -1U)),
- NestingThreshold(Options.get("NestingThreshold", -1U)),
- VariableThreshold(Options.get("VariableThreshold", -1U)) {}
- void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
- Options.store(Opts, "LineThreshold", LineThreshold);
- Options.store(Opts, "StatementThreshold", StatementThreshold);
- Options.store(Opts, "BranchThreshold", BranchThreshold);
- Options.store(Opts, "ParameterThreshold", ParameterThreshold);
- Options.store(Opts, "NestingThreshold", NestingThreshold);
- Options.store(Opts, "VariableThreshold", VariableThreshold);
- }
- void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
- // Lambdas ignored - historically considered part of enclosing function.
- // FIXME: include them instead? Top-level lambdas are currently never counted.
- Finder->addMatcher(functionDecl(unless(isInstantiated()),
- unless(cxxMethodDecl(ofClass(isLambda()))))
- .bind("func"),
- this);
- }
- void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
- FunctionASTVisitor Visitor;
- Visitor.Info.NestingThreshold = NestingThreshold;
- Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
- auto &FI = Visitor.Info;
- if (FI.Statements == 0)
- return;
- // Count the lines including whitespace and comments. Really simple.
- if (const Stmt *Body = Func->getBody()) {
- SourceManager *SM = Result.SourceManager;
- if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
- FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
- SM->getSpellingLineNumber(Body->getBeginLoc());
- }
- }
- unsigned ActualNumberParameters = Func->getNumParams();
- if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
- FI.Branches > BranchThreshold ||
- ActualNumberParameters > ParameterThreshold ||
- !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) {
- diag(Func->getLocation(),
- "function %0 exceeds recommended size/complexity thresholds")
- << Func;
- }
- if (FI.Lines > LineThreshold) {
- diag(Func->getLocation(),
- "%0 lines including whitespace and comments (threshold %1)",
- DiagnosticIDs::Note)
- << FI.Lines << LineThreshold;
- }
- if (FI.Statements > StatementThreshold) {
- diag(Func->getLocation(), "%0 statements (threshold %1)",
- DiagnosticIDs::Note)
- << FI.Statements << StatementThreshold;
- }
- if (FI.Branches > BranchThreshold) {
- diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
- << FI.Branches << BranchThreshold;
- }
- if (ActualNumberParameters > ParameterThreshold) {
- diag(Func->getLocation(), "%0 parameters (threshold %1)",
- DiagnosticIDs::Note)
- << ActualNumberParameters << ParameterThreshold;
- }
- for (const auto &CSPos : FI.NestingThresholders) {
- diag(CSPos, "nesting level %0 starts here (threshold %1)",
- DiagnosticIDs::Note)
- << NestingThreshold + 1 << NestingThreshold;
- }
- if (FI.Variables > VariableThreshold) {
- diag(Func->getLocation(), "%0 variables (threshold %1)",
- DiagnosticIDs::Note)
- << FI.Variables << VariableThreshold;
- }
- }
- } // namespace clang::tidy::readability
|