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