llvm-project/clang-tools-extra/clang-tidy/readability/ImplicitBoolCastCheck.cpp

433 lines
16 KiB
C++

//===--- ImplicitBoolCastCheck.cpp - clang-tidy----------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ImplicitBoolCastCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace readability {
namespace {
AST_MATCHER(Stmt, isMacroExpansion) {
SourceManager &SM = Finder->getASTContext().getSourceManager();
SourceLocation Loc = Node.getLocStart();
return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
}
bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
SourceManager &SM = Context.getSourceManager();
const LangOptions &LO = Context.getLangOpts();
SourceLocation Loc = Statement->getLocStart();
return SM.isMacroBodyExpansion(Loc) &&
clang::Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
}
AST_MATCHER(Stmt, isNULLMacroExpansion) {
return isNULLMacroExpansion(&Node, Finder->getASTContext());
}
ast_matchers::internal::Matcher<Expr> createExceptionCasesMatcher() {
return expr(anyOf(hasParent(explicitCastExpr()),
allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
isInTemplateInstantiation(),
hasAncestor(functionTemplateDecl())));
}
StatementMatcher createImplicitCastFromBoolMatcher() {
return implicitCastExpr(
unless(createExceptionCasesMatcher()),
anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
// Prior to C++11 cast from bool literal to pointer was allowed.
allOf(anyOf(hasCastKind(CK_NullToPointer),
hasCastKind(CK_NullToMemberPointer)),
hasSourceExpression(cxxBoolLiteral()))),
hasSourceExpression(expr(hasType(qualType(booleanType())))));
}
StringRef
getZeroLiteralToCompareWithForGivenType(CastKind CastExpressionKind,
QualType CastSubExpressionType,
ASTContext &Context) {
switch (CastExpressionKind) {
case CK_IntegralToBoolean:
return CastSubExpressionType->isUnsignedIntegerType() ? "0u" : "0";
case CK_FloatingToBoolean:
return Context.hasSameType(CastSubExpressionType, Context.FloatTy) ? "0.0f"
: "0.0";
case CK_PointerToBoolean:
case CK_MemberPointerToBoolean: // Fall-through on purpose.
return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0";
default:
llvm_unreachable("Unexpected cast kind");
}
}
bool isUnaryLogicalNotOperator(const Stmt *Statement) {
const auto *UnaryOperatorExpression =
llvm::dyn_cast<UnaryOperator>(Statement);
return UnaryOperatorExpression != nullptr &&
UnaryOperatorExpression->getOpcode() == UO_LNot;
}
bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) {
switch (OperatorKind) {
case OO_New:
case OO_Delete: // Fall-through on purpose.
case OO_Array_New:
case OO_Array_Delete:
case OO_ArrowStar:
case OO_Arrow:
case OO_Call:
case OO_Subscript:
return false;
default:
return true;
}
}
bool areParensNeededForStatement(const Stmt *Statement) {
if (const auto *OverloadedOperatorCall =
llvm::dyn_cast<CXXOperatorCallExpr>(Statement)) {
return areParensNeededForOverloadedOperator(
OverloadedOperatorCall->getOperator());
}
return llvm::isa<BinaryOperator>(Statement) ||
llvm::isa<UnaryOperator>(Statement);
}
void addFixItHintsForGenericExpressionCastToBool(
DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression,
const Stmt *ParentStatement, ASTContext &Context) {
// In case of expressions like (! integer), we should remove the redundant not
// operator and use inverted comparison (integer == 0).
bool InvertComparison =
ParentStatement != nullptr && isUnaryLogicalNotOperator(ParentStatement);
if (InvertComparison) {
SourceLocation ParentStartLoc = ParentStatement->getLocStart();
SourceLocation ParentEndLoc =
llvm::cast<UnaryOperator>(ParentStatement)->getSubExpr()->getLocStart();
Diagnostic.AddFixItHint(FixItHint::CreateRemoval(
CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc)));
auto FurtherParents = Context.getParents(*ParentStatement);
ParentStatement = FurtherParents[0].get<Stmt>();
}
const Expr *SubExpression = CastExpression->getSubExpr();
bool NeedInnerParens = areParensNeededForStatement(SubExpression);
bool NeedOuterParens = ParentStatement != nullptr &&
areParensNeededForStatement(ParentStatement);
std::string StartLocInsertion;
if (NeedOuterParens) {
StartLocInsertion += "(";
}
if (NeedInnerParens) {
StartLocInsertion += "(";
}
if (!StartLocInsertion.empty()) {
SourceLocation StartLoc = CastExpression->getLocStart();
Diagnostic.AddFixItHint(
FixItHint::CreateInsertion(StartLoc, StartLocInsertion));
}
std::string EndLocInsertion;
if (NeedInnerParens) {
EndLocInsertion += ")";
}
if (InvertComparison) {
EndLocInsertion += " == ";
} else {
EndLocInsertion += " != ";
}
EndLocInsertion += getZeroLiteralToCompareWithForGivenType(
CastExpression->getCastKind(), SubExpression->getType(), Context);
if (NeedOuterParens) {
EndLocInsertion += ")";
}
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
CastExpression->getLocEnd(), 0, Context.getSourceManager(),
Context.getLangOpts());
Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, EndLocInsertion));
}
StringRef getEquivalentBoolLiteralForExpression(const Expr *Expression,
ASTContext &Context) {
if (isNULLMacroExpansion(Expression, Context)) {
return "false";
}
if (const auto *IntLit = llvm::dyn_cast<IntegerLiteral>(Expression)) {
return (IntLit->getValue() == 0) ? "false" : "true";
}
if (const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(Expression)) {
llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
FloatLitAbsValue.clearSign();
return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
}
if (const auto *CharLit = llvm::dyn_cast<CharacterLiteral>(Expression)) {
return (CharLit->getValue() == 0) ? "false" : "true";
}
if (llvm::isa<StringLiteral>(Expression->IgnoreCasts())) {
return "true";
}
return StringRef();
}
void addFixItHintsForLiteralCastToBool(DiagnosticBuilder &Diagnostic,
const ImplicitCastExpr *CastExpression,
StringRef EquivalentLiteralExpression) {
SourceLocation StartLoc = CastExpression->getLocStart();
SourceLocation EndLoc = CastExpression->getLocEnd();
Diagnostic.AddFixItHint(FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(StartLoc, EndLoc),
EquivalentLiteralExpression));
}
void addFixItHintsForGenericExpressionCastFromBool(
DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression,
ASTContext &Context, StringRef OtherType) {
const Expr *SubExpression = CastExpression->getSubExpr();
bool NeedParens = !llvm::isa<ParenExpr>(SubExpression);
std::string StartLocInsertion = "static_cast<";
StartLocInsertion += OtherType.str();
StartLocInsertion += ">";
if (NeedParens) {
StartLocInsertion += "(";
}
SourceLocation StartLoc = CastExpression->getLocStart();
Diagnostic.AddFixItHint(
FixItHint::CreateInsertion(StartLoc, StartLocInsertion));
if (NeedParens) {
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
CastExpression->getLocEnd(), 0, Context.getSourceManager(),
Context.getLangOpts());
Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, ")"));
}
}
StringRef getEquivalentLiteralForBoolLiteral(
const CXXBoolLiteralExpr *BoolLiteralExpression, QualType DestinationType,
ASTContext &Context) {
// Prior to C++11, false literal could be implicitly converted to pointer.
if (!Context.getLangOpts().CPlusPlus11 &&
(DestinationType->isPointerType() ||
DestinationType->isMemberPointerType()) &&
BoolLiteralExpression->getValue() == false) {
return "0";
}
if (DestinationType->isFloatingType()) {
if (BoolLiteralExpression->getValue() == true) {
return Context.hasSameType(DestinationType, Context.FloatTy) ? "1.0f"
: "1.0";
}
return Context.hasSameType(DestinationType, Context.FloatTy) ? "0.0f"
: "0.0";
}
if (BoolLiteralExpression->getValue() == true) {
return DestinationType->isUnsignedIntegerType() ? "1u" : "1";
}
return DestinationType->isUnsignedIntegerType() ? "0u" : "0";
}
void addFixItHintsForLiteralCastFromBool(DiagnosticBuilder &Diagnostic,
const ImplicitCastExpr *CastExpression,
ASTContext &Context,
QualType DestinationType) {
SourceLocation StartLoc = CastExpression->getLocStart();
SourceLocation EndLoc = CastExpression->getLocEnd();
const auto *BoolLiteralExpression =
llvm::dyn_cast<CXXBoolLiteralExpr>(CastExpression->getSubExpr());
Diagnostic.AddFixItHint(FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(StartLoc, EndLoc),
getEquivalentLiteralForBoolLiteral(BoolLiteralExpression, DestinationType,
Context)));
}
StatementMatcher createConditionalExpressionMatcher() {
return stmt(anyOf(ifStmt(), conditionalOperator(),
parenExpr(hasParent(conditionalOperator()))));
}
bool isAllowedConditionalCast(const ImplicitCastExpr *CastExpression,
ASTContext &Context) {
auto AllowedConditionalMatcher = stmt(hasParent(stmt(
anyOf(createConditionalExpressionMatcher(),
unaryOperator(hasOperatorName("!"),
hasParent(createConditionalExpressionMatcher()))))));
auto MatchResult = match(AllowedConditionalMatcher, *CastExpression, Context);
return !MatchResult.empty();
}
} // anonymous namespace
ImplicitBoolCastCheck::ImplicitBoolCastCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AllowConditionalIntegerCasts(
Options.get("AllowConditionalIntegerCasts", false)),
AllowConditionalPointerCasts(
Options.get("AllowConditionalPointerCasts", false)) {}
void ImplicitBoolCastCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AllowConditionalIntegerCasts",
AllowConditionalIntegerCasts);
Options.store(Opts, "AllowConditionalPointerCasts",
AllowConditionalPointerCasts);
}
void ImplicitBoolCastCheck::registerMatchers(MatchFinder *Finder) {
// This check doesn't make much sense if we run it on language without
// built-in bool support.
if (!getLangOpts().Bool) {
return;
}
Finder->addMatcher(
implicitCastExpr(
// Exclude cases common to implicit cast to and from bool.
unless(createExceptionCasesMatcher()),
// Exclude case of using if or while statements with variable
// declaration, e.g.:
// if (int var = functionCall()) {}
unless(
hasParent(stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
anyOf(hasCastKind(CK_IntegralToBoolean),
hasCastKind(CK_FloatingToBoolean),
hasCastKind(CK_PointerToBoolean),
hasCastKind(CK_MemberPointerToBoolean)),
// Retrive also parent statement, to check if we need additional
// parens in replacement.
anyOf(hasParent(stmt().bind("parentStmt")), anything()))
.bind("implicitCastToBool"),
this);
Finder->addMatcher(
implicitCastExpr(
createImplicitCastFromBoolMatcher(),
// Exclude comparisons of bools, as they are always cast to integers
// in such context:
// bool_expr_a == bool_expr_b
// bool_expr_a != bool_expr_b
unless(hasParent(binaryOperator(
anyOf(hasOperatorName("=="), hasOperatorName("!=")),
hasLHS(createImplicitCastFromBoolMatcher()),
hasRHS(createImplicitCastFromBoolMatcher())))),
// Check also for nested casts, for example: bool -> int -> float.
anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
anything()))
.bind("implicitCastFromBool"),
this);
}
void ImplicitBoolCastCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *CastToBool =
Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
const auto *ParentStatement = Result.Nodes.getNodeAs<Stmt>("parentStmt");
return handleCastToBool(CastToBool, ParentStatement, *Result.Context);
}
if (const auto *CastFromBool =
Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
const auto *FurtherImplicitCastExpression =
Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
return handleCastFromBool(CastFromBool, FurtherImplicitCastExpression,
*Result.Context);
}
}
void ImplicitBoolCastCheck::handleCastToBool(
const ImplicitCastExpr *CastExpression, const Stmt *ParentStatement,
ASTContext &Context) {
if (AllowConditionalPointerCasts &&
(CastExpression->getCastKind() == CK_PointerToBoolean ||
CastExpression->getCastKind() == CK_MemberPointerToBoolean) &&
isAllowedConditionalCast(CastExpression, Context)) {
return;
}
if (AllowConditionalIntegerCasts &&
CastExpression->getCastKind() == CK_IntegralToBoolean &&
isAllowedConditionalCast(CastExpression, Context)) {
return;
}
std::string OtherType = CastExpression->getSubExpr()->getType().getAsString();
DiagnosticBuilder Diagnostic =
diag(CastExpression->getLocStart(), "implicit cast '%0' -> bool")
<< OtherType;
StringRef EquivalentLiteralExpression = getEquivalentBoolLiteralForExpression(
CastExpression->getSubExpr(), Context);
if (!EquivalentLiteralExpression.empty()) {
addFixItHintsForLiteralCastToBool(Diagnostic, CastExpression,
EquivalentLiteralExpression);
} else {
addFixItHintsForGenericExpressionCastToBool(Diagnostic, CastExpression,
ParentStatement, Context);
}
}
void ImplicitBoolCastCheck::handleCastFromBool(
const ImplicitCastExpr *CastExpression,
const ImplicitCastExpr *FurtherImplicitCastExpression,
ASTContext &Context) {
QualType DestinationType = (FurtherImplicitCastExpression != nullptr)
? FurtherImplicitCastExpression->getType()
: CastExpression->getType();
std::string DestinationTypeString = DestinationType.getAsString();
DiagnosticBuilder Diagnostic =
diag(CastExpression->getLocStart(), "implicit cast bool -> '%0'")
<< DestinationTypeString;
if (llvm::isa<CXXBoolLiteralExpr>(CastExpression->getSubExpr())) {
addFixItHintsForLiteralCastFromBool(Diagnostic, CastExpression, Context,
DestinationType);
} else {
addFixItHintsForGenericExpressionCastFromBool(
Diagnostic, CastExpression, Context, DestinationTypeString);
}
}
} // namespace readability
} // namespace tidy
} // namespace clang