blob: 67d8e3693fb568dd6f5031f7f1094d9acc25a80c [file] [log] [blame]
//===--- 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::cxxMemberCallExpr;
using ast_matchers::cxxMethodDecl;
using ast_matchers::expr;
using ast_matchers::functionDecl;
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<IfStmt>(NodeId);
if (If != nullptr)
return If->getCond();
const auto *For = Nodes.getNodeAs<ForStmt>(NodeId);
if (For != nullptr)
return For->getCond();
const auto *While = Nodes.getNodeAs<WhileStmt>(NodeId);
if (While != nullptr)
return While->getCond();
const auto *Do = Nodes.getNodeAs<DoStmt>(NodeId);
if (Do != nullptr)
return Do->getCond();
const auto *Switch = Nodes.getNodeAs<SwitchStmt>(NodeId);
if (Switch != nullptr)
return Switch->getCond();
return nullptr;
}
void StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
const auto NonMemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts(
callExpr(
hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
.bind("parent")),
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<Expr>("parent"))
return;
const auto PParentStmtExpr = Result.Nodes.getNodeAs<Expr>("stexpr");
const auto ParentCompStmt = Result.Nodes.getNodeAs<CompoundStmt>("parent");
const auto *ParentCond = getCondition(Result.Nodes, "parent");
const auto *ParentReturnStmt = Result.Nodes.getNodeAs<ReturnStmt>("parent");
if (const auto *MemberCall =
Result.Nodes.getNodeAs<CXXMemberCallExpr>("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<CXXMethodDecl>(ND) &&
llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==
0 &&
!llvm::cast<CXXMethodDecl>(ND)->isConst();
});
bool HasClear = !Candidates.empty();
if (HasClear) {
const CXXMethodDecl *Clear = llvm::cast<CXXMethodDecl>(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<CallExpr>("empty")) {
if (ParentCond == NonMemberCall->getExprStmt())
return;
if (PParentStmtExpr && ParentCompStmt &&
ParentCompStmt->body_back() == NonMemberCall->getExprStmt())
return;
if (ParentReturnStmt)
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<CXXMethodDecl>(ND) &&
llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==
0 &&
!llvm::cast<CXXMethodDecl>(ND)->isConst();
});
bool HasClear = !Candidates.empty();
if (HasClear) {
const CXXMethodDecl *Clear = llvm::cast<CXXMethodDecl>(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<NamedDecl>(NonMemberCall->getCalleeDecl())
->getQualifiedNameAsString()
<< FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
return;
}
}
diag(NonMemberLoc, "ignoring the result of '%0'")
<< llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())
->getQualifiedNameAsString();
}
}
} // namespace clang::tidy::bugprone