SimplifyBooleanExprCheck.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  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. #include "SimplifyBooleanExprCheck.h"
  9. #include "clang/AST/RecursiveASTVisitor.h"
  10. #include "clang/Lex/Lexer.h"
  11. #include "llvm/Support/SaveAndRestore.h"
  12. #include <optional>
  13. #include <string>
  14. #include <utility>
  15. using namespace clang::ast_matchers;
  16. namespace clang::tidy::readability {
  17. namespace {
  18. StringRef getText(const ASTContext &Context, SourceRange Range) {
  19. return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
  20. Context.getSourceManager(),
  21. Context.getLangOpts());
  22. }
  23. template <typename T> StringRef getText(const ASTContext &Context, T &Node) {
  24. return getText(Context, Node.getSourceRange());
  25. }
  26. } // namespace
  27. static constexpr char SimplifyOperatorDiagnostic[] =
  28. "redundant boolean literal supplied to boolean operator";
  29. static constexpr char SimplifyConditionDiagnostic[] =
  30. "redundant boolean literal in if statement condition";
  31. static constexpr char SimplifyConditionalReturnDiagnostic[] =
  32. "redundant boolean literal in conditional return statement";
  33. static bool needsParensAfterUnaryNegation(const Expr *E) {
  34. E = E->IgnoreImpCasts();
  35. if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
  36. return true;
  37. if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
  38. return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
  39. Op->getOperator() != OO_Subscript;
  40. return false;
  41. }
  42. static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
  43. {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
  44. static StringRef negatedOperator(const BinaryOperator *BinOp) {
  45. const BinaryOperatorKind Opcode = BinOp->getOpcode();
  46. for (auto NegatableOp : Opposites) {
  47. if (Opcode == NegatableOp.first)
  48. return BinOp->getOpcodeStr(NegatableOp.second);
  49. if (Opcode == NegatableOp.second)
  50. return BinOp->getOpcodeStr(NegatableOp.first);
  51. }
  52. return {};
  53. }
  54. static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
  55. {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
  56. {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
  57. static StringRef getOperatorName(OverloadedOperatorKind OpKind) {
  58. for (auto Name : OperatorNames) {
  59. if (Name.first == OpKind)
  60. return Name.second;
  61. }
  62. return {};
  63. }
  64. static std::pair<OverloadedOperatorKind, OverloadedOperatorKind>
  65. OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual},
  66. {OO_Less, OO_GreaterEqual},
  67. {OO_Greater, OO_LessEqual}};
  68. static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
  69. const OverloadedOperatorKind Opcode = OpCall->getOperator();
  70. for (auto NegatableOp : OppositeOverloads) {
  71. if (Opcode == NegatableOp.first)
  72. return getOperatorName(NegatableOp.second);
  73. if (Opcode == NegatableOp.second)
  74. return getOperatorName(NegatableOp.first);
  75. }
  76. return {};
  77. }
  78. static std::string asBool(StringRef Text, bool NeedsStaticCast) {
  79. if (NeedsStaticCast)
  80. return ("static_cast<bool>(" + Text + ")").str();
  81. return std::string(Text);
  82. }
  83. static bool needsNullPtrComparison(const Expr *E) {
  84. if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
  85. return ImpCast->getCastKind() == CK_PointerToBoolean ||
  86. ImpCast->getCastKind() == CK_MemberPointerToBoolean;
  87. return false;
  88. }
  89. static bool needsZeroComparison(const Expr *E) {
  90. if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
  91. return ImpCast->getCastKind() == CK_IntegralToBoolean;
  92. return false;
  93. }
  94. static bool needsStaticCast(const Expr *E) {
  95. if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
  96. if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
  97. ImpCast->getSubExpr()->getType()->isBooleanType()) {
  98. if (const auto *MemCall =
  99. dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
  100. if (const auto *MemDecl =
  101. dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
  102. if (MemDecl->isExplicit())
  103. return true;
  104. }
  105. }
  106. }
  107. }
  108. E = E->IgnoreImpCasts();
  109. return !E->getType()->isBooleanType();
  110. }
  111. static std::string compareExpressionToConstant(const ASTContext &Context,
  112. const Expr *E, bool Negated,
  113. const char *Constant) {
  114. E = E->IgnoreImpCasts();
  115. const std::string ExprText =
  116. (isa<BinaryOperator>(E) ? ("(" + getText(Context, *E) + ")")
  117. : getText(Context, *E))
  118. .str();
  119. return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
  120. }
  121. static std::string compareExpressionToNullPtr(const ASTContext &Context,
  122. const Expr *E, bool Negated) {
  123. const char *NullPtr = Context.getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
  124. return compareExpressionToConstant(Context, E, Negated, NullPtr);
  125. }
  126. static std::string compareExpressionToZero(const ASTContext &Context,
  127. const Expr *E, bool Negated) {
  128. return compareExpressionToConstant(Context, E, Negated, "0");
  129. }
  130. static std::string replacementExpression(const ASTContext &Context,
  131. bool Negated, const Expr *E) {
  132. E = E->IgnoreParenBaseCasts();
  133. if (const auto *EC = dyn_cast<ExprWithCleanups>(E))
  134. E = EC->getSubExpr();
  135. const bool NeedsStaticCast = needsStaticCast(E);
  136. if (Negated) {
  137. if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
  138. if (UnOp->getOpcode() == UO_LNot) {
  139. if (needsNullPtrComparison(UnOp->getSubExpr()))
  140. return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), true);
  141. if (needsZeroComparison(UnOp->getSubExpr()))
  142. return compareExpressionToZero(Context, UnOp->getSubExpr(), true);
  143. return replacementExpression(Context, false, UnOp->getSubExpr());
  144. }
  145. }
  146. if (needsNullPtrComparison(E))
  147. return compareExpressionToNullPtr(Context, E, false);
  148. if (needsZeroComparison(E))
  149. return compareExpressionToZero(Context, E, false);
  150. StringRef NegatedOperator;
  151. const Expr *LHS = nullptr;
  152. const Expr *RHS = nullptr;
  153. if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
  154. NegatedOperator = negatedOperator(BinOp);
  155. LHS = BinOp->getLHS();
  156. RHS = BinOp->getRHS();
  157. } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
  158. if (OpExpr->getNumArgs() == 2) {
  159. NegatedOperator = negatedOperator(OpExpr);
  160. LHS = OpExpr->getArg(0);
  161. RHS = OpExpr->getArg(1);
  162. }
  163. }
  164. if (!NegatedOperator.empty() && LHS && RHS)
  165. return (asBool((getText(Context, *LHS) + " " + NegatedOperator + " " +
  166. getText(Context, *RHS))
  167. .str(),
  168. NeedsStaticCast));
  169. StringRef Text = getText(Context, *E);
  170. if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
  171. return ("!(" + Text + ")").str();
  172. if (needsNullPtrComparison(E))
  173. return compareExpressionToNullPtr(Context, E, false);
  174. if (needsZeroComparison(E))
  175. return compareExpressionToZero(Context, E, false);
  176. return ("!" + asBool(Text, NeedsStaticCast));
  177. }
  178. if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
  179. if (UnOp->getOpcode() == UO_LNot) {
  180. if (needsNullPtrComparison(UnOp->getSubExpr()))
  181. return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), false);
  182. if (needsZeroComparison(UnOp->getSubExpr()))
  183. return compareExpressionToZero(Context, UnOp->getSubExpr(), false);
  184. }
  185. }
  186. if (needsNullPtrComparison(E))
  187. return compareExpressionToNullPtr(Context, E, true);
  188. if (needsZeroComparison(E))
  189. return compareExpressionToZero(Context, E, true);
  190. return asBool(getText(Context, *E), NeedsStaticCast);
  191. }
  192. static bool containsDiscardedTokens(const ASTContext &Context,
  193. CharSourceRange CharRange) {
  194. std::string ReplacementText =
  195. Lexer::getSourceText(CharRange, Context.getSourceManager(),
  196. Context.getLangOpts())
  197. .str();
  198. Lexer Lex(CharRange.getBegin(), Context.getLangOpts(), ReplacementText.data(),
  199. ReplacementText.data(),
  200. ReplacementText.data() + ReplacementText.size());
  201. Lex.SetCommentRetentionState(true);
  202. Token Tok;
  203. while (!Lex.LexFromRawLexer(Tok)) {
  204. if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
  205. return true;
  206. }
  207. return false;
  208. }
  209. class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
  210. using Base = RecursiveASTVisitor<Visitor>;
  211. public:
  212. Visitor(SimplifyBooleanExprCheck *Check, ASTContext &Context)
  213. : Check(Check), Context(Context) {}
  214. bool traverse() { return TraverseAST(Context); }
  215. static bool shouldIgnore(Stmt *S) {
  216. switch (S->getStmtClass()) {
  217. case Stmt::ImplicitCastExprClass:
  218. case Stmt::MaterializeTemporaryExprClass:
  219. case Stmt::CXXBindTemporaryExprClass:
  220. return true;
  221. default:
  222. return false;
  223. }
  224. }
  225. bool dataTraverseStmtPre(Stmt *S) {
  226. if (S && !shouldIgnore(S))
  227. StmtStack.push_back(S);
  228. return true;
  229. }
  230. bool dataTraverseStmtPost(Stmt *S) {
  231. if (S && !shouldIgnore(S)) {
  232. assert(StmtStack.back() == S);
  233. StmtStack.pop_back();
  234. }
  235. return true;
  236. }
  237. bool VisitBinaryOperator(const BinaryOperator *Op) const {
  238. Check->reportBinOp(Context, Op);
  239. return true;
  240. }
  241. // Extracts a bool if an expression is (true|false|!true|!false);
  242. static std::optional<bool> getAsBoolLiteral(const Expr *E, bool FilterMacro) {
  243. if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(E)) {
  244. if (FilterMacro && Bool->getBeginLoc().isMacroID())
  245. return std::nullopt;
  246. return Bool->getValue();
  247. }
  248. if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) {
  249. if (FilterMacro && UnaryOp->getBeginLoc().isMacroID())
  250. return std::nullopt;
  251. if (UnaryOp->getOpcode() == UO_LNot)
  252. if (std::optional<bool> Res = getAsBoolLiteral(
  253. UnaryOp->getSubExpr()->IgnoreImplicit(), FilterMacro))
  254. return !*Res;
  255. }
  256. return std::nullopt;
  257. }
  258. template <typename Node> struct NodeAndBool {
  259. const Node *Item = nullptr;
  260. bool Bool = false;
  261. operator bool() const { return Item != nullptr; }
  262. };
  263. using ExprAndBool = NodeAndBool<Expr>;
  264. using DeclAndBool = NodeAndBool<Decl>;
  265. /// Detect's return (true|false|!true|!false);
  266. static ExprAndBool parseReturnLiteralBool(const Stmt *S) {
  267. const auto *RS = dyn_cast<ReturnStmt>(S);
  268. if (!RS || !RS->getRetValue())
  269. return {};
  270. if (std::optional<bool> Ret =
  271. getAsBoolLiteral(RS->getRetValue()->IgnoreImplicit(), false)) {
  272. return {RS->getRetValue(), *Ret};
  273. }
  274. return {};
  275. }
  276. /// If \p S is not a \c CompoundStmt, applies F on \p S, otherwise if there is
  277. /// only 1 statement in the \c CompoundStmt, applies F on that single
  278. /// statement.
  279. template <typename Functor>
  280. static auto checkSingleStatement(Stmt *S, Functor F) -> decltype(F(S)) {
  281. if (auto *CS = dyn_cast<CompoundStmt>(S)) {
  282. if (CS->size() == 1)
  283. return F(CS->body_front());
  284. return {};
  285. }
  286. return F(S);
  287. }
  288. Stmt *parent() const {
  289. return StmtStack.size() < 2 ? nullptr : StmtStack[StmtStack.size() - 2];
  290. }
  291. bool VisitIfStmt(IfStmt *If) {
  292. // Skip any if's that have a condition var or an init statement, or are
  293. // "if consteval" statements.
  294. if (If->hasInitStorage() || If->hasVarStorage() || If->isConsteval())
  295. return true;
  296. /*
  297. * if (true) ThenStmt(); -> ThenStmt();
  298. * if (false) ThenStmt(); -> <Empty>;
  299. * if (false) ThenStmt(); else ElseStmt() -> ElseStmt();
  300. */
  301. Expr *Cond = If->getCond()->IgnoreImplicit();
  302. if (std::optional<bool> Bool = getAsBoolLiteral(Cond, true)) {
  303. if (*Bool)
  304. Check->replaceWithThenStatement(Context, If, Cond);
  305. else
  306. Check->replaceWithElseStatement(Context, If, Cond);
  307. }
  308. if (If->getElse()) {
  309. /*
  310. * if (Cond) return true; else return false; -> return Cond;
  311. * if (Cond) return false; else return true; -> return !Cond;
  312. */
  313. if (ExprAndBool ThenReturnBool =
  314. checkSingleStatement(If->getThen(), parseReturnLiteralBool)) {
  315. ExprAndBool ElseReturnBool =
  316. checkSingleStatement(If->getElse(), parseReturnLiteralBool);
  317. if (ElseReturnBool && ThenReturnBool.Bool != ElseReturnBool.Bool) {
  318. if (Check->ChainedConditionalReturn ||
  319. !isa_and_nonnull<IfStmt>(parent())) {
  320. Check->replaceWithReturnCondition(Context, If, ThenReturnBool.Item,
  321. ElseReturnBool.Bool);
  322. }
  323. }
  324. } else {
  325. /*
  326. * if (Cond) A = true; else A = false; -> A = Cond;
  327. * if (Cond) A = false; else A = true; -> A = !Cond;
  328. */
  329. Expr *Var = nullptr;
  330. SourceLocation Loc;
  331. auto VarBoolAssignmentMatcher = [&Var,
  332. &Loc](const Stmt *S) -> DeclAndBool {
  333. const auto *BO = dyn_cast<BinaryOperator>(S);
  334. if (!BO || BO->getOpcode() != BO_Assign)
  335. return {};
  336. std::optional<bool> RightasBool =
  337. getAsBoolLiteral(BO->getRHS()->IgnoreImplicit(), false);
  338. if (!RightasBool)
  339. return {};
  340. Expr *IgnImp = BO->getLHS()->IgnoreImplicit();
  341. if (!Var) {
  342. // We only need to track these for the Then branch.
  343. Loc = BO->getRHS()->getBeginLoc();
  344. Var = IgnImp;
  345. }
  346. if (auto *DRE = dyn_cast<DeclRefExpr>(IgnImp))
  347. return {DRE->getDecl(), *RightasBool};
  348. if (auto *ME = dyn_cast<MemberExpr>(IgnImp))
  349. return {ME->getMemberDecl(), *RightasBool};
  350. return {};
  351. };
  352. if (DeclAndBool ThenAssignment =
  353. checkSingleStatement(If->getThen(), VarBoolAssignmentMatcher)) {
  354. DeclAndBool ElseAssignment =
  355. checkSingleStatement(If->getElse(), VarBoolAssignmentMatcher);
  356. if (ElseAssignment.Item == ThenAssignment.Item &&
  357. ElseAssignment.Bool != ThenAssignment.Bool) {
  358. if (Check->ChainedConditionalAssignment ||
  359. !isa_and_nonnull<IfStmt>(parent())) {
  360. Check->replaceWithAssignment(Context, If, Var, Loc,
  361. ElseAssignment.Bool);
  362. }
  363. }
  364. }
  365. }
  366. }
  367. return true;
  368. }
  369. bool VisitConditionalOperator(ConditionalOperator *Cond) {
  370. /*
  371. * Condition ? true : false; -> Condition
  372. * Condition ? false : true; -> !Condition;
  373. */
  374. if (std::optional<bool> Then =
  375. getAsBoolLiteral(Cond->getTrueExpr()->IgnoreImplicit(), false)) {
  376. if (std::optional<bool> Else =
  377. getAsBoolLiteral(Cond->getFalseExpr()->IgnoreImplicit(), false)) {
  378. if (*Then != *Else)
  379. Check->replaceWithCondition(Context, Cond, *Else);
  380. }
  381. }
  382. return true;
  383. }
  384. bool VisitCompoundStmt(CompoundStmt *CS) {
  385. if (CS->size() < 2)
  386. return true;
  387. bool CurIf = false, PrevIf = false;
  388. for (auto First = CS->body_begin(), Second = std::next(First),
  389. End = CS->body_end();
  390. Second != End; ++Second, ++First) {
  391. PrevIf = CurIf;
  392. CurIf = isa<IfStmt>(*First);
  393. ExprAndBool TrailingReturnBool = parseReturnLiteralBool(*Second);
  394. if (!TrailingReturnBool)
  395. continue;
  396. if (CurIf) {
  397. /*
  398. * if (Cond) return true; return false; -> return Cond;
  399. * if (Cond) return false; return true; -> return !Cond;
  400. */
  401. auto *If = cast<IfStmt>(*First);
  402. if (!If->hasInitStorage() && !If->hasVarStorage() &&
  403. !If->isConsteval()) {
  404. ExprAndBool ThenReturnBool =
  405. checkSingleStatement(If->getThen(), parseReturnLiteralBool);
  406. if (ThenReturnBool &&
  407. ThenReturnBool.Bool != TrailingReturnBool.Bool) {
  408. if ((Check->ChainedConditionalReturn || !PrevIf) &&
  409. If->getElse() == nullptr) {
  410. Check->replaceCompoundReturnWithCondition(
  411. Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool,
  412. If, ThenReturnBool.Item);
  413. }
  414. }
  415. }
  416. } else if (isa<LabelStmt, CaseStmt, DefaultStmt>(*First)) {
  417. /*
  418. * (case X|label_X|default): if (Cond) return BoolLiteral;
  419. * return !BoolLiteral
  420. */
  421. Stmt *SubStmt =
  422. isa<LabelStmt>(*First) ? cast<LabelStmt>(*First)->getSubStmt()
  423. : isa<CaseStmt>(*First) ? cast<CaseStmt>(*First)->getSubStmt()
  424. : cast<DefaultStmt>(*First)->getSubStmt();
  425. auto *SubIf = dyn_cast<IfStmt>(SubStmt);
  426. if (SubIf && !SubIf->getElse() && !SubIf->hasInitStorage() &&
  427. !SubIf->hasVarStorage() && !SubIf->isConsteval()) {
  428. ExprAndBool ThenReturnBool =
  429. checkSingleStatement(SubIf->getThen(), parseReturnLiteralBool);
  430. if (ThenReturnBool &&
  431. ThenReturnBool.Bool != TrailingReturnBool.Bool) {
  432. Check->replaceCompoundReturnWithCondition(
  433. Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool,
  434. SubIf, ThenReturnBool.Item);
  435. }
  436. }
  437. }
  438. }
  439. return true;
  440. }
  441. static bool isUnaryLNot(const Expr *E) {
  442. return isa<UnaryOperator>(E) &&
  443. cast<UnaryOperator>(E)->getOpcode() == UO_LNot;
  444. }
  445. template <typename Functor>
  446. static bool checkEitherSide(const BinaryOperator *BO, Functor Func) {
  447. return Func(BO->getLHS()) || Func(BO->getRHS());
  448. }
  449. static bool nestedDemorgan(const Expr *E, unsigned NestingLevel) {
  450. const auto *BO = dyn_cast<BinaryOperator>(E->IgnoreUnlessSpelledInSource());
  451. if (!BO)
  452. return false;
  453. if (!BO->getType()->isBooleanType())
  454. return false;
  455. switch (BO->getOpcode()) {
  456. case BO_LT:
  457. case BO_GT:
  458. case BO_LE:
  459. case BO_GE:
  460. case BO_EQ:
  461. case BO_NE:
  462. return true;
  463. case BO_LAnd:
  464. case BO_LOr:
  465. if (checkEitherSide(BO, isUnaryLNot))
  466. return true;
  467. if (NestingLevel) {
  468. if (checkEitherSide(BO, [NestingLevel](const Expr *E) {
  469. return nestedDemorgan(E, NestingLevel - 1);
  470. }))
  471. return true;
  472. }
  473. return false;
  474. default:
  475. return false;
  476. }
  477. }
  478. bool TraverseUnaryOperator(UnaryOperator *Op) {
  479. if (!Check->SimplifyDeMorgan || Op->getOpcode() != UO_LNot)
  480. return Base::TraverseUnaryOperator(Op);
  481. Expr *SubImp = Op->getSubExpr()->IgnoreImplicit();
  482. auto *Parens = dyn_cast<ParenExpr>(SubImp);
  483. auto *BinaryOp =
  484. Parens
  485. ? dyn_cast<BinaryOperator>(Parens->getSubExpr()->IgnoreImplicit())
  486. : dyn_cast<BinaryOperator>(SubImp);
  487. if (!BinaryOp || !BinaryOp->isLogicalOp() ||
  488. !BinaryOp->getType()->isBooleanType())
  489. return Base::TraverseUnaryOperator(Op);
  490. if (Check->SimplifyDeMorganRelaxed ||
  491. checkEitherSide(BinaryOp, isUnaryLNot) ||
  492. checkEitherSide(BinaryOp,
  493. [](const Expr *E) { return nestedDemorgan(E, 1); })) {
  494. if (Check->reportDeMorgan(Context, Op, BinaryOp, !IsProcessing, parent(),
  495. Parens) &&
  496. !Check->areDiagsSelfContained()) {
  497. llvm::SaveAndRestore RAII(IsProcessing, true);
  498. return Base::TraverseUnaryOperator(Op);
  499. }
  500. }
  501. return Base::TraverseUnaryOperator(Op);
  502. }
  503. private:
  504. bool IsProcessing = false;
  505. SimplifyBooleanExprCheck *Check;
  506. SmallVector<Stmt *, 32> StmtStack;
  507. ASTContext &Context;
  508. };
  509. SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
  510. ClangTidyContext *Context)
  511. : ClangTidyCheck(Name, Context),
  512. ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)),
  513. ChainedConditionalAssignment(
  514. Options.get("ChainedConditionalAssignment", false)),
  515. SimplifyDeMorgan(Options.get("SimplifyDeMorgan", true)),
  516. SimplifyDeMorganRelaxed(Options.get("SimplifyDeMorganRelaxed", false)) {
  517. if (SimplifyDeMorganRelaxed && !SimplifyDeMorgan)
  518. configurationDiag("%0: 'SimplifyDeMorganRelaxed' cannot be enabled "
  519. "without 'SimplifyDeMorgan' enabled")
  520. << Name;
  521. }
  522. static bool containsBoolLiteral(const Expr *E) {
  523. if (!E)
  524. return false;
  525. E = E->IgnoreParenImpCasts();
  526. if (isa<CXXBoolLiteralExpr>(E))
  527. return true;
  528. if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
  529. return containsBoolLiteral(BinOp->getLHS()) ||
  530. containsBoolLiteral(BinOp->getRHS());
  531. if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
  532. return containsBoolLiteral(UnaryOp->getSubExpr());
  533. return false;
  534. }
  535. void SimplifyBooleanExprCheck::reportBinOp(const ASTContext &Context,
  536. const BinaryOperator *Op) {
  537. const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
  538. const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
  539. const CXXBoolLiteralExpr *Bool;
  540. const Expr *Other;
  541. if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr)
  542. Other = RHS;
  543. else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr)
  544. Other = LHS;
  545. else
  546. return;
  547. if (Bool->getBeginLoc().isMacroID())
  548. return;
  549. // FIXME: why do we need this?
  550. if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
  551. return;
  552. bool BoolValue = Bool->getValue();
  553. auto ReplaceWithExpression = [this, &Context, LHS, RHS,
  554. Bool](const Expr *ReplaceWith, bool Negated) {
  555. std::string Replacement =
  556. replacementExpression(Context, Negated, ReplaceWith);
  557. SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
  558. issueDiag(Context, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
  559. Replacement);
  560. };
  561. switch (Op->getOpcode()) {
  562. case BO_LAnd:
  563. if (BoolValue)
  564. // expr && true -> expr
  565. ReplaceWithExpression(Other, /*Negated=*/false);
  566. else
  567. // expr && false -> false
  568. ReplaceWithExpression(Bool, /*Negated=*/false);
  569. break;
  570. case BO_LOr:
  571. if (BoolValue)
  572. // expr || true -> true
  573. ReplaceWithExpression(Bool, /*Negated=*/false);
  574. else
  575. // expr || false -> expr
  576. ReplaceWithExpression(Other, /*Negated=*/false);
  577. break;
  578. case BO_EQ:
  579. // expr == true -> expr, expr == false -> !expr
  580. ReplaceWithExpression(Other, /*Negated=*/!BoolValue);
  581. break;
  582. case BO_NE:
  583. // expr != true -> !expr, expr != false -> expr
  584. ReplaceWithExpression(Other, /*Negated=*/BoolValue);
  585. break;
  586. default:
  587. break;
  588. }
  589. }
  590. void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  591. Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
  592. Options.store(Opts, "ChainedConditionalAssignment",
  593. ChainedConditionalAssignment);
  594. Options.store(Opts, "SimplifyDeMorgan", SimplifyDeMorgan);
  595. Options.store(Opts, "SimplifyDeMorganRelaxed", SimplifyDeMorganRelaxed);
  596. }
  597. void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
  598. Finder->addMatcher(translationUnitDecl(), this);
  599. }
  600. void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
  601. Visitor(this, *Result.Context).traverse();
  602. }
  603. void SimplifyBooleanExprCheck::issueDiag(const ASTContext &Context,
  604. SourceLocation Loc,
  605. StringRef Description,
  606. SourceRange ReplacementRange,
  607. StringRef Replacement) {
  608. CharSourceRange CharRange =
  609. Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
  610. Context.getSourceManager(), getLangOpts());
  611. DiagnosticBuilder Diag = diag(Loc, Description);
  612. if (!containsDiscardedTokens(Context, CharRange))
  613. Diag << FixItHint::CreateReplacement(CharRange, Replacement);
  614. }
  615. void SimplifyBooleanExprCheck::replaceWithThenStatement(
  616. const ASTContext &Context, const IfStmt *IfStatement,
  617. const Expr *BoolLiteral) {
  618. issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
  619. IfStatement->getSourceRange(),
  620. getText(Context, *IfStatement->getThen()));
  621. }
  622. void SimplifyBooleanExprCheck::replaceWithElseStatement(
  623. const ASTContext &Context, const IfStmt *IfStatement,
  624. const Expr *BoolLiteral) {
  625. const Stmt *ElseStatement = IfStatement->getElse();
  626. issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
  627. IfStatement->getSourceRange(),
  628. ElseStatement ? getText(Context, *ElseStatement) : "");
  629. }
  630. void SimplifyBooleanExprCheck::replaceWithCondition(
  631. const ASTContext &Context, const ConditionalOperator *Ternary,
  632. bool Negated) {
  633. std::string Replacement =
  634. replacementExpression(Context, Negated, Ternary->getCond());
  635. issueDiag(Context, Ternary->getTrueExpr()->getBeginLoc(),
  636. "redundant boolean literal in ternary expression result",
  637. Ternary->getSourceRange(), Replacement);
  638. }
  639. void SimplifyBooleanExprCheck::replaceWithReturnCondition(
  640. const ASTContext &Context, const IfStmt *If, const Expr *BoolLiteral,
  641. bool Negated) {
  642. StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
  643. std::string Condition =
  644. replacementExpression(Context, Negated, If->getCond());
  645. std::string Replacement = ("return " + Condition + Terminator).str();
  646. SourceLocation Start = BoolLiteral->getBeginLoc();
  647. issueDiag(Context, Start, SimplifyConditionalReturnDiagnostic,
  648. If->getSourceRange(), Replacement);
  649. }
  650. void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
  651. const ASTContext &Context, const ReturnStmt *Ret, bool Negated,
  652. const IfStmt *If, const Expr *ThenReturn) {
  653. const std::string Replacement =
  654. "return " + replacementExpression(Context, Negated, If->getCond());
  655. issueDiag(Context, ThenReturn->getBeginLoc(),
  656. SimplifyConditionalReturnDiagnostic,
  657. SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
  658. }
  659. void SimplifyBooleanExprCheck::replaceWithAssignment(const ASTContext &Context,
  660. const IfStmt *IfAssign,
  661. const Expr *Var,
  662. SourceLocation Loc,
  663. bool Negated) {
  664. SourceRange Range = IfAssign->getSourceRange();
  665. StringRef VariableName = getText(Context, *Var);
  666. StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
  667. std::string Condition =
  668. replacementExpression(Context, Negated, IfAssign->getCond());
  669. std::string Replacement =
  670. (VariableName + " = " + Condition + Terminator).str();
  671. issueDiag(Context, Loc, "redundant boolean literal in conditional assignment",
  672. Range, Replacement);
  673. }
  674. /// Swaps a \c BinaryOperator opcode from `&&` to `||` or vice-versa.
  675. static bool flipDemorganOperator(llvm::SmallVectorImpl<FixItHint> &Output,
  676. const BinaryOperator *BO) {
  677. assert(BO->isLogicalOp());
  678. if (BO->getOperatorLoc().isMacroID())
  679. return true;
  680. Output.push_back(FixItHint::CreateReplacement(
  681. BO->getOperatorLoc(), BO->getOpcode() == BO_LAnd ? "||" : "&&"));
  682. return false;
  683. }
  684. static BinaryOperatorKind getDemorganFlippedOperator(BinaryOperatorKind BO) {
  685. assert(BinaryOperator::isLogicalOp(BO));
  686. return BO == BO_LAnd ? BO_LOr : BO_LAnd;
  687. }
  688. static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
  689. const ASTContext &Ctx, const Expr *E,
  690. std::optional<BinaryOperatorKind> OuterBO);
  691. /// Inverts \p BinOp, Removing \p Parens if they exist and are safe to remove.
  692. /// returns \c true if there is any issue building the Fixes, \c false
  693. /// otherwise.
  694. static bool
  695. flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
  696. const ASTContext &Ctx, const BinaryOperator *BinOp,
  697. std::optional<BinaryOperatorKind> OuterBO,
  698. const ParenExpr *Parens = nullptr) {
  699. switch (BinOp->getOpcode()) {
  700. case BO_LAnd:
  701. case BO_LOr: {
  702. // if we have 'a && b' or 'a || b', use demorgan to flip it to '!a || !b'
  703. // or '!a && !b'.
  704. if (flipDemorganOperator(Fixes, BinOp))
  705. return true;
  706. auto NewOp = getDemorganFlippedOperator(BinOp->getOpcode());
  707. if (OuterBO) {
  708. // The inner parens are technically needed in a fix for
  709. // `!(!A1 && !(A2 || A3)) -> (A1 || (A2 && A3))`,
  710. // however this would trip the LogicalOpParentheses warning.
  711. // FIXME: Make this user configurable or detect if that warning is
  712. // enabled.
  713. constexpr bool LogicalOpParentheses = true;
  714. if (((*OuterBO == NewOp) || (!LogicalOpParentheses &&
  715. (*OuterBO == BO_LOr && NewOp == BO_LAnd))) &&
  716. Parens) {
  717. if (!Parens->getLParen().isMacroID() &&
  718. !Parens->getRParen().isMacroID()) {
  719. Fixes.push_back(FixItHint::CreateRemoval(Parens->getLParen()));
  720. Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen()));
  721. }
  722. }
  723. if (*OuterBO == BO_LAnd && NewOp == BO_LOr && !Parens) {
  724. Fixes.push_back(FixItHint::CreateInsertion(BinOp->getBeginLoc(), "("));
  725. Fixes.push_back(FixItHint::CreateInsertion(
  726. Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0,
  727. Ctx.getSourceManager(),
  728. Ctx.getLangOpts()),
  729. ")"));
  730. }
  731. }
  732. if (flipDemorganSide(Fixes, Ctx, BinOp->getLHS(), NewOp) ||
  733. flipDemorganSide(Fixes, Ctx, BinOp->getRHS(), NewOp))
  734. return true;
  735. return false;
  736. };
  737. case BO_LT:
  738. case BO_GT:
  739. case BO_LE:
  740. case BO_GE:
  741. case BO_EQ:
  742. case BO_NE:
  743. // For comparison operators, just negate the comparison.
  744. if (BinOp->getOperatorLoc().isMacroID())
  745. return true;
  746. Fixes.push_back(FixItHint::CreateReplacement(
  747. BinOp->getOperatorLoc(),
  748. BinaryOperator::getOpcodeStr(
  749. BinaryOperator::negateComparisonOp(BinOp->getOpcode()))));
  750. return false;
  751. default:
  752. // for any other binary operator, just use logical not and wrap in
  753. // parens.
  754. if (Parens) {
  755. if (Parens->getBeginLoc().isMacroID())
  756. return true;
  757. Fixes.push_back(FixItHint::CreateInsertion(Parens->getBeginLoc(), "!"));
  758. } else {
  759. if (BinOp->getBeginLoc().isMacroID() || BinOp->getEndLoc().isMacroID())
  760. return true;
  761. Fixes.append({FixItHint::CreateInsertion(BinOp->getBeginLoc(), "!("),
  762. FixItHint::CreateInsertion(
  763. Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0,
  764. Ctx.getSourceManager(),
  765. Ctx.getLangOpts()),
  766. ")")});
  767. }
  768. break;
  769. }
  770. return false;
  771. }
  772. static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
  773. const ASTContext &Ctx, const Expr *E,
  774. std::optional<BinaryOperatorKind> OuterBO) {
  775. if (isa<UnaryOperator>(E) && cast<UnaryOperator>(E)->getOpcode() == UO_LNot) {
  776. // if we have a not operator, '!a', just remove the '!'.
  777. if (cast<UnaryOperator>(E)->getOperatorLoc().isMacroID())
  778. return true;
  779. Fixes.push_back(
  780. FixItHint::CreateRemoval(cast<UnaryOperator>(E)->getOperatorLoc()));
  781. return false;
  782. }
  783. if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
  784. return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO);
  785. }
  786. if (const auto *Paren = dyn_cast<ParenExpr>(E)) {
  787. if (const auto *BinOp = dyn_cast<BinaryOperator>(Paren->getSubExpr())) {
  788. return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren);
  789. }
  790. }
  791. // Fallback case just insert a logical not operator.
  792. if (E->getBeginLoc().isMacroID())
  793. return true;
  794. Fixes.push_back(FixItHint::CreateInsertion(E->getBeginLoc(), "!"));
  795. return false;
  796. }
  797. static bool shouldRemoveParens(const Stmt *Parent,
  798. BinaryOperatorKind NewOuterBinary,
  799. const ParenExpr *Parens) {
  800. if (!Parens)
  801. return false;
  802. if (!Parent)
  803. return true;
  804. switch (Parent->getStmtClass()) {
  805. case Stmt::BinaryOperatorClass: {
  806. const auto *BO = cast<BinaryOperator>(Parent);
  807. if (BO->isAssignmentOp())
  808. return true;
  809. if (BO->isCommaOp())
  810. return true;
  811. if (BO->getOpcode() == NewOuterBinary)
  812. return true;
  813. return false;
  814. }
  815. case Stmt::UnaryOperatorClass:
  816. case Stmt::CXXRewrittenBinaryOperatorClass:
  817. return false;
  818. default:
  819. return true;
  820. }
  821. }
  822. bool SimplifyBooleanExprCheck::reportDeMorgan(const ASTContext &Context,
  823. const UnaryOperator *Outer,
  824. const BinaryOperator *Inner,
  825. bool TryOfferFix,
  826. const Stmt *Parent,
  827. const ParenExpr *Parens) {
  828. assert(Outer);
  829. assert(Inner);
  830. assert(Inner->isLogicalOp());
  831. auto Diag =
  832. diag(Outer->getBeginLoc(),
  833. "boolean expression can be simplified by DeMorgan's theorem");
  834. Diag << Outer->getSourceRange();
  835. // If we have already fixed this with a previous fix, don't attempt any fixes
  836. if (!TryOfferFix)
  837. return false;
  838. if (Outer->getOperatorLoc().isMacroID())
  839. return false;
  840. SmallVector<FixItHint> Fixes;
  841. auto NewOpcode = getDemorganFlippedOperator(Inner->getOpcode());
  842. if (shouldRemoveParens(Parent, NewOpcode, Parens)) {
  843. Fixes.push_back(FixItHint::CreateRemoval(
  844. SourceRange(Outer->getOperatorLoc(), Parens->getLParen())));
  845. Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen()));
  846. } else {
  847. Fixes.push_back(FixItHint::CreateRemoval(Outer->getOperatorLoc()));
  848. }
  849. if (flipDemorganOperator(Fixes, Inner))
  850. return false;
  851. if (flipDemorganSide(Fixes, Context, Inner->getLHS(), NewOpcode) ||
  852. flipDemorganSide(Fixes, Context, Inner->getRHS(), NewOpcode))
  853. return false;
  854. Diag << Fixes;
  855. return true;
  856. }
  857. } // namespace clang::tidy::readability