| //===-- 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" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace 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; |
| LLVM_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; |
| std::vector<bool> 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 readability |
| } // namespace tidy |
| } // namespace clang |