1 //===--- SimplifyBooleanExpr.cpp clang-tidy ---------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "SimplifyBooleanExprCheck.h" 11 #include "clang/Lex/Lexer.h" 12 13 #include <cassert> 14 #include <string> 15 16 using namespace clang::ast_matchers; 17 18 namespace clang { 19 namespace tidy { 20 namespace readability { 21 22 namespace { 23 24 StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) { 25 return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), 26 *Result.SourceManager, 27 Result.Context->getLangOpts()); 28 } 29 30 template <typename T> 31 StringRef getText(const MatchFinder::MatchResult &Result, T &Node) { 32 return getText(Result, Node.getSourceRange()); 33 } 34 35 const char RightExpressionId[] = "bool-op-expr-yields-expr"; 36 const char LeftExpressionId[] = "expr-op-bool-yields-expr"; 37 const char NegatedRightExpressionId[] = "bool-op-expr-yields-not-expr"; 38 const char NegatedLeftExpressionId[] = "expr-op-bool-yields-not-expr"; 39 const char ConditionThenStmtId[] = "if-bool-yields-then"; 40 const char ConditionElseStmtId[] = "if-bool-yields-else"; 41 const char TernaryId[] = "ternary-bool-yields-condition"; 42 const char TernaryNegatedId[] = "ternary-bool-yields-not-condition"; 43 const char IfReturnsBoolId[] = "if-return"; 44 const char IfReturnsNotBoolId[] = "if-not-return"; 45 const char ThenLiteralId[] = "then-literal"; 46 const char IfAssignVariableId[] = "if-assign-lvalue"; 47 const char IfAssignLocId[] = "if-assign-loc"; 48 const char IfAssignBoolId[] = "if-assign"; 49 const char IfAssignNotBoolId[] = "if-assign-not"; 50 const char IfAssignObjId[] = "if-assign-obj"; 51 52 const char IfStmtId[] = "if"; 53 const char LHSId[] = "lhs-expr"; 54 const char RHSId[] = "rhs-expr"; 55 56 const char SimplifyOperatorDiagnostic[] = 57 "redundant boolean literal supplied to boolean operator"; 58 const char SimplifyConditionDiagnostic[] = 59 "redundant boolean literal in if statement condition"; 60 61 const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result, 62 StringRef Id) { 63 const auto *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id); 64 return (Literal && 65 Result.SourceManager->isMacroBodyExpansion(Literal->getLocStart())) 66 ? nullptr 67 : Literal; 68 } 69 70 internal::Matcher<Stmt> ReturnsBool(bool Value, StringRef Id = "") { 71 auto SimpleReturnsBool = returnStmt( 72 has(boolLiteral(equals(Value)).bind(Id.empty() ? "ignored" : Id))); 73 return anyOf(SimpleReturnsBool, 74 compoundStmt(statementCountIs(1), has(SimpleReturnsBool))); 75 } 76 77 bool needsParensAfterUnaryNegation(const Expr *E) { 78 if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E)) 79 return true; 80 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E)) 81 return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && 82 Op->getOperator() != OO_Subscript; 83 return false; 84 } 85 86 std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = { 87 std::make_pair(BO_LT, BO_GE), std::make_pair(BO_GT, BO_LE), 88 std::make_pair(BO_EQ, BO_NE)}; 89 90 StringRef negatedOperator(const BinaryOperator *BinOp) { 91 const BinaryOperatorKind Opcode = BinOp->getOpcode(); 92 for (auto NegatableOp : Opposites) { 93 if (Opcode == NegatableOp.first) 94 return BinOp->getOpcodeStr(NegatableOp.second); 95 if (Opcode == NegatableOp.second) 96 return BinOp->getOpcodeStr(NegatableOp.first); 97 } 98 return StringRef(); 99 } 100 101 std::string replacementExpression(const MatchFinder::MatchResult &Result, 102 bool Negated, const Expr *E) { 103 while (const auto *Parenthesized = dyn_cast<ParenExpr>(E)) { 104 E = Parenthesized->getSubExpr(); 105 } 106 if (Negated) { 107 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { 108 StringRef NegatedOperator = negatedOperator(BinOp); 109 if (!NegatedOperator.empty()) { 110 return (getText(Result, *BinOp->getLHS()) + " " + NegatedOperator + 111 " " + getText(Result, *BinOp->getRHS())).str(); 112 } 113 } 114 } 115 StringRef Text = getText(Result, *E); 116 return (Negated ? (needsParensAfterUnaryNegation(E) ? "!(" + Text + ")" 117 : "!" + Text) 118 : Text).str(); 119 } 120 121 } // namespace 122 123 SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, 124 ClangTidyContext *Context) 125 : ClangTidyCheck(Name, Context), 126 ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)), 127 ChainedConditionalAssignment( 128 Options.get("ChainedConditionalAssignment", 0U)) {} 129 130 void SimplifyBooleanExprCheck::matchBoolBinOpExpr(MatchFinder *Finder, 131 bool Value, 132 StringRef OperatorName, 133 StringRef BooleanId) { 134 Finder->addMatcher( 135 binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), 136 hasLHS(allOf(expr().bind(LHSId), 137 boolLiteral(equals(Value)).bind(BooleanId))), 138 hasRHS(expr().bind(RHSId)), 139 unless(hasRHS(hasDescendant(boolLiteral())))), 140 this); 141 } 142 143 void SimplifyBooleanExprCheck::matchExprBinOpBool(MatchFinder *Finder, 144 bool Value, 145 StringRef OperatorName, 146 StringRef BooleanId) { 147 Finder->addMatcher( 148 binaryOperator( 149 isExpansionInMainFile(), hasOperatorName(OperatorName), 150 hasLHS(expr().bind(LHSId)), 151 unless(hasLHS(anyOf(boolLiteral(), hasDescendant(boolLiteral())))), 152 hasRHS(allOf(expr().bind(RHSId), 153 boolLiteral(equals(Value)).bind(BooleanId)))), 154 this); 155 } 156 157 void SimplifyBooleanExprCheck::matchBoolCompOpExpr(MatchFinder *Finder, 158 bool Value, 159 StringRef OperatorName, 160 StringRef BooleanId) { 161 Finder->addMatcher( 162 binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), 163 hasLHS(allOf(expr().bind(LHSId), 164 ignoringImpCasts(boolLiteral(equals(Value)) 165 .bind(BooleanId)))), 166 hasRHS(expr().bind(RHSId)), 167 unless(hasRHS(hasDescendant(boolLiteral())))), 168 this); 169 } 170 171 void SimplifyBooleanExprCheck::matchExprCompOpBool(MatchFinder *Finder, 172 bool Value, 173 StringRef OperatorName, 174 StringRef BooleanId) { 175 Finder->addMatcher( 176 binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), 177 unless(hasLHS(hasDescendant(boolLiteral()))), 178 hasLHS(expr().bind(LHSId)), 179 hasRHS(allOf(expr().bind(RHSId), 180 ignoringImpCasts(boolLiteral(equals(Value)) 181 .bind(BooleanId))))), 182 this); 183 } 184 185 void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder, 186 bool Value, 187 StringRef BooleanId) { 188 Finder->addMatcher(ifStmt(isExpansionInMainFile(), 189 hasCondition(boolLiteral(equals(Value)) 190 .bind(BooleanId))).bind(IfStmtId), 191 this); 192 } 193 194 void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, 195 bool Value, 196 StringRef TernaryId) { 197 Finder->addMatcher( 198 conditionalOperator(isExpansionInMainFile(), 199 hasTrueExpression(boolLiteral(equals(Value))), 200 hasFalseExpression(boolLiteral(equals(!Value)))) 201 .bind(TernaryId), 202 this); 203 } 204 205 void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, 206 bool Value, StringRef Id) { 207 if (ChainedConditionalReturn) { 208 Finder->addMatcher(ifStmt(isExpansionInMainFile(), 209 hasThen(ReturnsBool(Value, ThenLiteralId)), 210 hasElse(ReturnsBool(!Value))).bind(Id), 211 this); 212 } else { 213 Finder->addMatcher(ifStmt(isExpansionInMainFile(), 214 unless(hasParent(ifStmt())), 215 hasThen(ReturnsBool(Value, ThenLiteralId)), 216 hasElse(ReturnsBool(!Value))).bind(Id), 217 this); 218 } 219 } 220 221 void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder, 222 bool Value, StringRef Id) { 223 auto SimpleThen = binaryOperator( 224 hasOperatorName("="), 225 hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))), 226 hasLHS(expr().bind(IfAssignVariableId)), 227 hasRHS(boolLiteral(equals(Value)).bind(IfAssignLocId))); 228 auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1), 229 hasAnySubstatement(SimpleThen))); 230 auto SimpleElse = binaryOperator( 231 hasOperatorName("="), 232 hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))), 233 hasRHS(boolLiteral(equals(!Value)))); 234 auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), 235 hasAnySubstatement(SimpleElse))); 236 if (ChainedConditionalAssignment) { 237 Finder->addMatcher( 238 ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id), 239 this); 240 } else { 241 Finder->addMatcher(ifStmt(isExpansionInMainFile(), 242 unless(hasParent(ifStmt())), hasThen(Then), 243 hasElse(Else)).bind(Id), 244 this); 245 } 246 } 247 248 void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 249 Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); 250 Options.store(Opts, "ChainedConditionalAssignment", 251 ChainedConditionalAssignment); 252 } 253 254 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { 255 matchBoolBinOpExpr(Finder, true, "&&", RightExpressionId); 256 matchBoolBinOpExpr(Finder, false, "||", RightExpressionId); 257 matchExprBinOpBool(Finder, false, "&&", RightExpressionId); 258 matchExprBinOpBool(Finder, true, "||", RightExpressionId); 259 matchBoolCompOpExpr(Finder, true, "==", RightExpressionId); 260 matchBoolCompOpExpr(Finder, false, "!=", RightExpressionId); 261 262 matchExprBinOpBool(Finder, true, "&&", LeftExpressionId); 263 matchExprBinOpBool(Finder, false, "||", LeftExpressionId); 264 matchBoolBinOpExpr(Finder, false, "&&", LeftExpressionId); 265 matchBoolBinOpExpr(Finder, true, "||", LeftExpressionId); 266 matchExprCompOpBool(Finder, true, "==", LeftExpressionId); 267 matchExprCompOpBool(Finder, false, "!=", LeftExpressionId); 268 269 matchBoolCompOpExpr(Finder, false, "==", NegatedRightExpressionId); 270 matchBoolCompOpExpr(Finder, true, "!=", NegatedRightExpressionId); 271 272 matchExprCompOpBool(Finder, false, "==", NegatedLeftExpressionId); 273 matchExprCompOpBool(Finder, true, "!=", NegatedLeftExpressionId); 274 275 matchBoolCondition(Finder, true, ConditionThenStmtId); 276 matchBoolCondition(Finder, false, ConditionElseStmtId); 277 278 matchTernaryResult(Finder, true, TernaryId); 279 matchTernaryResult(Finder, false, TernaryNegatedId); 280 281 matchIfReturnsBool(Finder, true, IfReturnsBoolId); 282 matchIfReturnsBool(Finder, false, IfReturnsNotBoolId); 283 284 matchIfAssignsBool(Finder, true, IfAssignBoolId); 285 matchIfAssignsBool(Finder, false, IfAssignNotBoolId); 286 } 287 288 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { 289 if (const CXXBoolLiteralExpr *LeftRemoved = 290 getBoolLiteral(Result, RightExpressionId)) { 291 replaceWithExpression(Result, LeftRemoved, false); 292 } else if (const CXXBoolLiteralExpr *RightRemoved = 293 getBoolLiteral(Result, LeftExpressionId)) { 294 replaceWithExpression(Result, RightRemoved, true); 295 } else if (const CXXBoolLiteralExpr *NegatedLeftRemoved = 296 getBoolLiteral(Result, NegatedRightExpressionId)) { 297 replaceWithExpression(Result, NegatedLeftRemoved, false, true); 298 } else if (const CXXBoolLiteralExpr *NegatedRightRemoved = 299 getBoolLiteral(Result, NegatedLeftExpressionId)) { 300 replaceWithExpression(Result, NegatedRightRemoved, true, true); 301 } else if (const CXXBoolLiteralExpr *TrueConditionRemoved = 302 getBoolLiteral(Result, ConditionThenStmtId)) { 303 replaceWithThenStatement(Result, TrueConditionRemoved); 304 } else if (const CXXBoolLiteralExpr *FalseConditionRemoved = 305 getBoolLiteral(Result, ConditionElseStmtId)) { 306 replaceWithElseStatement(Result, FalseConditionRemoved); 307 } else if (const auto *Ternary = 308 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId)) { 309 replaceWithCondition(Result, Ternary); 310 } else if (const auto *TernaryNegated = 311 Result.Nodes.getNodeAs<ConditionalOperator>( 312 TernaryNegatedId)) { 313 replaceWithCondition(Result, TernaryNegated, true); 314 } else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId)) { 315 replaceWithReturnCondition(Result, If); 316 } else if (const auto *IfNot = 317 Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId)) { 318 replaceWithReturnCondition(Result, IfNot, true); 319 } else if (const auto *IfAssign = 320 Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId)) { 321 replaceWithAssignment(Result, IfAssign); 322 } else if (const auto *IfAssignNot = 323 Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId)) { 324 replaceWithAssignment(Result, IfAssignNot, true); 325 } 326 } 327 328 void SimplifyBooleanExprCheck::replaceWithExpression( 329 const ast_matchers::MatchFinder::MatchResult &Result, 330 const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, bool Negated) { 331 const auto *LHS = Result.Nodes.getNodeAs<Expr>(LHSId); 332 const auto *RHS = Result.Nodes.getNodeAs<Expr>(RHSId); 333 std::string Replacement = 334 replacementExpression(Result, Negated, UseLHS ? LHS : RHS); 335 SourceLocation Start = LHS->getLocStart(); 336 SourceLocation End = RHS->getLocEnd(); 337 diag(BoolLiteral->getLocStart(), SimplifyOperatorDiagnostic) 338 << FixItHint::CreateReplacement(SourceRange(Start, End), Replacement); 339 } 340 341 void SimplifyBooleanExprCheck::replaceWithThenStatement( 342 const MatchFinder::MatchResult &Result, 343 const CXXBoolLiteralExpr *TrueConditionRemoved) { 344 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId); 345 diag(TrueConditionRemoved->getLocStart(), SimplifyConditionDiagnostic) 346 << FixItHint::CreateReplacement(IfStatement->getSourceRange(), 347 getText(Result, *IfStatement->getThen())); 348 } 349 350 void SimplifyBooleanExprCheck::replaceWithElseStatement( 351 const MatchFinder::MatchResult &Result, 352 const CXXBoolLiteralExpr *FalseConditionRemoved) { 353 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId); 354 const Stmt *ElseStatement = IfStatement->getElse(); 355 diag(FalseConditionRemoved->getLocStart(), SimplifyConditionDiagnostic) 356 << FixItHint::CreateReplacement( 357 IfStatement->getSourceRange(), 358 ElseStatement ? getText(Result, *ElseStatement) : ""); 359 } 360 361 void SimplifyBooleanExprCheck::replaceWithCondition( 362 const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary, 363 bool Negated) { 364 std::string Replacement = 365 replacementExpression(Result, Negated, Ternary->getCond()); 366 diag(Ternary->getTrueExpr()->getLocStart(), 367 "redundant boolean literal in ternary expression result") 368 << FixItHint::CreateReplacement(Ternary->getSourceRange(), Replacement); 369 } 370 371 void SimplifyBooleanExprCheck::replaceWithReturnCondition( 372 const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) { 373 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : ""; 374 std::string Condition = replacementExpression(Result, Negated, If->getCond()); 375 std::string Replacement = ("return " + Condition + Terminator).str(); 376 SourceLocation Start = 377 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart(); 378 diag(Start, "redundant boolean literal in conditional return statement") 379 << FixItHint::CreateReplacement(If->getSourceRange(), Replacement); 380 } 381 382 void SimplifyBooleanExprCheck::replaceWithAssignment( 383 const MatchFinder::MatchResult &Result, const IfStmt *IfAssign, 384 bool Negated) { 385 SourceRange Range = IfAssign->getSourceRange(); 386 StringRef VariableName = 387 getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId)); 388 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : ""; 389 std::string Condition = 390 replacementExpression(Result, Negated, IfAssign->getCond()); 391 std::string Replacement = 392 (VariableName + " = " + Condition + Terminator).str(); 393 SourceLocation Location = 394 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart(); 395 this->diag(Location, "redundant boolean literal in conditional assignment") 396 << FixItHint::CreateReplacement(Range, Replacement); 397 } 398 399 } // namespace readability 400 } // namespace tidy 401 } // namespace clang 402