1 //===--- ImplicitBoolConversionCheck.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
9 #include "ImplicitBoolConversionCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
13 #include "clang/Tooling/FixIt.h"
14 #include <queue>
15
16 using namespace clang::ast_matchers;
17
18 namespace clang {
19 namespace tidy {
20 namespace readability {
21
22 namespace {
23
AST_MATCHER(Stmt,isMacroExpansion)24 AST_MATCHER(Stmt, isMacroExpansion) {
25 SourceManager &SM = Finder->getASTContext().getSourceManager();
26 SourceLocation Loc = Node.getBeginLoc();
27 return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
28 }
29
isNULLMacroExpansion(const Stmt * Statement,ASTContext & Context)30 bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
31 SourceManager &SM = Context.getSourceManager();
32 const LangOptions &LO = Context.getLangOpts();
33 SourceLocation Loc = Statement->getBeginLoc();
34 return SM.isMacroBodyExpansion(Loc) &&
35 Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
36 }
37
AST_MATCHER(Stmt,isNULLMacroExpansion)38 AST_MATCHER(Stmt, isNULLMacroExpansion) {
39 return isNULLMacroExpansion(&Node, Finder->getASTContext());
40 }
41
getZeroLiteralToCompareWithForType(CastKind CastExprKind,QualType Type,ASTContext & Context)42 StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
43 QualType Type,
44 ASTContext &Context) {
45 switch (CastExprKind) {
46 case CK_IntegralToBoolean:
47 return Type->isUnsignedIntegerType() ? "0u" : "0";
48
49 case CK_FloatingToBoolean:
50 return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0";
51
52 case CK_PointerToBoolean:
53 case CK_MemberPointerToBoolean: // Fall-through on purpose.
54 return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0";
55
56 default:
57 llvm_unreachable("Unexpected cast kind");
58 }
59 }
60
isUnaryLogicalNotOperator(const Stmt * Statement)61 bool isUnaryLogicalNotOperator(const Stmt *Statement) {
62 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
63 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
64 }
65
areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind)66 bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) {
67 switch (OperatorKind) {
68 case OO_New:
69 case OO_Delete: // Fall-through on purpose.
70 case OO_Array_New:
71 case OO_Array_Delete:
72 case OO_ArrowStar:
73 case OO_Arrow:
74 case OO_Call:
75 case OO_Subscript:
76 return false;
77
78 default:
79 return true;
80 }
81 }
82
areParensNeededForStatement(const Stmt * Statement)83 bool areParensNeededForStatement(const Stmt *Statement) {
84 if (const auto *OperatorCall = dyn_cast<CXXOperatorCallExpr>(Statement)) {
85 return areParensNeededForOverloadedOperator(OperatorCall->getOperator());
86 }
87
88 return isa<BinaryOperator>(Statement) || isa<UnaryOperator>(Statement);
89 }
90
fixGenericExprCastToBool(DiagnosticBuilder & Diag,const ImplicitCastExpr * Cast,const Stmt * Parent,ASTContext & Context)91 void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
92 const ImplicitCastExpr *Cast, const Stmt *Parent,
93 ASTContext &Context) {
94 // In case of expressions like (! integer), we should remove the redundant not
95 // operator and use inverted comparison (integer == 0).
96 bool InvertComparison =
97 Parent != nullptr && isUnaryLogicalNotOperator(Parent);
98 if (InvertComparison) {
99 SourceLocation ParentStartLoc = Parent->getBeginLoc();
100 SourceLocation ParentEndLoc =
101 cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();
102 Diag << FixItHint::CreateRemoval(
103 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
104
105 Parent = Context.getParents(*Parent)[0].get<Stmt>();
106 }
107
108 const Expr *SubExpr = Cast->getSubExpr();
109
110 bool NeedInnerParens = areParensNeededForStatement(SubExpr);
111 bool NeedOuterParens =
112 Parent != nullptr && areParensNeededForStatement(Parent);
113
114 std::string StartLocInsertion;
115
116 if (NeedOuterParens) {
117 StartLocInsertion += "(";
118 }
119 if (NeedInnerParens) {
120 StartLocInsertion += "(";
121 }
122
123 if (!StartLocInsertion.empty()) {
124 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
125 }
126
127 std::string EndLocInsertion;
128
129 if (NeedInnerParens) {
130 EndLocInsertion += ")";
131 }
132
133 if (InvertComparison) {
134 EndLocInsertion += " == ";
135 } else {
136 EndLocInsertion += " != ";
137 }
138
139 EndLocInsertion += getZeroLiteralToCompareWithForType(
140 Cast->getCastKind(), SubExpr->getType(), Context);
141
142 if (NeedOuterParens) {
143 EndLocInsertion += ")";
144 }
145
146 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
147 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
148 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
149 }
150
getEquivalentBoolLiteralForExpr(const Expr * Expression,ASTContext & Context)151 StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,
152 ASTContext &Context) {
153 if (isNULLMacroExpansion(Expression, Context)) {
154 return "false";
155 }
156
157 if (const auto *IntLit = dyn_cast<IntegerLiteral>(Expression)) {
158 return (IntLit->getValue() == 0) ? "false" : "true";
159 }
160
161 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
162 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
163 FloatLitAbsValue.clearSign();
164 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
165 }
166
167 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
168 return (CharLit->getValue() == 0) ? "false" : "true";
169 }
170
171 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
172 return "true";
173 }
174
175 return StringRef();
176 }
177
fixGenericExprCastFromBool(DiagnosticBuilder & Diag,const ImplicitCastExpr * Cast,ASTContext & Context,StringRef OtherType)178 void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
179 const ImplicitCastExpr *Cast,
180 ASTContext &Context, StringRef OtherType) {
181 const Expr *SubExpr = Cast->getSubExpr();
182 bool NeedParens = !isa<ParenExpr>(SubExpr);
183
184 Diag << FixItHint::CreateInsertion(
185 Cast->getBeginLoc(),
186 (Twine("static_cast<") + OtherType + ">" + (NeedParens ? "(" : ""))
187 .str());
188
189 if (NeedParens) {
190 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
191 Cast->getEndLoc(), 0, Context.getSourceManager(),
192 Context.getLangOpts());
193
194 Diag << FixItHint::CreateInsertion(EndLoc, ")");
195 }
196 }
197
getEquivalentForBoolLiteral(const CXXBoolLiteralExpr * BoolLiteral,QualType DestType,ASTContext & Context)198 StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,
199 QualType DestType, ASTContext &Context) {
200 // Prior to C++11, false literal could be implicitly converted to pointer.
201 if (!Context.getLangOpts().CPlusPlus11 &&
202 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
203 BoolLiteral->getValue() == false) {
204 return "0";
205 }
206
207 if (DestType->isFloatingType()) {
208 if (Context.hasSameType(DestType, Context.FloatTy)) {
209 return BoolLiteral->getValue() ? "1.0f" : "0.0f";
210 }
211 return BoolLiteral->getValue() ? "1.0" : "0.0";
212 }
213
214 if (DestType->isUnsignedIntegerType()) {
215 return BoolLiteral->getValue() ? "1u" : "0u";
216 }
217 return BoolLiteral->getValue() ? "1" : "0";
218 }
219
isCastAllowedInCondition(const ImplicitCastExpr * Cast,ASTContext & Context)220 bool isCastAllowedInCondition(const ImplicitCastExpr *Cast,
221 ASTContext &Context) {
222 std::queue<const Stmt *> Q;
223 Q.push(Cast);
224
225 TraversalKindScope RAII(Context, TK_AsIs);
226
227 while (!Q.empty()) {
228 for (const auto &N : Context.getParents(*Q.front())) {
229 const Stmt *S = N.get<Stmt>();
230 if (!S)
231 return false;
232 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
233 isa<WhileStmt>(S) || isa<BinaryConditionalOperator>(S))
234 return true;
235 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
236 isUnaryLogicalNotOperator(S) ||
237 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
238 Q.push(S);
239 } else {
240 return false;
241 }
242 }
243 Q.pop();
244 }
245 return false;
246 }
247
248 } // anonymous namespace
249
ImplicitBoolConversionCheck(StringRef Name,ClangTidyContext * Context)250 ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(
251 StringRef Name, ClangTidyContext *Context)
252 : ClangTidyCheck(Name, Context),
253 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),
254 AllowPointerConditions(Options.get("AllowPointerConditions", false)) {}
255
storeOptions(ClangTidyOptions::OptionMap & Opts)256 void ImplicitBoolConversionCheck::storeOptions(
257 ClangTidyOptions::OptionMap &Opts) {
258 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
259 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
260 }
261
registerMatchers(MatchFinder * Finder)262 void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
263 auto ExceptionCases =
264 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
265 has(ignoringImplicit(
266 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
267 hasParent(explicitCastExpr())));
268 auto ImplicitCastFromBool = implicitCastExpr(
269 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
270 // Prior to C++11 cast from bool literal to pointer was allowed.
271 allOf(anyOf(hasCastKind(CK_NullToPointer),
272 hasCastKind(CK_NullToMemberPointer)),
273 hasSourceExpression(cxxBoolLiteral()))),
274 hasSourceExpression(expr(hasType(booleanType()))),
275 unless(ExceptionCases));
276 auto BoolXor =
277 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),
278 hasRHS(ImplicitCastFromBool));
279 Finder->addMatcher(
280 traverse(TK_AsIs,
281 implicitCastExpr(
282 anyOf(hasCastKind(CK_IntegralToBoolean),
283 hasCastKind(CK_FloatingToBoolean),
284 hasCastKind(CK_PointerToBoolean),
285 hasCastKind(CK_MemberPointerToBoolean)),
286 // Exclude case of using if or while statements with variable
287 // declaration, e.g.:
288 // if (int var = functionCall()) {}
289 unless(hasParent(
290 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
291 // Exclude cases common to implicit cast to and from bool.
292 unless(ExceptionCases), unless(has(BoolXor)),
293 // Retrieve also parent statement, to check if we need
294 // additional parens in replacement.
295 anyOf(hasParent(stmt().bind("parentStmt")), anything()),
296 unless(isInTemplateInstantiation()),
297 unless(hasAncestor(functionTemplateDecl())))
298 .bind("implicitCastToBool")),
299 this);
300
301 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
302 hasLHS(ImplicitCastFromBool),
303 hasRHS(ImplicitCastFromBool));
304 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
305 hasLHS(expr(hasType(booleanType()))));
306 auto BitfieldAssignment = binaryOperator(
307 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
308 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
309 withInitializer(equalsBoundNode("implicitCastFromBool")),
310 forField(hasBitWidth(1)))));
311 Finder->addMatcher(
312 traverse(
313 TK_AsIs,
314 implicitCastExpr(
315 ImplicitCastFromBool,
316 // Exclude comparisons of bools, as they are always cast to
317 // integers in such context:
318 // bool_expr_a == bool_expr_b
319 // bool_expr_a != bool_expr_b
320 unless(hasParent(
321 binaryOperator(anyOf(BoolComparison, BoolXor,
322 BoolOpAssignment, BitfieldAssignment)))),
323 implicitCastExpr().bind("implicitCastFromBool"),
324 unless(hasParent(BitfieldConstruct)),
325 // Check also for nested casts, for example: bool -> int -> float.
326 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
327 anything()),
328 unless(isInTemplateInstantiation()),
329 unless(hasAncestor(functionTemplateDecl())))),
330 this);
331 }
332
check(const MatchFinder::MatchResult & Result)333 void ImplicitBoolConversionCheck::check(
334 const MatchFinder::MatchResult &Result) {
335
336 if (const auto *CastToBool =
337 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
338 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
339 return handleCastToBool(CastToBool, Parent, *Result.Context);
340 }
341
342 if (const auto *CastFromBool =
343 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
344 const auto *NextImplicitCast =
345 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
346 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
347 }
348 }
349
handleCastToBool(const ImplicitCastExpr * Cast,const Stmt * Parent,ASTContext & Context)350 void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,
351 const Stmt *Parent,
352 ASTContext &Context) {
353 if (AllowPointerConditions &&
354 (Cast->getCastKind() == CK_PointerToBoolean ||
355 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
356 isCastAllowedInCondition(Cast, Context)) {
357 return;
358 }
359
360 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
361 isCastAllowedInCondition(Cast, Context)) {
362 return;
363 }
364
365 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> bool")
366 << Cast->getSubExpr()->getType();
367
368 StringRef EquivalentLiteral =
369 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
370 if (!EquivalentLiteral.empty()) {
371 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
372 } else {
373 fixGenericExprCastToBool(Diag, Cast, Parent, Context);
374 }
375 }
376
handleCastFromBool(const ImplicitCastExpr * Cast,const ImplicitCastExpr * NextImplicitCast,ASTContext & Context)377 void ImplicitBoolConversionCheck::handleCastFromBool(
378 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,
379 ASTContext &Context) {
380 QualType DestType =
381 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
382 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion bool -> %0")
383 << DestType;
384
385 if (const auto *BoolLiteral =
386 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr())) {
387 Diag << tooling::fixit::createReplacement(
388 *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context));
389 } else {
390 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
391 }
392 }
393
394 } // namespace readability
395 } // namespace tidy
396 } // namespace clang
397