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