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 225 TraversalKindScope RAII(Context, TK_AsIs); 226 227 while (!Q.empty()) { 228 for (const auto &N : Context.getParents(*Q.front())) { 229 const Stmt *S = N.get<Stmt>(); 230 if (!S) 231 return false; 232 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) || 233 isa<WhileStmt>(S) || isa<BinaryConditionalOperator>(S)) 234 return true; 235 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) || 236 isUnaryLogicalNotOperator(S) || 237 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) { 238 Q.push(S); 239 } else { 240 return false; 241 } 242 } 243 Q.pop(); 244 } 245 return false; 246 } 247 248 } // anonymous namespace 249 250 ImplicitBoolConversionCheck::ImplicitBoolConversionCheck( 251 StringRef Name, ClangTidyContext *Context) 252 : ClangTidyCheck(Name, Context), 253 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)), 254 AllowPointerConditions(Options.get("AllowPointerConditions", false)) {} 255 256 void ImplicitBoolConversionCheck::storeOptions( 257 ClangTidyOptions::OptionMap &Opts) { 258 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions); 259 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions); 260 } 261 262 void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) { 263 auto ExceptionCases = 264 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())), 265 has(ignoringImplicit( 266 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))), 267 hasParent(explicitCastExpr()))); 268 auto ImplicitCastFromBool = implicitCastExpr( 269 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating), 270 // Prior to C++11 cast from bool literal to pointer was allowed. 271 allOf(anyOf(hasCastKind(CK_NullToPointer), 272 hasCastKind(CK_NullToMemberPointer)), 273 hasSourceExpression(cxxBoolLiteral()))), 274 hasSourceExpression(expr(hasType(booleanType()))), 275 unless(ExceptionCases)); 276 auto BoolXor = 277 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool), 278 hasRHS(ImplicitCastFromBool)); 279 Finder->addMatcher( 280 traverse(TK_AsIs, 281 implicitCastExpr( 282 anyOf(hasCastKind(CK_IntegralToBoolean), 283 hasCastKind(CK_FloatingToBoolean), 284 hasCastKind(CK_PointerToBoolean), 285 hasCastKind(CK_MemberPointerToBoolean)), 286 // Exclude case of using if or while statements with variable 287 // declaration, e.g.: 288 // if (int var = functionCall()) {} 289 unless(hasParent( 290 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))), 291 // Exclude cases common to implicit cast to and from bool. 292 unless(ExceptionCases), unless(has(BoolXor)), 293 // Retrieve also parent statement, to check if we need 294 // additional parens in replacement. 295 anyOf(hasParent(stmt().bind("parentStmt")), anything()), 296 unless(isInTemplateInstantiation()), 297 unless(hasAncestor(functionTemplateDecl()))) 298 .bind("implicitCastToBool")), 299 this); 300 301 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="), 302 hasLHS(ImplicitCastFromBool), 303 hasRHS(ImplicitCastFromBool)); 304 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="), 305 hasLHS(expr(hasType(booleanType())))); 306 auto BitfieldAssignment = binaryOperator( 307 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))); 308 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer( 309 withInitializer(equalsBoundNode("implicitCastFromBool")), 310 forField(hasBitWidth(1))))); 311 Finder->addMatcher( 312 traverse( 313 TK_AsIs, 314 implicitCastExpr( 315 ImplicitCastFromBool, 316 // Exclude comparisons of bools, as they are always cast to 317 // integers in such context: 318 // bool_expr_a == bool_expr_b 319 // bool_expr_a != bool_expr_b 320 unless(hasParent( 321 binaryOperator(anyOf(BoolComparison, BoolXor, 322 BoolOpAssignment, BitfieldAssignment)))), 323 implicitCastExpr().bind("implicitCastFromBool"), 324 unless(hasParent(BitfieldConstruct)), 325 // Check also for nested casts, for example: bool -> int -> float. 326 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), 327 anything()), 328 unless(isInTemplateInstantiation()), 329 unless(hasAncestor(functionTemplateDecl())))), 330 this); 331 } 332 333 void ImplicitBoolConversionCheck::check( 334 const MatchFinder::MatchResult &Result) { 335 336 if (const auto *CastToBool = 337 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) { 338 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt"); 339 return handleCastToBool(CastToBool, Parent, *Result.Context); 340 } 341 342 if (const auto *CastFromBool = 343 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) { 344 const auto *NextImplicitCast = 345 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast"); 346 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context); 347 } 348 } 349 350 void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast, 351 const Stmt *Parent, 352 ASTContext &Context) { 353 if (AllowPointerConditions && 354 (Cast->getCastKind() == CK_PointerToBoolean || 355 Cast->getCastKind() == CK_MemberPointerToBoolean) && 356 isCastAllowedInCondition(Cast, Context)) { 357 return; 358 } 359 360 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean && 361 isCastAllowedInCondition(Cast, Context)) { 362 return; 363 } 364 365 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> bool") 366 << Cast->getSubExpr()->getType(); 367 368 StringRef EquivalentLiteral = 369 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context); 370 if (!EquivalentLiteral.empty()) { 371 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral); 372 } else { 373 fixGenericExprCastToBool(Diag, Cast, Parent, Context); 374 } 375 } 376 377 void ImplicitBoolConversionCheck::handleCastFromBool( 378 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast, 379 ASTContext &Context) { 380 QualType DestType = 381 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType(); 382 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion bool -> %0") 383 << DestType; 384 385 if (const auto *BoolLiteral = 386 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr())) { 387 Diag << tooling::fixit::createReplacement( 388 *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context)); 389 } else { 390 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString()); 391 } 392 } 393 394 } // namespace readability 395 } // namespace tidy 396 } // namespace clang 397