433 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 "ImplicitBoolConversionCheck.h"10#include "../utils/FixItHintUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "clang/Lex/Lexer.h"15#include "clang/Tooling/FixIt.h"16#include <queue>17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::readability {21 22namespace {23 24AST_MATCHER(Stmt, isMacroExpansion) {25 const SourceManager &SM = Finder->getASTContext().getSourceManager();26 const SourceLocation Loc = Node.getBeginLoc();27 return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);28}29 30AST_MATCHER(Stmt, isC23) { return Finder->getASTContext().getLangOpts().C23; }31 32// Preserve same name as AST_MATCHER(isNULLMacroExpansion)33// NOLINTNEXTLINE(llvm-prefer-static-over-anonymous-namespace)34bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {35 const SourceManager &SM = Context.getSourceManager();36 const LangOptions &LO = Context.getLangOpts();37 const SourceLocation Loc = Statement->getBeginLoc();38 return SM.isMacroBodyExpansion(Loc) &&39 Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";40}41 42AST_MATCHER(Stmt, isNULLMacroExpansion) {43 return isNULLMacroExpansion(&Node, Finder->getASTContext());44}45 46} // namespace47 48static StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,49 QualType Type,50 ASTContext &Context) {51 switch (CastExprKind) {52 case CK_IntegralToBoolean:53 return Type->isUnsignedIntegerType() ? "0u" : "0";54 55 case CK_FloatingToBoolean:56 return ASTContext::hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0";57 58 case CK_PointerToBoolean:59 case CK_MemberPointerToBoolean: // Fall-through on purpose.60 return (Context.getLangOpts().CPlusPlus11 || Context.getLangOpts().C23)61 ? "nullptr"62 : "0";63 64 default:65 llvm_unreachable("Unexpected cast kind");66 }67}68 69static bool isUnaryLogicalNotOperator(const Stmt *Statement) {70 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);71 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;72}73 74static void fixGenericExprCastToBool(DiagnosticBuilder &Diag,75 const ImplicitCastExpr *Cast,76 const Stmt *Parent, ASTContext &Context,77 bool UseUpperCaseLiteralSuffix) {78 // In case of expressions like (! integer), we should remove the redundant not79 // operator and use inverted comparison (integer == 0).80 const bool InvertComparison =81 Parent != nullptr && isUnaryLogicalNotOperator(Parent);82 if (InvertComparison) {83 const SourceLocation ParentStartLoc = Parent->getBeginLoc();84 const SourceLocation ParentEndLoc =85 cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();86 Diag << FixItHint::CreateRemoval(87 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));88 89 Parent = Context.getParents(*Parent)[0].get<Stmt>();90 }91 92 const Expr *SubExpr = Cast->getSubExpr();93 94 const bool NeedInnerParens =95 utils::fixit::areParensNeededForStatement(*SubExpr->IgnoreImpCasts());96 const bool NeedOuterParens =97 Parent != nullptr && utils::fixit::areParensNeededForStatement(*Parent);98 99 std::string StartLocInsertion;100 101 if (NeedOuterParens) {102 StartLocInsertion += "(";103 }104 if (NeedInnerParens) {105 StartLocInsertion += "(";106 }107 108 if (!StartLocInsertion.empty()) {109 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);110 }111 112 std::string EndLocInsertion;113 114 if (NeedInnerParens) {115 EndLocInsertion += ")";116 }117 118 if (InvertComparison) {119 EndLocInsertion += " == ";120 } else {121 EndLocInsertion += " != ";122 }123 124 const StringRef ZeroLiteral = getZeroLiteralToCompareWithForType(125 Cast->getCastKind(), SubExpr->getType(), Context);126 127 if (UseUpperCaseLiteralSuffix)128 EndLocInsertion += ZeroLiteral.upper();129 else130 EndLocInsertion += ZeroLiteral;131 132 if (NeedOuterParens) {133 EndLocInsertion += ")";134 }135 136 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(137 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());138 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);139}140 141static StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,142 ASTContext &Context) {143 if (isNULLMacroExpansion(Expression, Context)) {144 return "false";145 }146 147 if (const auto *IntLit =148 dyn_cast<IntegerLiteral>(Expression->IgnoreParens())) {149 return (IntLit->getValue() == 0) ? "false" : "true";150 }151 152 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {153 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();154 FloatLitAbsValue.clearSign();155 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";156 }157 158 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {159 return (CharLit->getValue() == 0) ? "false" : "true";160 }161 162 if (isa<StringLiteral>(Expression->IgnoreCasts())) {163 return "true";164 }165 166 return {};167}168 169static bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {170 const SourceRange PrefixRange(Loc.getLocWithOffset(-1), Loc);171 const StringRef SpaceBeforeStmtStr = Lexer::getSourceText(172 CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(),173 Context.getLangOpts(), nullptr);174 if (SpaceBeforeStmtStr.empty())175 return true;176 177 const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");178 return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());179}180 181static void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,182 const ImplicitCastExpr *Cast,183 ASTContext &Context,184 StringRef OtherType) {185 if (!Context.getLangOpts().CPlusPlus) {186 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(),187 (Twine("(") + OtherType + ")").str());188 return;189 }190 191 const Expr *SubExpr = Cast->getSubExpr();192 const bool NeedParens = !isa<ParenExpr>(SubExpr->IgnoreImplicit());193 const bool NeedSpace = needsSpacePrefix(Cast->getBeginLoc(), Context);194 195 Diag << FixItHint::CreateInsertion(196 Cast->getBeginLoc(), (Twine() + (NeedSpace ? " " : "") + "static_cast<" +197 OtherType + ">" + (NeedParens ? "(" : ""))198 .str());199 200 if (NeedParens) {201 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(202 Cast->getEndLoc(), 0, Context.getSourceManager(),203 Context.getLangOpts());204 205 Diag << FixItHint::CreateInsertion(EndLoc, ")");206 }207}208 209static StringRef210getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,211 QualType DestType, ASTContext &Context) {212 // Prior to C++11, false literal could be implicitly converted to pointer.213 if (!Context.getLangOpts().CPlusPlus11 &&214 (DestType->isPointerType() || DestType->isMemberPointerType()) &&215 BoolLiteral->getValue() == false) {216 return "0";217 }218 219 if (DestType->isFloatingType()) {220 if (ASTContext::hasSameType(DestType, Context.FloatTy)) {221 return BoolLiteral->getValue() ? "1.0f" : "0.0f";222 }223 return BoolLiteral->getValue() ? "1.0" : "0.0";224 }225 226 if (DestType->isUnsignedIntegerType()) {227 return BoolLiteral->getValue() ? "1u" : "0u";228 }229 return BoolLiteral->getValue() ? "1" : "0";230}231 232static bool isCastAllowedInCondition(const ImplicitCastExpr *Cast,233 ASTContext &Context) {234 std::queue<const Stmt *> Q;235 Q.push(Cast);236 237 const TraversalKindScope RAII(Context, TK_AsIs);238 239 while (!Q.empty()) {240 for (const auto &N : Context.getParents(*Q.front())) {241 const Stmt *S = N.get<Stmt>();242 if (!S)243 return false;244 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||245 isa<WhileStmt>(S) || isa<DoStmt>(S) ||246 isa<BinaryConditionalOperator>(S))247 return true;248 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||249 isUnaryLogicalNotOperator(S) ||250 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {251 Q.push(S);252 } else {253 return false;254 }255 }256 Q.pop();257 }258 return false;259}260 261ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(262 StringRef Name, ClangTidyContext *Context)263 : ClangTidyCheck(Name, Context),264 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),265 AllowPointerConditions(Options.get("AllowPointerConditions", false)),266 UseUpperCaseLiteralSuffix(267 Options.get("UseUpperCaseLiteralSuffix", false)) {}268 269void ImplicitBoolConversionCheck::storeOptions(270 ClangTidyOptions::OptionMap &Opts) {271 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);272 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);273 Options.store(Opts, "UseUpperCaseLiteralSuffix", UseUpperCaseLiteralSuffix);274}275 276void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {277 auto ExceptionCases =278 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),279 has(ignoringImplicit(280 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),281 hasParent(explicitCastExpr()),282 expr(hasType(qualType().bind("type")),283 hasParent(initListExpr(hasParent(explicitCastExpr(284 hasType(qualType(equalsBoundNode("type"))))))))));285 auto ImplicitCastFromBool = implicitCastExpr(286 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),287 // Prior to C++11 cast from bool literal to pointer was allowed.288 allOf(anyOf(hasCastKind(CK_NullToPointer),289 hasCastKind(CK_NullToMemberPointer)),290 hasSourceExpression(cxxBoolLiteral()))),291 hasSourceExpression(expr(hasType(booleanType()))));292 auto BoolXor =293 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),294 hasRHS(ImplicitCastFromBool));295 auto ComparisonInCall = allOf(296 hasParent(callExpr()),297 hasSourceExpression(binaryOperator(hasAnyOperatorName("==", "!="))));298 299 auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf(300 isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));301 302 Finder->addMatcher(303 traverse(TK_AsIs,304 implicitCastExpr(305 anyOf(hasCastKind(CK_IntegralToBoolean),306 hasCastKind(CK_FloatingToBoolean),307 hasCastKind(CK_PointerToBoolean),308 hasCastKind(CK_MemberPointerToBoolean)),309 // Exclude cases of C23 comparison result.310 unless(allOf(isC23(),311 hasSourceExpression(ignoringParens(312 binaryOperator(hasAnyOperatorName(313 ">", ">=", "==", "!=", "<", "<=")))))),314 // Exclude case of using if or while statements with variable315 // declaration, e.g.:316 // if (int var = functionCall()) {}317 unless(hasParent(318 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),319 // Exclude cases common to implicit cast to and from bool.320 unless(ExceptionCases), unless(has(BoolXor)),321 // Exclude C23 cases common to implicit cast to bool.322 unless(ComparisonInCall),323 // Retrieve also parent statement, to check if we need324 // additional parens in replacement.325 optionally(hasParent(stmt().bind("parentStmt"))),326 unless(isInTemplateInstantiation()),327 unless(IsInCompilerGeneratedFunction))328 .bind("implicitCastToBool")),329 this);330 331 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),332 hasLHS(ImplicitCastFromBool),333 hasRHS(ImplicitCastFromBool));334 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),335 hasLHS(expr(hasType(booleanType()))));336 auto BitfieldAssignment = binaryOperator(337 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));338 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(339 withInitializer(equalsBoundNode("implicitCastFromBool")),340 forField(hasBitWidth(1)))));341 Finder->addMatcher(342 traverse(343 TK_AsIs,344 implicitCastExpr(345 ImplicitCastFromBool, unless(ExceptionCases),346 // Exclude comparisons of bools, as they are always cast to347 // integers in such context:348 // bool_expr_a == bool_expr_b349 // bool_expr_a != bool_expr_b350 unless(hasParent(351 binaryOperator(anyOf(BoolComparison, BoolXor,352 BoolOpAssignment, BitfieldAssignment)))),353 implicitCastExpr().bind("implicitCastFromBool"),354 unless(hasParent(BitfieldConstruct)),355 // Check also for nested casts, for example: bool -> int -> float.356 optionally(357 hasParent(implicitCastExpr().bind("furtherImplicitCast"))),358 unless(isInTemplateInstantiation()),359 unless(IsInCompilerGeneratedFunction))),360 this);361}362 363void ImplicitBoolConversionCheck::check(364 const MatchFinder::MatchResult &Result) {365 if (const auto *CastToBool =366 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {367 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");368 handleCastToBool(CastToBool, Parent, *Result.Context);369 return;370 }371 372 if (const auto *CastFromBool =373 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {374 const auto *NextImplicitCast =375 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");376 handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);377 }378}379 380void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,381 const Stmt *Parent,382 ASTContext &Context) {383 if (AllowPointerConditions &&384 (Cast->getCastKind() == CK_PointerToBoolean ||385 Cast->getCastKind() == CK_MemberPointerToBoolean) &&386 isCastAllowedInCondition(Cast, Context)) {387 return;388 }389 390 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&391 isCastAllowedInCondition(Cast, Context)) {392 return;393 }394 395 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> 'bool'")396 << Cast->getSubExpr()->getType();397 398 const StringRef EquivalentLiteral =399 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);400 if (!EquivalentLiteral.empty()) {401 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);402 } else {403 fixGenericExprCastToBool(Diag, Cast, Parent, Context,404 UseUpperCaseLiteralSuffix);405 }406}407 408void ImplicitBoolConversionCheck::handleCastFromBool(409 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,410 ASTContext &Context) {411 const QualType DestType =412 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();413 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion 'bool' -> %0")414 << DestType;415 416 if (const auto *BoolLiteral =417 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr()->IgnoreParens())) {418 const auto EquivalentForBoolLiteral =419 getEquivalentForBoolLiteral(BoolLiteral, DestType, Context);420 if (UseUpperCaseLiteralSuffix)421 Diag << tooling::fixit::createReplacement(422 *Cast, EquivalentForBoolLiteral.upper());423 else424 Diag << tooling::fixit::createReplacement(*Cast,425 EquivalentForBoolLiteral);426 427 } else {428 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());429 }430}431 432} // namespace clang::tidy::readability433