brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.0 KiB · 056ae4b Raw
218 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 "StandaloneEmptyCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/Decl.h"12#include "clang/AST/DeclBase.h"13#include "clang/AST/DeclCXX.h"14#include "clang/AST/Expr.h"15#include "clang/AST/ExprCXX.h"16#include "clang/AST/Stmt.h"17#include "clang/AST/Type.h"18#include "clang/ASTMatchers/ASTMatchFinder.h"19#include "clang/ASTMatchers/ASTMatchers.h"20#include "clang/Basic/Diagnostic.h"21#include "clang/Basic/SourceLocation.h"22#include "clang/Lex/Lexer.h"23#include "clang/Sema/HeuristicResolver.h"24#include "llvm/Support/Casting.h"25 26namespace clang::tidy::bugprone {27 28using ast_matchers::BoundNodes;29using ast_matchers::callee;30using ast_matchers::callExpr;31using ast_matchers::classTemplateDecl;32using ast_matchers::cxxMemberCallExpr;33using ast_matchers::cxxMethodDecl;34using ast_matchers::expr;35using ast_matchers::functionDecl;36using ast_matchers::hasAncestor;37using ast_matchers::hasName;38using ast_matchers::hasParent;39using ast_matchers::ignoringImplicit;40using ast_matchers::ignoringParenImpCasts;41using ast_matchers::MatchFinder;42using ast_matchers::optionally;43using ast_matchers::returns;44using ast_matchers::stmt;45using ast_matchers::stmtExpr;46using ast_matchers::unless;47using ast_matchers::voidType;48 49static const Expr *getCondition(const BoundNodes &Nodes,50                                const StringRef NodeId) {51  const auto *If = Nodes.getNodeAs<IfStmt>(NodeId);52  if (If != nullptr)53    return If->getCond();54 55  const auto *For = Nodes.getNodeAs<ForStmt>(NodeId);56  if (For != nullptr)57    return For->getCond();58 59  const auto *While = Nodes.getNodeAs<WhileStmt>(NodeId);60  if (While != nullptr)61    return While->getCond();62 63  const auto *Do = Nodes.getNodeAs<DoStmt>(NodeId);64  if (Do != nullptr)65    return Do->getCond();66 67  const auto *Switch = Nodes.getNodeAs<SwitchStmt>(NodeId);68  if (Switch != nullptr)69    return Switch->getCond();70 71  return nullptr;72}73 74void StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {75  // Ignore empty calls in a template definition which fall under callExpr76  // non-member matcher even if they are methods.77  const auto NonMemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts(78      callExpr(79          hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))80                        .bind("parent")),81          unless(hasAncestor(classTemplateDecl())),82          callee(functionDecl(hasName("empty"), unless(returns(voidType())))))83          .bind("empty"))));84  const auto MemberMatcher =85      expr(ignoringImplicit(ignoringParenImpCasts(cxxMemberCallExpr(86               hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))87                             .bind("parent")),88               callee(cxxMethodDecl(hasName("empty"),89                                    unless(returns(voidType()))))))))90          .bind("empty");91 92  Finder->addMatcher(MemberMatcher, this);93  Finder->addMatcher(NonMemberMatcher, this);94}95 96void StandaloneEmptyCheck::check(const MatchFinder::MatchResult &Result) {97  // Skip if the parent node is Expr.98  if (Result.Nodes.getNodeAs<Expr>("parent"))99    return;100 101  const auto *PParentStmtExpr = Result.Nodes.getNodeAs<Expr>("stexpr");102  const auto *ParentCompStmt = Result.Nodes.getNodeAs<CompoundStmt>("parent");103  const auto *ParentCond = getCondition(Result.Nodes, "parent");104  const auto *ParentReturnStmt = Result.Nodes.getNodeAs<ReturnStmt>("parent");105 106  if (const auto *MemberCall =107          Result.Nodes.getNodeAs<CXXMemberCallExpr>("empty")) {108    // Skip if it's a condition of the parent statement.109    if (ParentCond == MemberCall->getExprStmt())110      return;111    // Skip if it's the last statement in the GNU extension112    // statement expression.113    if (PParentStmtExpr && ParentCompStmt &&114        ParentCompStmt->body_back() == MemberCall->getExprStmt())115      return;116    // Skip if it's a return statement117    if (ParentReturnStmt)118      return;119 120    const SourceLocation MemberLoc = MemberCall->getBeginLoc();121    const SourceLocation ReplacementLoc = MemberCall->getExprLoc();122    const SourceRange ReplacementRange =123        SourceRange(ReplacementLoc, ReplacementLoc);124 125    ASTContext &Context = MemberCall->getRecordDecl()->getASTContext();126    const DeclarationName Name =127        Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear"));128 129    auto Candidates = HeuristicResolver(Context).lookupDependentName(130        MemberCall->getRecordDecl(), Name, [](const NamedDecl *ND) {131          return isa<CXXMethodDecl>(ND) &&132                 llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==133                     0 &&134                 !llvm::cast<CXXMethodDecl>(ND)->isConst();135        });136 137    const bool HasClear = !Candidates.empty();138    if (HasClear) {139      const auto *Clear = llvm::cast<CXXMethodDecl>(Candidates.at(0));140      const QualType RangeType =141          MemberCall->getImplicitObjectArgument()->getType();142      const bool QualifierIncompatible =143          (!Clear->isVolatile() && RangeType.isVolatileQualified()) ||144          RangeType.isConstQualified();145      if (!QualifierIncompatible) {146        diag(MemberLoc,147             "ignoring the result of 'empty()'; did you mean 'clear()'? ")148            << FixItHint::CreateReplacement(ReplacementRange, "clear");149        return;150      }151    }152 153    diag(MemberLoc, "ignoring the result of 'empty()'");154 155  } else if (const auto *NonMemberCall =156                 Result.Nodes.getNodeAs<CallExpr>("empty")) {157    if (ParentCond == NonMemberCall->getExprStmt())158      return;159    if (PParentStmtExpr && ParentCompStmt &&160        ParentCompStmt->body_back() == NonMemberCall->getExprStmt())161      return;162    if (ParentReturnStmt)163      return;164    if (NonMemberCall->getNumArgs() != 1)165      return;166 167    const SourceLocation NonMemberLoc = NonMemberCall->getExprLoc();168    const SourceLocation NonMemberEndLoc = NonMemberCall->getEndLoc();169 170    const Expr *Arg = NonMemberCall->getArg(0);171    CXXRecordDecl *ArgRecordDecl = Arg->getType()->getAsCXXRecordDecl();172    if (ArgRecordDecl == nullptr)173      return;174 175    ASTContext &Context = ArgRecordDecl->getASTContext();176    const DeclarationName Name =177        Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear"));178 179    auto Candidates = HeuristicResolver(Context).lookupDependentName(180        ArgRecordDecl, Name, [](const NamedDecl *ND) {181          return isa<CXXMethodDecl>(ND) &&182                 llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==183                     0 &&184                 !llvm::cast<CXXMethodDecl>(ND)->isConst();185        });186 187    const bool HasClear = !Candidates.empty();188 189    if (HasClear) {190      const auto *Clear = llvm::cast<CXXMethodDecl>(Candidates.at(0));191      const bool QualifierIncompatible =192          (!Clear->isVolatile() && Arg->getType().isVolatileQualified()) ||193          Arg->getType().isConstQualified();194      if (!QualifierIncompatible) {195        const std::string ReplacementText =196            std::string(Lexer::getSourceText(197                CharSourceRange::getTokenRange(Arg->getSourceRange()),198                *Result.SourceManager, getLangOpts())) +199            ".clear()";200        const SourceRange ReplacementRange =201            SourceRange(NonMemberLoc, NonMemberEndLoc);202        diag(NonMemberLoc,203             "ignoring the result of '%0'; did you mean 'clear()'?")204            << llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())205                   ->getQualifiedNameAsString()206            << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);207        return;208      }209    }210 211    diag(NonMemberLoc, "ignoring the result of '%0'")212        << llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())213               ->getQualifiedNameAsString();214  }215}216 217} // namespace clang::tidy::bugprone218