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