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())) 112 .str(); 113 } 114 } 115 } 116 StringRef Text = getText(Result, *E); 117 return (Negated ? (needsParensAfterUnaryNegation(E) ? "!(" + Text + ")" 118 : "!" + Text) 119 : Text) 120 .str(); 121 } 122 123 } // namespace 124 125 void SimplifyBooleanExprCheck::matchBoolBinOpExpr(MatchFinder *Finder, 126 bool Value, 127 StringRef OperatorName, 128 StringRef BooleanId) { 129 Finder->addMatcher( 130 binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), 131 hasLHS(allOf(expr().bind(LHSId), 132 boolLiteral(equals(Value)).bind(BooleanId))), 133 hasRHS(expr().bind(RHSId)), 134 unless(hasRHS(hasDescendant(boolLiteral())))), 135 this); 136 } 137 138 void SimplifyBooleanExprCheck::matchExprBinOpBool(MatchFinder *Finder, 139 bool Value, 140 StringRef OperatorName, 141 StringRef BooleanId) { 142 Finder->addMatcher( 143 binaryOperator( 144 isExpansionInMainFile(), hasOperatorName(OperatorName), 145 hasLHS(expr().bind(LHSId)), 146 unless(hasLHS(anyOf(boolLiteral(), hasDescendant(boolLiteral())))), 147 hasRHS(allOf(expr().bind(RHSId), 148 boolLiteral(equals(Value)).bind(BooleanId)))), 149 this); 150 } 151 152 void SimplifyBooleanExprCheck::matchBoolCompOpExpr(MatchFinder *Finder, 153 bool Value, 154 StringRef OperatorName, 155 StringRef BooleanId) { 156 Finder->addMatcher( 157 binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), 158 hasLHS(allOf(expr().bind(LHSId), 159 ignoringImpCasts(boolLiteral(equals(Value)) 160 .bind(BooleanId)))), 161 hasRHS(expr().bind(RHSId)), 162 unless(hasRHS(hasDescendant(boolLiteral())))), 163 this); 164 } 165 166 void SimplifyBooleanExprCheck::matchExprCompOpBool(MatchFinder *Finder, 167 bool Value, 168 StringRef OperatorName, 169 StringRef BooleanId) { 170 Finder->addMatcher( 171 binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName), 172 unless(hasLHS(hasDescendant(boolLiteral()))), 173 hasLHS(expr().bind(LHSId)), 174 hasRHS(allOf(expr().bind(RHSId), 175 ignoringImpCasts(boolLiteral(equals(Value)) 176 .bind(BooleanId))))), 177 this); 178 } 179 180 void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder, 181 bool Value, 182 StringRef BooleanId) { 183 Finder->addMatcher(ifStmt(isExpansionInMainFile(), 184 hasCondition(boolLiteral(equals(Value)) 185 .bind(BooleanId))).bind(IfStmtId), 186 this); 187 } 188 189 void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, 190 bool Value, 191 StringRef TernaryId) { 192 Finder->addMatcher( 193 conditionalOperator(isExpansionInMainFile(), 194 hasTrueExpression(boolLiteral(equals(Value))), 195 hasFalseExpression(boolLiteral(equals(!Value)))) 196 .bind(TernaryId), 197 this); 198 } 199 200 void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, 201 bool Value, StringRef Id) { 202 Finder->addMatcher(ifStmt(isExpansionInMainFile(), 203 hasThen(ReturnsBool(Value, ThenLiteralId)), 204 hasElse(ReturnsBool(!Value))).bind(Id), 205 this); 206 } 207 208 void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder, 209 bool Value, StringRef Id) { 210 auto SimpleThen = binaryOperator( 211 hasOperatorName("="), 212 hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))), 213 hasLHS(expr().bind(IfAssignVariableId)), 214 hasRHS(boolLiteral(equals(Value)).bind(IfAssignLocId))); 215 auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1), 216 hasAnySubstatement(SimpleThen))); 217 auto SimpleElse = binaryOperator( 218 hasOperatorName("="), 219 hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))), 220 hasRHS(boolLiteral(equals(!Value)))); 221 auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), 222 hasAnySubstatement(SimpleElse))); 223 Finder->addMatcher( 224 ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id), 225 this); 226 } 227 228 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { 229 matchBoolBinOpExpr(Finder, true, "&&", RightExpressionId); 230 matchBoolBinOpExpr(Finder, false, "||", RightExpressionId); 231 matchExprBinOpBool(Finder, false, "&&", RightExpressionId); 232 matchExprBinOpBool(Finder, true, "||", RightExpressionId); 233 matchBoolCompOpExpr(Finder, true, "==", RightExpressionId); 234 matchBoolCompOpExpr(Finder, false, "!=", RightExpressionId); 235 236 matchExprBinOpBool(Finder, true, "&&", LeftExpressionId); 237 matchExprBinOpBool(Finder, false, "||", LeftExpressionId); 238 matchBoolBinOpExpr(Finder, false, "&&", LeftExpressionId); 239 matchBoolBinOpExpr(Finder, true, "||", LeftExpressionId); 240 matchExprCompOpBool(Finder, true, "==", LeftExpressionId); 241 matchExprCompOpBool(Finder, false, "!=", LeftExpressionId); 242 243 matchBoolCompOpExpr(Finder, false, "==", NegatedRightExpressionId); 244 matchBoolCompOpExpr(Finder, true, "!=", NegatedRightExpressionId); 245 246 matchExprCompOpBool(Finder, false, "==", NegatedLeftExpressionId); 247 matchExprCompOpBool(Finder, true, "!=", NegatedLeftExpressionId); 248 249 matchBoolCondition(Finder, true, ConditionThenStmtId); 250 matchBoolCondition(Finder, false, ConditionElseStmtId); 251 252 matchTernaryResult(Finder, true, TernaryId); 253 matchTernaryResult(Finder, false, TernaryNegatedId); 254 255 matchIfReturnsBool(Finder, true, IfReturnsBoolId); 256 matchIfReturnsBool(Finder, false, IfReturnsNotBoolId); 257 258 matchIfAssignsBool(Finder, true, IfAssignBoolId); 259 matchIfAssignsBool(Finder, false, IfAssignNotBoolId); 260 } 261 262 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { 263 if (const CXXBoolLiteralExpr *LeftRemoved = 264 getBoolLiteral(Result, RightExpressionId)) { 265 replaceWithExpression(Result, LeftRemoved, false); 266 } else if (const CXXBoolLiteralExpr *RightRemoved = 267 getBoolLiteral(Result, LeftExpressionId)) { 268 replaceWithExpression(Result, RightRemoved, true); 269 } else if (const CXXBoolLiteralExpr *NegatedLeftRemoved = 270 getBoolLiteral(Result, NegatedRightExpressionId)) { 271 replaceWithExpression(Result, NegatedLeftRemoved, false, true); 272 } else if (const CXXBoolLiteralExpr *NegatedRightRemoved = 273 getBoolLiteral(Result, NegatedLeftExpressionId)) { 274 replaceWithExpression(Result, NegatedRightRemoved, true, true); 275 } else if (const CXXBoolLiteralExpr *TrueConditionRemoved = 276 getBoolLiteral(Result, ConditionThenStmtId)) { 277 replaceWithThenStatement(Result, TrueConditionRemoved); 278 } else if (const CXXBoolLiteralExpr *FalseConditionRemoved = 279 getBoolLiteral(Result, ConditionElseStmtId)) { 280 replaceWithElseStatement(Result, FalseConditionRemoved); 281 } else if (const auto *Ternary = 282 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId)) { 283 replaceWithCondition(Result, Ternary); 284 } else if (const auto *TernaryNegated = 285 Result.Nodes.getNodeAs<ConditionalOperator>( 286 TernaryNegatedId)) { 287 replaceWithCondition(Result, TernaryNegated, true); 288 } else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId)) { 289 replaceWithReturnCondition(Result, If); 290 } else if (const auto *IfNot = 291 Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId)) { 292 replaceWithReturnCondition(Result, IfNot, true); 293 } else if (const auto *IfAssign = 294 Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId)) { 295 replaceWithAssignment(Result, IfAssign); 296 } else if (const auto *IfAssignNot = 297 Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId)) { 298 replaceWithAssignment(Result, IfAssignNot, true); 299 } 300 } 301 302 void SimplifyBooleanExprCheck::replaceWithExpression( 303 const ast_matchers::MatchFinder::MatchResult &Result, 304 const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, bool Negated) { 305 const auto *LHS = Result.Nodes.getNodeAs<Expr>(LHSId); 306 const auto *RHS = Result.Nodes.getNodeAs<Expr>(RHSId); 307 std::string Replacement = 308 replacementExpression(Result, Negated, UseLHS ? LHS : RHS); 309 SourceLocation Start = LHS->getLocStart(); 310 SourceLocation End = RHS->getLocEnd(); 311 diag(BoolLiteral->getLocStart(), SimplifyOperatorDiagnostic) 312 << FixItHint::CreateReplacement(SourceRange(Start, End), Replacement); 313 } 314 315 void SimplifyBooleanExprCheck::replaceWithThenStatement( 316 const MatchFinder::MatchResult &Result, 317 const CXXBoolLiteralExpr *TrueConditionRemoved) { 318 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId); 319 diag(TrueConditionRemoved->getLocStart(), SimplifyConditionDiagnostic) 320 << FixItHint::CreateReplacement(IfStatement->getSourceRange(), 321 getText(Result, *IfStatement->getThen())); 322 } 323 324 void SimplifyBooleanExprCheck::replaceWithElseStatement( 325 const MatchFinder::MatchResult &Result, 326 const CXXBoolLiteralExpr *FalseConditionRemoved) { 327 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId); 328 const Stmt *ElseStatement = IfStatement->getElse(); 329 diag(FalseConditionRemoved->getLocStart(), SimplifyConditionDiagnostic) 330 << FixItHint::CreateReplacement( 331 IfStatement->getSourceRange(), 332 ElseStatement ? getText(Result, *ElseStatement) : ""); 333 } 334 335 void SimplifyBooleanExprCheck::replaceWithCondition( 336 const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary, 337 bool Negated) { 338 std::string Replacement = 339 replacementExpression(Result, Negated, Ternary->getCond()); 340 diag(Ternary->getTrueExpr()->getLocStart(), 341 "redundant boolean literal in ternary expression result") 342 << FixItHint::CreateReplacement(Ternary->getSourceRange(), Replacement); 343 } 344 345 void SimplifyBooleanExprCheck::replaceWithReturnCondition( 346 const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) { 347 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : ""; 348 std::string Condition = replacementExpression(Result, Negated, If->getCond()); 349 std::string Replacement = ("return " + Condition + Terminator).str(); 350 SourceLocation Start = 351 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart(); 352 diag(Start, "redundant boolean literal in conditional return statement") 353 << FixItHint::CreateReplacement(If->getSourceRange(), Replacement); 354 } 355 356 void SimplifyBooleanExprCheck::replaceWithAssignment( 357 const MatchFinder::MatchResult &Result, const IfStmt *IfAssign, 358 bool Negated) { 359 SourceRange Range = IfAssign->getSourceRange(); 360 StringRef VariableName = 361 getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId)); 362 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : ""; 363 std::string Condition = 364 replacementExpression(Result, Negated, IfAssign->getCond()); 365 std::string Replacement = 366 (VariableName + " = " + Condition + Terminator).str(); 367 SourceLocation Location = 368 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart(); 369 this->diag(Location, "redundant boolean literal in conditional assignment") 370 << FixItHint::CreateReplacement(Range, Replacement); 371 } 372 373 } // namespace readability 374 } // namespace tidy 375 } // namespace clang 376