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