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