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