blob: c9a1182ac597795980f8d93bb9409b7a2f3c8a4d [file] [log] [blame]
Alexander Kornienkof5e72b02015-04-10 19:26:43 +00001//===--- 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
16using namespace clang::ast_matchers;
17
18namespace clang {
19namespace tidy {
20namespace readability {
21
22namespace {
23
24StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) {
25 return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
26 *Result.SourceManager,
27 Result.Context->getLangOpts());
28}
29
30template <typename T>
31StringRef getText(const MatchFinder::MatchResult &Result, T &Node) {
32 return getText(Result, Node.getSourceRange());
33}
34
35const char RightExpressionId[] = "bool-op-expr-yields-expr";
36const char LeftExpressionId[] = "expr-op-bool-yields-expr";
37const char NegatedRightExpressionId[] = "bool-op-expr-yields-not-expr";
38const char NegatedLeftExpressionId[] = "expr-op-bool-yields-not-expr";
39const char ConditionThenStmtId[] = "if-bool-yields-then";
40const char ConditionElseStmtId[] = "if-bool-yields-else";
41const char TernaryId[] = "ternary-bool-yields-condition";
42const char TernaryNegatedId[] = "ternary-bool-yields-not-condition";
43const char IfReturnsBoolId[] = "if-return";
44const char IfReturnsNotBoolId[] = "if-not-return";
45const char ThenLiteralId[] = "then-literal";
46const char IfAssignVariableId[] = "if-assign-lvalue";
47const char IfAssignLocId[] = "if-assign-loc";
48const char IfAssignBoolId[] = "if-assign";
49const char IfAssignNotBoolId[] = "if-assign-not";
50const char IfAssignObjId[] = "if-assign-obj";
51
52const char IfStmtId[] = "if";
53const char LHSId[] = "lhs-expr";
54const char RHSId[] = "rhs-expr";
55
56const char SimplifyOperatorDiagnostic[] =
57 "redundant boolean literal supplied to boolean operator";
58const char SimplifyConditionDiagnostic[] =
59 "redundant boolean literal in if statement condition";
60
61const 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
70internal::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
77bool 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
86std::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
90StringRef 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
101std::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
125void 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
138void 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
152void 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
166void 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
180void 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
189void 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
200void 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
208void 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
228void 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
262void 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
302void 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
315void 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
324void 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
335void 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
345void 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
356void 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