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