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