blob: 201c8d23dd4dbb7ebfa8ad4d7e08a28fcce31e77 [file] [log] [blame]
//===--- PreferIsaOrDynCastInConditionalsCheck.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 "PreferIsaOrDynCastInConditionalsCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace ast_matchers {
AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); }
} // namespace ast_matchers
namespace tidy {
namespace llvm_check {
void PreferIsaOrDynCastInConditionalsCheck::registerMatchers(
MatchFinder *Finder) {
auto Condition = hasCondition(implicitCastExpr(has(
callExpr(
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
anyOf(callee(namedDecl(hasName("cast"))),
callee(namedDecl(hasName("dyn_cast")).bind("dyn_cast")))))
.bind("call"))));
auto Any = anyOf(
has(declStmt(containsDeclaration(
0,
varDecl(hasInitializer(
callExpr(allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
callee(namedDecl(hasName("cast")))))
.bind("assign")))))),
Condition);
auto CallExpression =
callExpr(
allOf(
unless(isMacroID()), unless(cxxMemberCallExpr()),
allOf(callee(namedDecl(hasAnyName("isa", "cast", "cast_or_null",
"dyn_cast", "dyn_cast_or_null"))
.bind("func")),
hasArgument(
0,
mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg")))))
.bind("rhs");
Finder->addMatcher(
traverse(TK_AsIs,
stmt(anyOf(
ifStmt(Any), whileStmt(Any), doStmt(Condition),
binaryOperator(
allOf(unless(isExpansionInFileMatching(
"llvm/include/llvm/Support/Casting.h")),
hasOperatorName("&&"),
hasLHS(implicitCastExpr().bind("lhs")),
hasRHS(anyOf(implicitCastExpr(has(CallExpression)),
CallExpression))))
.bind("and")))),
this);
}
void PreferIsaOrDynCastInConditionalsCheck::check(
const MatchFinder::MatchResult &Result) {
if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("assign")) {
SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
SourceLocation EndLoc =
StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
diag(MatchedDecl->getBeginLoc(),
"cast<> in conditional will assert rather than return a null pointer")
<< FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc),
"dyn_cast");
} else if (const auto *MatchedDecl =
Result.Nodes.getNodeAs<CallExpr>("call")) {
SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
SourceLocation EndLoc =
StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
StringRef Message =
"cast<> in conditional will assert rather than return a null pointer";
if (Result.Nodes.getNodeAs<NamedDecl>("dyn_cast"))
Message = "return value from dyn_cast<> not used";
diag(MatchedDecl->getBeginLoc(), Message)
<< FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa");
} else if (const auto *MatchedDecl =
Result.Nodes.getNodeAs<BinaryOperator>("and")) {
const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs");
const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs");
const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
const auto *Func = Result.Nodes.getNodeAs<NamedDecl>("func");
assert(LHS && "LHS is null");
assert(RHS && "RHS is null");
assert(Arg && "Arg is null");
assert(Func && "Func is null");
StringRef LHSString(Lexer::getSourceText(
CharSourceRange::getTokenRange(LHS->getSourceRange()),
*Result.SourceManager, getLangOpts()));
StringRef ArgString(Lexer::getSourceText(
CharSourceRange::getTokenRange(Arg->getSourceRange()),
*Result.SourceManager, getLangOpts()));
if (ArgString != LHSString)
return;
StringRef RHSString(Lexer::getSourceText(
CharSourceRange::getTokenRange(RHS->getSourceRange()),
*Result.SourceManager, getLangOpts()));
std::string Replacement("isa_and_nonnull");
Replacement += RHSString.substr(Func->getName().size());
diag(MatchedDecl->getBeginLoc(),
"isa_and_nonnull<> is preferred over an explicit test for null "
"followed by calling isa<>")
<< FixItHint::CreateReplacement(SourceRange(MatchedDecl->getBeginLoc(),
MatchedDecl->getEndLoc()),
Replacement);
}
}
} // namespace llvm_check
} // namespace tidy
} // namespace clang