blob: 129123b353480fbe09496724cd50fdd97cc05b03 [file] [log] [blame]
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +00001//===--- ImplicitBoolCastCheck.cpp - clang-tidy----------------------------===//
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 "ImplicitBoolCastCheck.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
Alexander Kornienkocbe8d162017-05-04 15:34:23 +000014#include "clang/Tooling/FixIt.h"
Alexander Kornienko4e001b52017-04-29 12:06:45 +000015#include <queue>
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000016
17using namespace clang::ast_matchers;
18
19namespace clang {
20namespace tidy {
Etienne Bergeron456177b2016-05-02 18:00:29 +000021namespace readability {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000022
23namespace {
24
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000025AST_MATCHER(Stmt, isMacroExpansion) {
26 SourceManager &SM = Finder->getASTContext().getSourceManager();
27 SourceLocation Loc = Node.getLocStart();
28 return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
29}
30
31bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
32 SourceManager &SM = Context.getSourceManager();
33 const LangOptions &LO = Context.getLangOpts();
34 SourceLocation Loc = Statement->getLocStart();
35 return SM.isMacroBodyExpansion(Loc) &&
Alexander Kornienkocbe8d162017-05-04 15:34:23 +000036 Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000037}
38
39AST_MATCHER(Stmt, isNULLMacroExpansion) {
40 return isNULLMacroExpansion(&Node, Finder->getASTContext());
41}
42
43ast_matchers::internal::Matcher<Expr> createExceptionCasesMatcher() {
44 return expr(anyOf(hasParent(explicitCastExpr()),
45 allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
46 isInTemplateInstantiation(),
47 hasAncestor(functionTemplateDecl())));
48}
49
50StatementMatcher createImplicitCastFromBoolMatcher() {
51 return implicitCastExpr(
52 unless(createExceptionCasesMatcher()),
53 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
54 // Prior to C++11 cast from bool literal to pointer was allowed.
55 allOf(anyOf(hasCastKind(CK_NullToPointer),
56 hasCastKind(CK_NullToMemberPointer)),
57 hasSourceExpression(cxxBoolLiteral()))),
Etienne Bergeron9d265992016-04-21 16:57:56 +000058 hasSourceExpression(expr(hasType(qualType(booleanType())))));
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000059}
60
Alexander Kornienkocbe8d162017-05-04 15:34:23 +000061StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
62 QualType Type,
63 ASTContext &Context) {
64 switch (CastExprKind) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000065 case CK_IntegralToBoolean:
Alexander Kornienkocbe8d162017-05-04 15:34:23 +000066 return Type->isUnsignedIntegerType() ? "0u" : "0";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000067
68 case CK_FloatingToBoolean:
Alexander Kornienkocbe8d162017-05-04 15:34:23 +000069 return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000070
71 case CK_PointerToBoolean:
72 case CK_MemberPointerToBoolean: // Fall-through on purpose.
73 return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0";
74
75 default:
Benjamin Kramer6d505c02015-10-25 22:03:00 +000076 llvm_unreachable("Unexpected cast kind");
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000077 }
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000078}
79
80bool isUnaryLogicalNotOperator(const Stmt *Statement) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +000081 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
82 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +000083}
84
85bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) {
86 switch (OperatorKind) {
87 case OO_New:
88 case OO_Delete: // Fall-through on purpose.
89 case OO_Array_New:
90 case OO_Array_Delete:
91 case OO_ArrowStar:
92 case OO_Arrow:
93 case OO_Call:
94 case OO_Subscript:
95 return false;
96
97 default:
98 return true;
99 }
100}
101
102bool areParensNeededForStatement(const Stmt *Statement) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000103 if (const auto *OperatorCall = dyn_cast<CXXOperatorCallExpr>(Statement)) {
104 return areParensNeededForOverloadedOperator(OperatorCall->getOperator());
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000105 }
106
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000107 return isa<BinaryOperator>(Statement) || isa<UnaryOperator>(Statement);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000108}
109
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000110void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
111 const ImplicitCastExpr *Cast, const Stmt *Parent,
112 ASTContext &Context) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000113 // In case of expressions like (! integer), we should remove the redundant not
114 // operator and use inverted comparison (integer == 0).
115 bool InvertComparison =
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000116 Parent != nullptr && isUnaryLogicalNotOperator(Parent);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000117 if (InvertComparison) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000118 SourceLocation ParentStartLoc = Parent->getLocStart();
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000119 SourceLocation ParentEndLoc =
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000120 cast<UnaryOperator>(Parent)->getSubExpr()->getLocStart();
121 Diag << FixItHint::CreateRemoval(
122 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000123
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000124 Parent = Context.getParents(*Parent)[0].get<Stmt>();
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000125 }
126
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000127 const Expr *SubExpr = Cast->getSubExpr();
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000128
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000129 bool NeedInnerParens = areParensNeededForStatement(SubExpr);
130 bool NeedOuterParens =
131 Parent != nullptr && areParensNeededForStatement(Parent);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000132
133 std::string StartLocInsertion;
134
135 if (NeedOuterParens) {
136 StartLocInsertion += "(";
137 }
138 if (NeedInnerParens) {
139 StartLocInsertion += "(";
140 }
141
142 if (!StartLocInsertion.empty()) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000143 Diag << FixItHint::CreateInsertion(Cast->getLocStart(), StartLocInsertion);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000144 }
145
146 std::string EndLocInsertion;
147
148 if (NeedInnerParens) {
149 EndLocInsertion += ")";
150 }
151
152 if (InvertComparison) {
153 EndLocInsertion += " == ";
154 } else {
155 EndLocInsertion += " != ";
156 }
157
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000158 EndLocInsertion += getZeroLiteralToCompareWithForType(
159 Cast->getCastKind(), SubExpr->getType(), Context);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000160
161 if (NeedOuterParens) {
162 EndLocInsertion += ")";
163 }
164
165 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000166 Cast->getLocEnd(), 0, Context.getSourceManager(), Context.getLangOpts());
167 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000168}
169
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000170StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,
171 ASTContext &Context) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000172 if (isNULLMacroExpansion(Expression, Context)) {
173 return "false";
174 }
175
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000176 if (const auto *IntLit = dyn_cast<IntegerLiteral>(Expression)) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000177 return (IntLit->getValue() == 0) ? "false" : "true";
178 }
179
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000180 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000181 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
182 FloatLitAbsValue.clearSign();
183 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
184 }
185
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000186 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000187 return (CharLit->getValue() == 0) ? "false" : "true";
188 }
189
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000190 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000191 return "true";
192 }
193
194 return StringRef();
195}
196
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000197void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
198 const ImplicitCastExpr *Cast,
199 ASTContext &Context, StringRef OtherType) {
200 const Expr *SubExpr = Cast->getSubExpr();
201 bool NeedParens = !isa<ParenExpr>(SubExpr);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000202
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000203 Diag << FixItHint::CreateInsertion(
204 Cast->getLocStart(),
205 (Twine("static_cast<") + OtherType + ">" + (NeedParens ? "(" : ""))
206 .str());
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000207
208 if (NeedParens) {
209 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000210 Cast->getLocEnd(), 0, Context.getSourceManager(),
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000211 Context.getLangOpts());
212
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000213 Diag << FixItHint::CreateInsertion(EndLoc, ")");
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000214 }
215}
216
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000217StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,
218 QualType DestType, ASTContext &Context) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000219 // Prior to C++11, false literal could be implicitly converted to pointer.
220 if (!Context.getLangOpts().CPlusPlus11 &&
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000221 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
222 BoolLiteral->getValue() == false) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000223 return "0";
224 }
225
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000226 if (DestType->isFloatingType()) {
227 if (Context.hasSameType(DestType, Context.FloatTy)) {
228 return BoolLiteral->getValue() ? "1.0f" : "0.0f";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000229 }
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000230 return BoolLiteral->getValue() ? "1.0" : "0.0";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000231 }
232
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000233 if (DestType->isUnsignedIntegerType()) {
234 return BoolLiteral->getValue() ? "1u" : "0u";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000235 }
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000236 return BoolLiteral->getValue() ? "1" : "0";
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000237}
238
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000239bool isAllowedConditionalCast(const ImplicitCastExpr *Cast,
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000240 ASTContext &Context) {
Alexander Kornienko4e001b52017-04-29 12:06:45 +0000241 std::queue<const Stmt *> Q;
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000242 Q.push(Cast);
Alexander Kornienko4e001b52017-04-29 12:06:45 +0000243 while (!Q.empty()) {
244 for (const auto &N : Context.getParents(*Q.front())) {
245 const Stmt *S = N.get<Stmt>();
246 if (!S)
247 return false;
248 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S))
249 return true;
250 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000251 isUnaryLogicalNotOperator(S) ||
Alexander Kornienko4e001b52017-04-29 12:06:45 +0000252 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
253 Q.push(S);
254 } else {
255 return false;
256 }
257 }
258 Q.pop();
259 }
260 return false;
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000261}
262
263} // anonymous namespace
264
Haojian Wu80c1c9f2016-08-16 11:15:05 +0000265ImplicitBoolCastCheck::ImplicitBoolCastCheck(StringRef Name,
266 ClangTidyContext *Context)
267 : ClangTidyCheck(Name, Context),
268 AllowConditionalIntegerCasts(
269 Options.get("AllowConditionalIntegerCasts", false)),
270 AllowConditionalPointerCasts(
271 Options.get("AllowConditionalPointerCasts", false)) {}
272
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000273void ImplicitBoolCastCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Haojian Wu80c1c9f2016-08-16 11:15:05 +0000274 Options.store(Opts, "AllowConditionalIntegerCasts",
275 AllowConditionalIntegerCasts);
276 Options.store(Opts, "AllowConditionalPointerCasts",
277 AllowConditionalPointerCasts);
278}
279
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000280void ImplicitBoolCastCheck::registerMatchers(MatchFinder *Finder) {
281 // This check doesn't make much sense if we run it on language without
282 // built-in bool support.
283 if (!getLangOpts().Bool) {
284 return;
285 }
286
287 Finder->addMatcher(
288 implicitCastExpr(
289 // Exclude cases common to implicit cast to and from bool.
290 unless(createExceptionCasesMatcher()),
291 // Exclude case of using if or while statements with variable
292 // declaration, e.g.:
293 // if (int var = functionCall()) {}
294 unless(
295 hasParent(stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
296 anyOf(hasCastKind(CK_IntegralToBoolean),
297 hasCastKind(CK_FloatingToBoolean),
298 hasCastKind(CK_PointerToBoolean),
299 hasCastKind(CK_MemberPointerToBoolean)),
300 // Retrive also parent statement, to check if we need additional
301 // parens in replacement.
302 anyOf(hasParent(stmt().bind("parentStmt")), anything()))
303 .bind("implicitCastToBool"),
304 this);
305
306 Finder->addMatcher(
307 implicitCastExpr(
308 createImplicitCastFromBoolMatcher(),
309 // Exclude comparisons of bools, as they are always cast to integers
310 // in such context:
311 // bool_expr_a == bool_expr_b
312 // bool_expr_a != bool_expr_b
313 unless(hasParent(binaryOperator(
314 anyOf(hasOperatorName("=="), hasOperatorName("!=")),
315 hasLHS(createImplicitCastFromBoolMatcher()),
316 hasRHS(createImplicitCastFromBoolMatcher())))),
317 // Check also for nested casts, for example: bool -> int -> float.
318 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
319 anything()))
320 .bind("implicitCastFromBool"),
321 this);
322}
323
324void ImplicitBoolCastCheck::check(const MatchFinder::MatchResult &Result) {
325 if (const auto *CastToBool =
326 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000327 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
328 return handleCastToBool(CastToBool, Parent, *Result.Context);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000329 }
330
331 if (const auto *CastFromBool =
332 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000333 const auto *NextImplicitCast =
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000334 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000335 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000336 }
337}
338
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000339void ImplicitBoolCastCheck::handleCastToBool(const ImplicitCastExpr *Cast,
340 const Stmt *Parent,
341 ASTContext &Context) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000342 if (AllowConditionalPointerCasts &&
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000343 (Cast->getCastKind() == CK_PointerToBoolean ||
344 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
345 isAllowedConditionalCast(Cast, Context)) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000346 return;
347 }
348
349 if (AllowConditionalIntegerCasts &&
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000350 Cast->getCastKind() == CK_IntegralToBoolean &&
351 isAllowedConditionalCast(Cast, Context)) {
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000352 return;
353 }
354
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000355 auto Diag = diag(Cast->getLocStart(), "implicit cast %0 -> bool")
356 << Cast->getSubExpr()->getType();
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000357
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000358 StringRef EquivalentLiteral =
359 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
360 if (!EquivalentLiteral.empty()) {
361 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000362 } else {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000363 fixGenericExprCastToBool(Diag, Cast, Parent, Context);
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000364 }
365}
366
367void ImplicitBoolCastCheck::handleCastFromBool(
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000368 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000369 ASTContext &Context) {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000370 QualType DestType =
371 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
372 auto Diag = diag(Cast->getLocStart(), "implicit cast bool -> %0") << DestType;
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000373
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000374 if (const auto *BoolLiteral =
375 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr())) {
376 Diag << tooling::fixit::createReplacement(
377 *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context));
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000378 } else {
Alexander Kornienkocbe8d162017-05-04 15:34:23 +0000379 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000380 }
381}
382
Etienne Bergeron456177b2016-05-02 18:00:29 +0000383} // namespace readability
Piotr Dziwinski7f1b5092015-10-25 15:31:25 +0000384} // namespace tidy
385} // namespace clang