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 Context.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 = utils::fixit::areParensNeededForStatement(*SubExpr);
93 bool NeedOuterParens =
94 Parent != nullptr && utils::fixit::areParensNeededForStatement(*Parent);
95
96 std::string StartLocInsertion;
97
98 if (NeedOuterParens) {
99 StartLocInsertion += "(";
100 }
101 if (NeedInnerParens) {
102 StartLocInsertion += "(";
103 }
104
105 if (!StartLocInsertion.empty()) {
106 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
107 }
108
109 std::string EndLocInsertion;
110
111 if (NeedInnerParens) {
112 EndLocInsertion += ")";
113 }
114
115 if (InvertComparison) {
116 EndLocInsertion += " == ";
117 } else {
118 EndLocInsertion += " != ";
119 }
120
121 const StringRef ZeroLiteral = getZeroLiteralToCompareWithForType(
122 Cast->getCastKind(), SubExpr->getType(), Context);
123
124 if (UseUpperCaseLiteralSuffix)
125 EndLocInsertion += ZeroLiteral.upper();
126 else
127 EndLocInsertion += ZeroLiteral;
128
129 if (NeedOuterParens) {
130 EndLocInsertion += ")";
131 }
132
133 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
134 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
135 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
136}
137
138static StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,
139 ASTContext &Context) {
140 if (isNULLMacroExpansion(Expression, Context)) {
141 return "false";
142 }
143
144 if (const auto *IntLit =
145 dyn_cast<IntegerLiteral>(Expression->IgnoreParens())) {
146 return (IntLit->getValue() == 0) ? "false" : "true";
147 }
148
149 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
150 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
151 FloatLitAbsValue.clearSign();
152 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
153 }
154
155 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
156 return (CharLit->getValue() == 0) ? "false" : "true";
157 }
158
159 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
160 return "true";
161 }
162
163 return {};
164}
165
166static bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {
167 SourceRange PrefixRange(Loc.getLocWithOffset(-1), Loc);
168 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
169 CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(),
170 Context.getLangOpts(), nullptr);
171 if (SpaceBeforeStmtStr.empty())
172 return true;
173
174 const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
175 return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());
176}
177
178static void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
179 const ImplicitCastExpr *Cast,
180 ASTContext &Context,
181 StringRef OtherType) {
182 if (!Context.getLangOpts().CPlusPlus) {
183 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(),
184 (Twine("(") + OtherType + ")").str());
185 return;
186 }
187
188 const Expr *SubExpr = Cast->getSubExpr();
189 const bool NeedParens = !isa<ParenExpr>(SubExpr->IgnoreImplicit());
190 const bool NeedSpace = needsSpacePrefix(Cast->getBeginLoc(), Context);
191
192 Diag << FixItHint::CreateInsertion(
193 Cast->getBeginLoc(), (Twine() + (NeedSpace ? " " : "") + "static_cast<" +
194 OtherType + ">" + (NeedParens ? "(" : ""))
195 .str());
196
197 if (NeedParens) {
198 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
199 Cast->getEndLoc(), 0, Context.getSourceManager(),
200 Context.getLangOpts());
201
202 Diag << FixItHint::CreateInsertion(EndLoc, ")");
203 }
204}
205
206static StringRef
207getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,
208 QualType DestType, ASTContext &Context) {
209 // Prior to C++11, false literal could be implicitly converted to pointer.
210 if (!Context.getLangOpts().CPlusPlus11 &&
211 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
212 BoolLiteral->getValue() == false) {
213 return "0";
214 }
215
216 if (DestType->isFloatingType()) {
217 if (Context.hasSameType(DestType, Context.FloatTy)) {
218 return BoolLiteral->getValue() ? "1.0f" : "0.0f";
219 }
220 return BoolLiteral->getValue() ? "1.0" : "0.0";
221 }
222
223 if (DestType->isUnsignedIntegerType()) {
224 return BoolLiteral->getValue() ? "1u" : "0u";
225 }
226 return BoolLiteral->getValue() ? "1" : "0";
227}
228
229static bool isCastAllowedInCondition(const ImplicitCastExpr *Cast,
230 ASTContext &Context) {
231 std::queue<const Stmt *> Q;
232 Q.push(Cast);
233
234 TraversalKindScope RAII(Context, TK_AsIs);
235
236 while (!Q.empty()) {
237 for (const auto &N : Context.getParents(*Q.front())) {
238 const Stmt *S = N.get<Stmt>();
239 if (!S)
240 return false;
241 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
242 isa<WhileStmt>(S) || isa<DoStmt>(S) ||
243 isa<BinaryConditionalOperator>(S))
244 return true;
245 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
247 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
248 Q.push(S);
249 } else {
250 return false;
251 }
252 }
253 Q.pop();
254 }
255 return false;
256}
257
259 StringRef Name, ClangTidyContext *Context)
260 : ClangTidyCheck(Name, Context),
261 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),
262 AllowPointerConditions(Options.get("AllowPointerConditions", false)),
263 UseUpperCaseLiteralSuffix(
264 Options.get("UseUpperCaseLiteralSuffix", false)) {}
265
268 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
269 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
270 Options.store(Opts, "UseUpperCaseLiteralSuffix", UseUpperCaseLiteralSuffix);
271}
272
274 auto ExceptionCases =
275 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
276 has(ignoringImplicit(
277 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
278 hasParent(explicitCastExpr()),
279 expr(hasType(qualType().bind("type")),
280 hasParent(initListExpr(hasParent(explicitCastExpr(
281 hasType(qualType(equalsBoundNode("type"))))))))));
282 auto ImplicitCastFromBool = implicitCastExpr(
283 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
284 // Prior to C++11 cast from bool literal to pointer was allowed.
285 allOf(anyOf(hasCastKind(CK_NullToPointer),
286 hasCastKind(CK_NullToMemberPointer)),
287 hasSourceExpression(cxxBoolLiteral()))),
288 hasSourceExpression(expr(hasType(booleanType()))));
289 auto BoolXor =
290 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),
291 hasRHS(ImplicitCastFromBool));
292 auto ComparisonInCall = allOf(
293 hasParent(callExpr()),
294 hasSourceExpression(binaryOperator(hasAnyOperatorName("==", "!="))));
295
296 auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf(
297 isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));
298
299 Finder->addMatcher(
300 traverse(TK_AsIs,
301 implicitCastExpr(
302 anyOf(hasCastKind(CK_IntegralToBoolean),
303 hasCastKind(CK_FloatingToBoolean),
304 hasCastKind(CK_PointerToBoolean),
305 hasCastKind(CK_MemberPointerToBoolean)),
306 // Exclude cases of C23 comparison result.
307 unless(allOf(isC23(),
308 hasSourceExpression(ignoringParens(
309 binaryOperator(hasAnyOperatorName(
310 ">", ">=", "==", "!=", "<", "<=")))))),
311 // Exclude case of using if or while statements with variable
312 // declaration, e.g.:
313 // if (int var = functionCall()) {}
314 unless(hasParent(
315 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
316 // Exclude cases common to implicit cast to and from bool.
317 unless(ExceptionCases), unless(has(BoolXor)),
318 // Exclude C23 cases common to implicit cast to bool.
319 unless(ComparisonInCall),
320 // Retrieve also parent statement, to check if we need
321 // additional parens in replacement.
322 optionally(hasParent(stmt().bind("parentStmt"))),
323 unless(isInTemplateInstantiation()),
324 unless(IsInCompilerGeneratedFunction))
325 .bind("implicitCastToBool")),
326 this);
327
328 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
329 hasLHS(ImplicitCastFromBool),
330 hasRHS(ImplicitCastFromBool));
331 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
332 hasLHS(expr(hasType(booleanType()))));
333 auto BitfieldAssignment = binaryOperator(
334 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
335 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
336 withInitializer(equalsBoundNode("implicitCastFromBool")),
337 forField(hasBitWidth(1)))));
338 Finder->addMatcher(
339 traverse(
340 TK_AsIs,
341 implicitCastExpr(
342 ImplicitCastFromBool, unless(ExceptionCases),
343 // Exclude comparisons of bools, as they are always cast to
344 // integers in such context:
345 // bool_expr_a == bool_expr_b
346 // bool_expr_a != bool_expr_b
347 unless(hasParent(
348 binaryOperator(anyOf(BoolComparison, BoolXor,
349 BoolOpAssignment, BitfieldAssignment)))),
350 implicitCastExpr().bind("implicitCastFromBool"),
351 unless(hasParent(BitfieldConstruct)),
352 // Check also for nested casts, for example: bool -> int -> float.
353 optionally(
354 hasParent(implicitCastExpr().bind("furtherImplicitCast"))),
355 unless(isInTemplateInstantiation()),
356 unless(IsInCompilerGeneratedFunction))),
357 this);
358}
359
361 const MatchFinder::MatchResult &Result) {
362
363 if (const auto *CastToBool =
364 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
365 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
366 handleCastToBool(CastToBool, Parent, *Result.Context);
367 return;
368 }
369
370 if (const auto *CastFromBool =
371 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
372 const auto *NextImplicitCast =
373 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
374 handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
375 }
376}
377
378void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,
379 const Stmt *Parent,
380 ASTContext &Context) {
381 if (AllowPointerConditions &&
382 (Cast->getCastKind() == CK_PointerToBoolean ||
383 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
384 isCastAllowedInCondition(Cast, Context)) {
385 return;
386 }
387
388 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
389 isCastAllowedInCondition(Cast, Context)) {
390 return;
391 }
392
393 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> 'bool'")
394 << Cast->getSubExpr()->getType();
395
396 StringRef EquivalentLiteral =
397 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
398 if (!EquivalentLiteral.empty()) {
399 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
400 } else {
401 fixGenericExprCastToBool(Diag, Cast, Parent, Context,
402 UseUpperCaseLiteralSuffix);
403 }
404}
405
406void ImplicitBoolConversionCheck::handleCastFromBool(
407 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,
408 ASTContext &Context) {
409 QualType DestType =
410 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
411 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion 'bool' -> %0")
412 << DestType;
413
414 if (const auto *BoolLiteral =
415 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr()->IgnoreParens())) {
416
417 const auto EquivalentForBoolLiteral =
418 getEquivalentForBoolLiteral(BoolLiteral, DestType, Context);
419 if (UseUpperCaseLiteralSuffix)
420 Diag << tooling::fixit::createReplacement(
421 *Cast, EquivalentForBoolLiteral.upper());
422 else
423 Diag << tooling::fixit::createReplacement(*Cast,
424 EquivalentForBoolLiteral);
425
426 } else {
427 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
428 }
429}
430
431} // 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