1 //===-- SimplifyBooleanExprCheck.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 "SimplifyBooleanExprCheck.h" 10 #include "SimplifyBooleanExprMatchers.h" 11 #include "clang/AST/RecursiveASTVisitor.h" 12 #include "clang/Lex/Lexer.h" 13 14 #include <string> 15 #include <utility> 16 17 using namespace clang::ast_matchers; 18 19 namespace clang { 20 namespace tidy { 21 namespace readability { 22 23 namespace { 24 25 StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) { 26 return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), 27 *Result.SourceManager, 28 Result.Context->getLangOpts()); 29 } 30 31 template <typename T> 32 StringRef getText(const MatchFinder::MatchResult &Result, T &Node) { 33 return getText(Result, Node.getSourceRange()); 34 } 35 36 } // namespace 37 38 static constexpr char ConditionThenStmtId[] = "if-bool-yields-then"; 39 static constexpr char ConditionElseStmtId[] = "if-bool-yields-else"; 40 static constexpr char TernaryId[] = "ternary-bool-yields-condition"; 41 static constexpr char TernaryNegatedId[] = "ternary-bool-yields-not-condition"; 42 static constexpr char IfReturnsBoolId[] = "if-return"; 43 static constexpr char IfReturnsNotBoolId[] = "if-not-return"; 44 static constexpr char ThenLiteralId[] = "then-literal"; 45 static constexpr char IfAssignVariableId[] = "if-assign-lvalue"; 46 static constexpr char IfAssignLocId[] = "if-assign-loc"; 47 static constexpr char IfAssignBoolId[] = "if-assign"; 48 static constexpr char IfAssignNotBoolId[] = "if-assign-not"; 49 static constexpr char IfAssignVarId[] = "if-assign-var"; 50 static constexpr char CompoundReturnId[] = "compound-return"; 51 static constexpr char CompoundIfId[] = "compound-if"; 52 static constexpr char CompoundBoolId[] = "compound-bool"; 53 static constexpr char CompoundNotBoolId[] = "compound-bool-not"; 54 static constexpr char CaseId[] = "case"; 55 static constexpr char CaseCompoundBoolId[] = "case-compound-bool"; 56 static constexpr char CaseCompoundNotBoolId[] = "case-compound-bool-not"; 57 static constexpr char DefaultId[] = "default"; 58 static constexpr char DefaultCompoundBoolId[] = "default-compound-bool"; 59 static constexpr char DefaultCompoundNotBoolId[] = "default-compound-bool-not"; 60 static constexpr char LabelId[] = "label"; 61 static constexpr char LabelCompoundBoolId[] = "label-compound-bool"; 62 static constexpr char LabelCompoundNotBoolId[] = "label-compound-bool-not"; 63 static constexpr char IfStmtId[] = "if"; 64 65 static constexpr char SimplifyOperatorDiagnostic[] = 66 "redundant boolean literal supplied to boolean operator"; 67 static constexpr char SimplifyConditionDiagnostic[] = 68 "redundant boolean literal in if statement condition"; 69 static constexpr char SimplifyConditionalReturnDiagnostic[] = 70 "redundant boolean literal in conditional return statement"; 71 72 static const Expr *getBoolLiteral(const MatchFinder::MatchResult &Result, 73 StringRef Id) { 74 if (const Expr *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id)) 75 return Literal->getBeginLoc().isMacroID() ? nullptr : Literal; 76 if (const auto *Negated = Result.Nodes.getNodeAs<UnaryOperator>(Id)) { 77 if (Negated->getOpcode() == UO_LNot && 78 isa<CXXBoolLiteralExpr>(Negated->getSubExpr())) 79 return Negated->getBeginLoc().isMacroID() ? nullptr : Negated; 80 } 81 return nullptr; 82 } 83 84 static internal::BindableMatcher<Stmt> literalOrNegatedBool(bool Value) { 85 return expr( 86 anyOf(cxxBoolLiteral(equals(Value)), 87 unaryOperator(hasUnaryOperand(cxxBoolLiteral(equals(!Value))), 88 hasOperatorName("!")))); 89 } 90 91 static internal::Matcher<Stmt> returnsBool(bool Value, 92 StringRef Id = "ignored") { 93 auto SimpleReturnsBool = returnStmt(has(literalOrNegatedBool(Value).bind(Id))) 94 .bind("returns-bool"); 95 return anyOf(SimpleReturnsBool, 96 compoundStmt(statementCountIs(1), has(SimpleReturnsBool))); 97 } 98 99 static bool needsParensAfterUnaryNegation(const Expr *E) { 100 E = E->IgnoreImpCasts(); 101 if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E)) 102 return true; 103 104 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E)) 105 return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && 106 Op->getOperator() != OO_Subscript; 107 108 return false; 109 } 110 111 static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = { 112 {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; 113 114 static StringRef negatedOperator(const BinaryOperator *BinOp) { 115 const BinaryOperatorKind Opcode = BinOp->getOpcode(); 116 for (auto NegatableOp : Opposites) { 117 if (Opcode == NegatableOp.first) 118 return BinOp->getOpcodeStr(NegatableOp.second); 119 if (Opcode == NegatableOp.second) 120 return BinOp->getOpcodeStr(NegatableOp.first); 121 } 122 return {}; 123 } 124 125 static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = { 126 {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"}, 127 {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}}; 128 129 static StringRef getOperatorName(OverloadedOperatorKind OpKind) { 130 for (auto Name : OperatorNames) { 131 if (Name.first == OpKind) 132 return Name.second; 133 } 134 135 return {}; 136 } 137 138 static std::pair<OverloadedOperatorKind, OverloadedOperatorKind> 139 OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual}, 140 {OO_Less, OO_GreaterEqual}, 141 {OO_Greater, OO_LessEqual}}; 142 143 static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) { 144 const OverloadedOperatorKind Opcode = OpCall->getOperator(); 145 for (auto NegatableOp : OppositeOverloads) { 146 if (Opcode == NegatableOp.first) 147 return getOperatorName(NegatableOp.second); 148 if (Opcode == NegatableOp.second) 149 return getOperatorName(NegatableOp.first); 150 } 151 return {}; 152 } 153 154 static std::string asBool(StringRef Text, bool NeedsStaticCast) { 155 if (NeedsStaticCast) 156 return ("static_cast<bool>(" + Text + ")").str(); 157 158 return std::string(Text); 159 } 160 161 static bool needsNullPtrComparison(const Expr *E) { 162 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) 163 return ImpCast->getCastKind() == CK_PointerToBoolean || 164 ImpCast->getCastKind() == CK_MemberPointerToBoolean; 165 166 return false; 167 } 168 169 static bool needsZeroComparison(const Expr *E) { 170 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) 171 return ImpCast->getCastKind() == CK_IntegralToBoolean; 172 173 return false; 174 } 175 176 static bool needsStaticCast(const Expr *E) { 177 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) { 178 if (ImpCast->getCastKind() == CK_UserDefinedConversion && 179 ImpCast->getSubExpr()->getType()->isBooleanType()) { 180 if (const auto *MemCall = 181 dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) { 182 if (const auto *MemDecl = 183 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) { 184 if (MemDecl->isExplicit()) 185 return true; 186 } 187 } 188 } 189 } 190 191 E = E->IgnoreImpCasts(); 192 return !E->getType()->isBooleanType(); 193 } 194 195 static std::string 196 compareExpressionToConstant(const MatchFinder::MatchResult &Result, 197 const Expr *E, bool Negated, const char *Constant) { 198 E = E->IgnoreImpCasts(); 199 const std::string ExprText = 200 (isa<BinaryOperator>(E) ? ("(" + getText(Result, *E) + ")") 201 : getText(Result, *E)) 202 .str(); 203 return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant; 204 } 205 206 static std::string 207 compareExpressionToNullPtr(const MatchFinder::MatchResult &Result, 208 const Expr *E, bool Negated) { 209 const char *NullPtr = 210 Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL"; 211 return compareExpressionToConstant(Result, E, Negated, NullPtr); 212 } 213 214 static std::string 215 compareExpressionToZero(const MatchFinder::MatchResult &Result, const Expr *E, 216 bool Negated) { 217 return compareExpressionToConstant(Result, E, Negated, "0"); 218 } 219 220 static std::string replacementExpression(const MatchFinder::MatchResult &Result, 221 bool Negated, const Expr *E) { 222 E = E->IgnoreParenBaseCasts(); 223 if (const auto *EC = dyn_cast<ExprWithCleanups>(E)) 224 E = EC->getSubExpr(); 225 226 const bool NeedsStaticCast = needsStaticCast(E); 227 if (Negated) { 228 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) { 229 if (UnOp->getOpcode() == UO_LNot) { 230 if (needsNullPtrComparison(UnOp->getSubExpr())) 231 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true); 232 233 if (needsZeroComparison(UnOp->getSubExpr())) 234 return compareExpressionToZero(Result, UnOp->getSubExpr(), true); 235 236 return replacementExpression(Result, false, UnOp->getSubExpr()); 237 } 238 } 239 240 if (needsNullPtrComparison(E)) 241 return compareExpressionToNullPtr(Result, E, false); 242 243 if (needsZeroComparison(E)) 244 return compareExpressionToZero(Result, E, false); 245 246 StringRef NegatedOperator; 247 const Expr *LHS = nullptr; 248 const Expr *RHS = nullptr; 249 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { 250 NegatedOperator = negatedOperator(BinOp); 251 LHS = BinOp->getLHS(); 252 RHS = BinOp->getRHS(); 253 } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) { 254 if (OpExpr->getNumArgs() == 2) { 255 NegatedOperator = negatedOperator(OpExpr); 256 LHS = OpExpr->getArg(0); 257 RHS = OpExpr->getArg(1); 258 } 259 } 260 if (!NegatedOperator.empty() && LHS && RHS) 261 return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " + 262 getText(Result, *RHS)) 263 .str(), 264 NeedsStaticCast)); 265 266 StringRef Text = getText(Result, *E); 267 if (!NeedsStaticCast && needsParensAfterUnaryNegation(E)) 268 return ("!(" + Text + ")").str(); 269 270 if (needsNullPtrComparison(E)) 271 return compareExpressionToNullPtr(Result, E, false); 272 273 if (needsZeroComparison(E)) 274 return compareExpressionToZero(Result, E, false); 275 276 return ("!" + asBool(Text, NeedsStaticCast)); 277 } 278 279 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) { 280 if (UnOp->getOpcode() == UO_LNot) { 281 if (needsNullPtrComparison(UnOp->getSubExpr())) 282 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false); 283 284 if (needsZeroComparison(UnOp->getSubExpr())) 285 return compareExpressionToZero(Result, UnOp->getSubExpr(), false); 286 } 287 } 288 289 if (needsNullPtrComparison(E)) 290 return compareExpressionToNullPtr(Result, E, true); 291 292 if (needsZeroComparison(E)) 293 return compareExpressionToZero(Result, E, true); 294 295 return asBool(getText(Result, *E), NeedsStaticCast); 296 } 297 298 static const Expr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) { 299 if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) { 300 if (Bool->getValue() == !Negated) 301 return Bool; 302 } 303 if (const auto *Unary = dyn_cast<UnaryOperator>(Ret->getRetValue())) { 304 if (Unary->getOpcode() == UO_LNot) { 305 if (const auto *Bool = 306 dyn_cast<CXXBoolLiteralExpr>(Unary->getSubExpr())) { 307 if (Bool->getValue() == Negated) 308 return Bool; 309 } 310 } 311 } 312 313 return nullptr; 314 } 315 316 static const Expr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) { 317 if (IfRet->getElse() != nullptr) 318 return nullptr; 319 320 if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen())) 321 return stmtReturnsBool(Ret, Negated); 322 323 if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) { 324 if (Compound->size() == 1) { 325 if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back())) 326 return stmtReturnsBool(CompoundRet, Negated); 327 } 328 } 329 330 return nullptr; 331 } 332 333 static bool containsDiscardedTokens(const MatchFinder::MatchResult &Result, 334 CharSourceRange CharRange) { 335 std::string ReplacementText = 336 Lexer::getSourceText(CharRange, *Result.SourceManager, 337 Result.Context->getLangOpts()) 338 .str(); 339 Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(), 340 ReplacementText.data(), ReplacementText.data(), 341 ReplacementText.data() + ReplacementText.size()); 342 Lex.SetCommentRetentionState(true); 343 344 Token Tok; 345 while (!Lex.LexFromRawLexer(Tok)) { 346 if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash)) 347 return true; 348 } 349 350 return false; 351 } 352 353 class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> { 354 public: 355 Visitor(SimplifyBooleanExprCheck *Check, 356 const MatchFinder::MatchResult &Result) 357 : Check(Check), Result(Result) {} 358 359 bool VisitBinaryOperator(const BinaryOperator *Op) const { 360 Check->reportBinOp(Result, Op); 361 return true; 362 } 363 364 private: 365 SimplifyBooleanExprCheck *Check; 366 const MatchFinder::MatchResult &Result; 367 }; 368 369 SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, 370 ClangTidyContext *Context) 371 : ClangTidyCheck(Name, Context), 372 ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)), 373 ChainedConditionalAssignment( 374 Options.get("ChainedConditionalAssignment", false)) {} 375 376 static bool containsBoolLiteral(const Expr *E) { 377 if (!E) 378 return false; 379 E = E->IgnoreParenImpCasts(); 380 if (isa<CXXBoolLiteralExpr>(E)) 381 return true; 382 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) 383 return containsBoolLiteral(BinOp->getLHS()) || 384 containsBoolLiteral(BinOp->getRHS()); 385 if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) 386 return containsBoolLiteral(UnaryOp->getSubExpr()); 387 return false; 388 } 389 390 void SimplifyBooleanExprCheck::reportBinOp( 391 const MatchFinder::MatchResult &Result, const BinaryOperator *Op) { 392 const auto *LHS = Op->getLHS()->IgnoreParenImpCasts(); 393 const auto *RHS = Op->getRHS()->IgnoreParenImpCasts(); 394 395 const CXXBoolLiteralExpr *Bool; 396 const Expr *Other; 397 if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr) 398 Other = RHS; 399 else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr) 400 Other = LHS; 401 else 402 return; 403 404 if (Bool->getBeginLoc().isMacroID()) 405 return; 406 407 // FIXME: why do we need this? 408 if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other)) 409 return; 410 411 bool BoolValue = Bool->getValue(); 412 413 auto ReplaceWithExpression = [this, &Result, LHS, RHS, 414 Bool](const Expr *ReplaceWith, bool Negated) { 415 std::string Replacement = 416 replacementExpression(Result, Negated, ReplaceWith); 417 SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc()); 418 issueDiag(Result, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range, 419 Replacement); 420 }; 421 422 switch (Op->getOpcode()) { 423 case BO_LAnd: 424 if (BoolValue) 425 // expr && true -> expr 426 ReplaceWithExpression(Other, /*Negated=*/false); 427 else 428 // expr && false -> false 429 ReplaceWithExpression(Bool, /*Negated=*/false); 430 break; 431 case BO_LOr: 432 if (BoolValue) 433 // expr || true -> true 434 ReplaceWithExpression(Bool, /*Negated=*/false); 435 else 436 // expr || false -> expr 437 ReplaceWithExpression(Other, /*Negated=*/false); 438 break; 439 case BO_EQ: 440 // expr == true -> expr, expr == false -> !expr 441 ReplaceWithExpression(Other, /*Negated=*/!BoolValue); 442 break; 443 case BO_NE: 444 // expr != true -> !expr, expr != false -> expr 445 ReplaceWithExpression(Other, /*Negated=*/BoolValue); 446 break; 447 default: 448 break; 449 } 450 } 451 452 void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder, 453 bool Value, 454 StringRef BooleanId) { 455 Finder->addMatcher( 456 ifStmt(hasCondition(literalOrNegatedBool(Value).bind(BooleanId))) 457 .bind(IfStmtId), 458 this); 459 } 460 461 void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, 462 bool Value, StringRef Id) { 463 Finder->addMatcher( 464 conditionalOperator(hasTrueExpression(literalOrNegatedBool(Value)), 465 hasFalseExpression(literalOrNegatedBool(!Value))) 466 .bind(Id), 467 this); 468 } 469 470 void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, 471 bool Value, StringRef Id) { 472 if (ChainedConditionalReturn) 473 Finder->addMatcher(ifStmt(hasThen(returnsBool(Value, ThenLiteralId)), 474 hasElse(returnsBool(!Value))) 475 .bind(Id), 476 this); 477 else 478 Finder->addMatcher(ifStmt(unless(hasParent(ifStmt())), 479 hasThen(returnsBool(Value, ThenLiteralId)), 480 hasElse(returnsBool(!Value))) 481 .bind(Id), 482 this); 483 } 484 485 void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder, 486 bool Value, StringRef Id) { 487 auto VarAssign = declRefExpr(hasDeclaration(decl().bind(IfAssignVarId))); 488 auto VarRef = declRefExpr(hasDeclaration(equalsBoundNode(IfAssignVarId))); 489 auto MemAssign = memberExpr(hasDeclaration(decl().bind(IfAssignVarId))); 490 auto MemRef = memberExpr(hasDeclaration(equalsBoundNode(IfAssignVarId))); 491 auto SimpleThen = 492 binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarAssign, MemAssign)), 493 hasLHS(expr().bind(IfAssignVariableId)), 494 hasRHS(literalOrNegatedBool(Value).bind(IfAssignLocId))); 495 auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1), 496 hasAnySubstatement(SimpleThen))); 497 auto SimpleElse = 498 binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarRef, MemRef)), 499 hasRHS(literalOrNegatedBool(!Value))); 500 auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), 501 hasAnySubstatement(SimpleElse))); 502 if (ChainedConditionalAssignment) 503 Finder->addMatcher(ifStmt(hasThen(Then), hasElse(Else)).bind(Id), this); 504 else 505 Finder->addMatcher( 506 ifStmt(unless(hasParent(ifStmt())), hasThen(Then), hasElse(Else)) 507 .bind(Id), 508 this); 509 } 510 511 static internal::Matcher<Stmt> ifReturnValue(bool Value) { 512 return ifStmt(hasThen(returnsBool(Value)), unless(hasElse(stmt()))) 513 .bind(CompoundIfId); 514 } 515 516 static internal::Matcher<Stmt> returnNotValue(bool Value) { 517 return returnStmt(has(literalOrNegatedBool(!Value))).bind(CompoundReturnId); 518 } 519 520 void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder, 521 bool Value, 522 StringRef Id) { 523 if (ChainedConditionalReturn) 524 Finder->addMatcher( 525 compoundStmt(hasSubstatementSequence(ifReturnValue(Value), 526 returnNotValue(Value))) 527 .bind(Id), 528 this); 529 else 530 Finder->addMatcher( 531 compoundStmt(hasSubstatementSequence(ifStmt(hasThen(returnsBool(Value)), 532 unless(hasElse(stmt())), 533 unless(hasParent(ifStmt()))) 534 .bind(CompoundIfId), 535 returnNotValue(Value))) 536 .bind(Id), 537 this); 538 } 539 540 void SimplifyBooleanExprCheck::matchCaseIfReturnsBool(MatchFinder *Finder, 541 bool Value, 542 StringRef Id) { 543 internal::Matcher<Stmt> CaseStmt = 544 caseStmt(hasSubstatement(ifReturnValue(Value))).bind(CaseId); 545 internal::Matcher<Stmt> CompoundStmt = 546 compoundStmt(hasSubstatementSequence(CaseStmt, returnNotValue(Value))) 547 .bind(Id); 548 Finder->addMatcher(switchStmt(has(CompoundStmt)), this); 549 } 550 551 void SimplifyBooleanExprCheck::matchDefaultIfReturnsBool(MatchFinder *Finder, 552 bool Value, 553 StringRef Id) { 554 internal::Matcher<Stmt> DefaultStmt = 555 defaultStmt(hasSubstatement(ifReturnValue(Value))).bind(DefaultId); 556 internal::Matcher<Stmt> CompoundStmt = 557 compoundStmt(hasSubstatementSequence(DefaultStmt, returnNotValue(Value))) 558 .bind(Id); 559 Finder->addMatcher(switchStmt(has(CompoundStmt)), this); 560 } 561 562 void SimplifyBooleanExprCheck::matchLabelIfReturnsBool(MatchFinder *Finder, 563 bool Value, 564 StringRef Id) { 565 internal::Matcher<Stmt> LabelStmt = 566 labelStmt(hasSubstatement(ifReturnValue(Value))).bind(LabelId); 567 internal::Matcher<Stmt> CompoundStmt = 568 compoundStmt(hasSubstatementSequence(LabelStmt, returnNotValue(Value))) 569 .bind(Id); 570 Finder->addMatcher(CompoundStmt, this); 571 } 572 573 void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 574 Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); 575 Options.store(Opts, "ChainedConditionalAssignment", 576 ChainedConditionalAssignment); 577 } 578 579 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { 580 Finder->addMatcher(translationUnitDecl().bind("top"), this); 581 582 matchBoolCondition(Finder, true, ConditionThenStmtId); 583 matchBoolCondition(Finder, false, ConditionElseStmtId); 584 585 matchTernaryResult(Finder, true, TernaryId); 586 matchTernaryResult(Finder, false, TernaryNegatedId); 587 588 matchIfReturnsBool(Finder, true, IfReturnsBoolId); 589 matchIfReturnsBool(Finder, false, IfReturnsNotBoolId); 590 591 matchIfAssignsBool(Finder, true, IfAssignBoolId); 592 matchIfAssignsBool(Finder, false, IfAssignNotBoolId); 593 594 matchCompoundIfReturnsBool(Finder, true, CompoundBoolId); 595 matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId); 596 597 matchCaseIfReturnsBool(Finder, true, CaseCompoundBoolId); 598 matchCaseIfReturnsBool(Finder, false, CaseCompoundNotBoolId); 599 600 matchDefaultIfReturnsBool(Finder, true, DefaultCompoundBoolId); 601 matchDefaultIfReturnsBool(Finder, false, DefaultCompoundNotBoolId); 602 603 matchLabelIfReturnsBool(Finder, true, LabelCompoundBoolId); 604 matchLabelIfReturnsBool(Finder, false, LabelCompoundNotBoolId); 605 } 606 607 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { 608 if (Result.Nodes.getNodeAs<TranslationUnitDecl>("top")) 609 Visitor(this, Result).TraverseAST(*Result.Context); 610 else if (const Expr *TrueConditionRemoved = 611 getBoolLiteral(Result, ConditionThenStmtId)) 612 replaceWithThenStatement(Result, TrueConditionRemoved); 613 else if (const Expr *FalseConditionRemoved = 614 getBoolLiteral(Result, ConditionElseStmtId)) 615 replaceWithElseStatement(Result, FalseConditionRemoved); 616 else if (const auto *Ternary = 617 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId)) 618 replaceWithCondition(Result, Ternary, false); 619 else if (const auto *TernaryNegated = 620 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryNegatedId)) 621 replaceWithCondition(Result, TernaryNegated, true); 622 else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId)) 623 replaceWithReturnCondition(Result, If, false); 624 else if (const auto *IfNot = 625 Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId)) 626 replaceWithReturnCondition(Result, IfNot, true); 627 else if (const auto *IfAssign = 628 Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId)) 629 replaceWithAssignment(Result, IfAssign, false); 630 else if (const auto *IfAssignNot = 631 Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId)) 632 replaceWithAssignment(Result, IfAssignNot, true); 633 else if (const auto *Compound = 634 Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId)) 635 replaceCompoundReturnWithCondition(Result, Compound, false); 636 else if (const auto *CompoundNot = 637 Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId)) 638 replaceCompoundReturnWithCondition(Result, CompoundNot, true); 639 else if (Result.Nodes.getNodeAs<CompoundStmt>(CaseCompoundBoolId)) 640 replaceCaseCompoundReturnWithCondition(Result, false); 641 else if (Result.Nodes.getNodeAs<CompoundStmt>(CaseCompoundNotBoolId)) 642 replaceCaseCompoundReturnWithCondition(Result, true); 643 else if (Result.Nodes.getNodeAs<CompoundStmt>(DefaultCompoundBoolId)) 644 replaceDefaultCompoundReturnWithCondition(Result, false); 645 else if (Result.Nodes.getNodeAs<CompoundStmt>(DefaultCompoundNotBoolId)) 646 replaceDefaultCompoundReturnWithCondition(Result, true); 647 else if (Result.Nodes.getNodeAs<CompoundStmt>(LabelCompoundBoolId)) 648 replaceLabelCompoundReturnWithCondition(Result, false); 649 else if (Result.Nodes.getNodeAs<CompoundStmt>(LabelCompoundNotBoolId)) 650 replaceLabelCompoundReturnWithCondition(Result, true); 651 else if (const auto TU = Result.Nodes.getNodeAs<Decl>("top")) 652 Visitor(this, Result).TraverseDecl(const_cast<Decl *>(TU)); 653 } 654 655 void SimplifyBooleanExprCheck::issueDiag(const MatchFinder::MatchResult &Result, 656 SourceLocation Loc, 657 StringRef Description, 658 SourceRange ReplacementRange, 659 StringRef Replacement) { 660 CharSourceRange CharRange = 661 Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange), 662 *Result.SourceManager, getLangOpts()); 663 664 DiagnosticBuilder Diag = diag(Loc, Description); 665 if (!containsDiscardedTokens(Result, CharRange)) 666 Diag << FixItHint::CreateReplacement(CharRange, Replacement); 667 } 668 669 void SimplifyBooleanExprCheck::replaceWithThenStatement( 670 const MatchFinder::MatchResult &Result, const Expr *BoolLiteral) { 671 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId); 672 issueDiag(Result, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic, 673 IfStatement->getSourceRange(), 674 getText(Result, *IfStatement->getThen())); 675 } 676 677 void SimplifyBooleanExprCheck::replaceWithElseStatement( 678 const MatchFinder::MatchResult &Result, const Expr *BoolLiteral) { 679 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId); 680 const Stmt *ElseStatement = IfStatement->getElse(); 681 issueDiag(Result, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic, 682 IfStatement->getSourceRange(), 683 ElseStatement ? getText(Result, *ElseStatement) : ""); 684 } 685 686 void SimplifyBooleanExprCheck::replaceWithCondition( 687 const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary, 688 bool Negated) { 689 std::string Replacement = 690 replacementExpression(Result, Negated, Ternary->getCond()); 691 issueDiag(Result, Ternary->getTrueExpr()->getBeginLoc(), 692 "redundant boolean literal in ternary expression result", 693 Ternary->getSourceRange(), Replacement); 694 } 695 696 void SimplifyBooleanExprCheck::replaceWithReturnCondition( 697 const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) { 698 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : ""; 699 std::string Condition = replacementExpression(Result, Negated, If->getCond()); 700 std::string Replacement = ("return " + Condition + Terminator).str(); 701 SourceLocation Start = 702 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getBeginLoc(); 703 issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic, 704 If->getSourceRange(), Replacement); 705 } 706 707 void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( 708 const MatchFinder::MatchResult &Result, const CompoundStmt *Compound, 709 bool Negated) { 710 const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId); 711 712 // Scan through the CompoundStmt to look for a chained-if construct. 713 const IfStmt *BeforeIf = nullptr; 714 CompoundStmt::const_body_iterator Current = Compound->body_begin(); 715 CompoundStmt::const_body_iterator After = Compound->body_begin(); 716 for (++After; After != Compound->body_end() && *Current != Ret; 717 ++Current, ++After) { 718 if (const auto *If = dyn_cast<IfStmt>(*Current)) { 719 if (const Expr *Lit = stmtReturnsBool(If, Negated)) { 720 if (*After == Ret) { 721 if (!ChainedConditionalReturn && BeforeIf) 722 continue; 723 724 std::string Replacement = 725 "return " + replacementExpression(Result, Negated, If->getCond()); 726 issueDiag( 727 Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic, 728 SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement); 729 return; 730 } 731 732 BeforeIf = If; 733 } 734 } else { 735 BeforeIf = nullptr; 736 } 737 } 738 } 739 740 void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( 741 const MatchFinder::MatchResult &Result, bool Negated, const IfStmt *If) { 742 const auto *Lit = stmtReturnsBool(If, Negated); 743 const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId); 744 const std::string Replacement = 745 "return " + replacementExpression(Result, Negated, If->getCond()); 746 issueDiag(Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic, 747 SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement); 748 } 749 750 void SimplifyBooleanExprCheck::replaceCaseCompoundReturnWithCondition( 751 const MatchFinder::MatchResult &Result, bool Negated) { 752 const auto *CaseDefault = Result.Nodes.getNodeAs<CaseStmt>(CaseId); 753 const auto *If = cast<IfStmt>(CaseDefault->getSubStmt()); 754 replaceCompoundReturnWithCondition(Result, Negated, If); 755 } 756 757 void SimplifyBooleanExprCheck::replaceDefaultCompoundReturnWithCondition( 758 const MatchFinder::MatchResult &Result, bool Negated) { 759 const SwitchCase *CaseDefault = 760 Result.Nodes.getNodeAs<DefaultStmt>(DefaultId); 761 const auto *If = cast<IfStmt>(CaseDefault->getSubStmt()); 762 replaceCompoundReturnWithCondition(Result, Negated, If); 763 } 764 765 void SimplifyBooleanExprCheck::replaceLabelCompoundReturnWithCondition( 766 const MatchFinder::MatchResult &Result, bool Negated) { 767 const auto *Label = Result.Nodes.getNodeAs<LabelStmt>(LabelId); 768 const auto *If = cast<IfStmt>(Label->getSubStmt()); 769 replaceCompoundReturnWithCondition(Result, Negated, If); 770 } 771 772 void SimplifyBooleanExprCheck::replaceWithAssignment( 773 const MatchFinder::MatchResult &Result, const IfStmt *IfAssign, 774 bool Negated) { 775 SourceRange Range = IfAssign->getSourceRange(); 776 StringRef VariableName = 777 getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId)); 778 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : ""; 779 std::string Condition = 780 replacementExpression(Result, Negated, IfAssign->getCond()); 781 std::string Replacement = 782 (VariableName + " = " + Condition + Terminator).str(); 783 SourceLocation Location = 784 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getBeginLoc(); 785 issueDiag(Result, Location, 786 "redundant boolean literal in conditional assignment", Range, 787 Replacement); 788 } 789 790 } // namespace readability 791 } // namespace tidy 792 } // namespace clang 793