//===--- StandaloneEmptyCheck.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 "StandaloneEmptyCheck.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" namespace clang::tidy::bugprone { using ast_matchers::BoundNodes; using ast_matchers::callee; using ast_matchers::callExpr; using ast_matchers::classTemplateDecl; using ast_matchers::cxxMemberCallExpr; using ast_matchers::cxxMethodDecl; using ast_matchers::expr; using ast_matchers::functionDecl; using ast_matchers::hasAncestor; using ast_matchers::hasName; using ast_matchers::hasParent; using ast_matchers::ignoringImplicit; using ast_matchers::ignoringParenImpCasts; using ast_matchers::MatchFinder; using ast_matchers::optionally; using ast_matchers::returns; using ast_matchers::stmt; using ast_matchers::stmtExpr; using ast_matchers::unless; using ast_matchers::voidType; const Expr *getCondition(const BoundNodes &Nodes, const StringRef NodeId) { const auto *If = Nodes.getNodeAs(NodeId); if (If != nullptr) return If->getCond(); const auto *For = Nodes.getNodeAs(NodeId); if (For != nullptr) return For->getCond(); const auto *While = Nodes.getNodeAs(NodeId); if (While != nullptr) return While->getCond(); const auto *Do = Nodes.getNodeAs(NodeId); if (Do != nullptr) return Do->getCond(); const auto *Switch = Nodes.getNodeAs(NodeId); if (Switch != nullptr) return Switch->getCond(); return nullptr; } void StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { // Ignore empty calls in a template definition which fall under callExpr // non-member matcher even if they are methods. const auto NonMemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts( callExpr( hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr")))) .bind("parent")), unless(hasAncestor(classTemplateDecl())), callee(functionDecl(hasName("empty"), unless(returns(voidType()))))) .bind("empty")))); const auto MemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts(cxxMemberCallExpr( hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr")))) .bind("parent")), callee(cxxMethodDecl(hasName("empty"), unless(returns(voidType())))))))) .bind("empty"); Finder->addMatcher(MemberMatcher, this); Finder->addMatcher(NonMemberMatcher, this); } void StandaloneEmptyCheck::check(const MatchFinder::MatchResult &Result) { // Skip if the parent node is Expr. if (Result.Nodes.getNodeAs("parent")) return; const auto PParentStmtExpr = Result.Nodes.getNodeAs("stexpr"); const auto ParentCompStmt = Result.Nodes.getNodeAs("parent"); const auto *ParentCond = getCondition(Result.Nodes, "parent"); const auto *ParentReturnStmt = Result.Nodes.getNodeAs("parent"); if (const auto *MemberCall = Result.Nodes.getNodeAs("empty")) { // Skip if it's a condition of the parent statement. if (ParentCond == MemberCall->getExprStmt()) return; // Skip if it's the last statement in the GNU extension // statement expression. if (PParentStmtExpr && ParentCompStmt && ParentCompStmt->body_back() == MemberCall->getExprStmt()) return; // Skip if it's a return statement if (ParentReturnStmt) return; SourceLocation MemberLoc = MemberCall->getBeginLoc(); SourceLocation ReplacementLoc = MemberCall->getExprLoc(); SourceRange ReplacementRange = SourceRange(ReplacementLoc, ReplacementLoc); ASTContext &Context = MemberCall->getRecordDecl()->getASTContext(); DeclarationName Name = Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear")); auto Candidates = MemberCall->getRecordDecl()->lookupDependentName( Name, [](const NamedDecl *ND) { return isa(ND) && llvm::cast(ND)->getMinRequiredArguments() == 0 && !llvm::cast(ND)->isConst(); }); bool HasClear = !Candidates.empty(); if (HasClear) { const auto *Clear = llvm::cast(Candidates.at(0)); QualType RangeType = MemberCall->getImplicitObjectArgument()->getType(); bool QualifierIncompatible = (!Clear->isVolatile() && RangeType.isVolatileQualified()) || RangeType.isConstQualified(); if (!QualifierIncompatible) { diag(MemberLoc, "ignoring the result of 'empty()'; did you mean 'clear()'? ") << FixItHint::CreateReplacement(ReplacementRange, "clear"); return; } } diag(MemberLoc, "ignoring the result of 'empty()'"); } else if (const auto *NonMemberCall = Result.Nodes.getNodeAs("empty")) { if (ParentCond == NonMemberCall->getExprStmt()) return; if (PParentStmtExpr && ParentCompStmt && ParentCompStmt->body_back() == NonMemberCall->getExprStmt()) return; if (ParentReturnStmt) return; if (NonMemberCall->getNumArgs() != 1) return; SourceLocation NonMemberLoc = NonMemberCall->getExprLoc(); SourceLocation NonMemberEndLoc = NonMemberCall->getEndLoc(); const Expr *Arg = NonMemberCall->getArg(0); CXXRecordDecl *ArgRecordDecl = Arg->getType()->getAsCXXRecordDecl(); if (ArgRecordDecl == nullptr) return; ASTContext &Context = ArgRecordDecl->getASTContext(); DeclarationName Name = Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear")); auto Candidates = ArgRecordDecl->lookupDependentName(Name, [](const NamedDecl *ND) { return isa(ND) && llvm::cast(ND)->getMinRequiredArguments() == 0 && !llvm::cast(ND)->isConst(); }); bool HasClear = !Candidates.empty(); if (HasClear) { const auto *Clear = llvm::cast(Candidates.at(0)); bool QualifierIncompatible = (!Clear->isVolatile() && Arg->getType().isVolatileQualified()) || Arg->getType().isConstQualified(); if (!QualifierIncompatible) { std::string ReplacementText = std::string(Lexer::getSourceText( CharSourceRange::getTokenRange(Arg->getSourceRange()), *Result.SourceManager, getLangOpts())) + ".clear()"; SourceRange ReplacementRange = SourceRange(NonMemberLoc, NonMemberEndLoc); diag(NonMemberLoc, "ignoring the result of '%0'; did you mean 'clear()'?") << llvm::dyn_cast(NonMemberCall->getCalleeDecl()) ->getQualifiedNameAsString() << FixItHint::CreateReplacement(ReplacementRange, ReplacementText); return; } } diag(NonMemberLoc, "ignoring the result of '%0'") << llvm::dyn_cast(NonMemberCall->getCalleeDecl()) ->getQualifiedNameAsString(); } } } // namespace clang::tidy::bugprone