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
9#include "ImplicitBoolConversionCheck.h"
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
19namespace clang::tidy::readability {
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, LangOpts: LO) == "NULL";
35}
36
37AST_MATCHER(Stmt, isNULLMacroExpansion) {
38 return isNULLMacroExpansion(Statement: &Node, Context&: 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>(Val: 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(Statement: Parent);
72 if (InvertComparison) {
73 SourceLocation ParentStartLoc = Parent->getBeginLoc();
74 SourceLocation ParentEndLoc =
75 cast<UnaryOperator>(Val: Parent)->getSubExpr()->getBeginLoc();
76 Diag << FixItHint::CreateRemoval(
77 RemoveRange: CharSourceRange::getCharRange(B: ParentStartLoc, E: ParentEndLoc));
78
79 Parent = Context.getParents(Node: *Parent)[0].get<Stmt>();
80 }
81
82 const Expr *SubExpr = Cast->getSubExpr();
83
84 bool NeedInnerParens = utils::fixit::areParensNeededForStatement(*SubExpr);
85 bool NeedOuterParens =
86 Parent != nullptr && utils::fixit::areParensNeededForStatement(Node: *Parent);
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(InsertionLoc: Cast->getBeginLoc(), Code: 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 Loc: Cast->getEndLoc(), Offset: 0, SM: Context.getSourceManager(), LangOpts: Context.getLangOpts());
122 Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: 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>(Val: Expression->IgnoreParens())) {
133 return (IntLit->getValue() == 0) ? "false" : "true";
134 }
135
136 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Val: 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>(Val: Expression)) {
143 return (CharLit->getValue() == 0) ? "false" : "true";
144 }
145
146 if (isa<StringLiteral>(Val: Expression->IgnoreCasts())) {
147 return "true";
148 }
149
150 return {};
151}
152
153bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {
154 SourceRange PrefixRange(Loc.getLocWithOffset(Offset: -1), Loc);
155 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
156 Range: CharSourceRange::getCharRange(R: PrefixRange), SM: Context.getSourceManager(),
157 LangOpts: Context.getLangOpts(), Invalid: nullptr);
158 if (SpaceBeforeStmtStr.empty())
159 return true;
160
161 const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
162 return !AllowedCharacters.contains(C: 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>(Val: SubExpr->IgnoreImplicit());
170 const bool NeedSpace = needsSpacePrefix(Loc: Cast->getBeginLoc(), Context);
171
172 Diag << FixItHint::CreateInsertion(
173 InsertionLoc: Cast->getBeginLoc(), Code: (Twine() + (NeedSpace ? " " : "") + "static_cast<" +
174 OtherType + ">" + (NeedParens ? "(" : ""))
175 .str());
176
177 if (NeedParens) {
178 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
179 Loc: Cast->getEndLoc(), Offset: 0, SM: Context.getSourceManager(),
180 LangOpts: Context.getLangOpts());
181
182 Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")");
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(Node: *Q.front())) {
217 const Stmt *S = N.get<Stmt>();
218 if (!S)
219 return false;
220 if (isa<IfStmt>(Val: S) || isa<ConditionalOperator>(Val: S) || isa<ForStmt>(Val: S) ||
221 isa<WhileStmt>(Val: S) || isa<DoStmt>(Val: S) ||
222 isa<BinaryConditionalOperator>(Val: S))
223 return true;
224 if (isa<ParenExpr>(Val: S) || isa<ImplicitCastExpr>(Val: S) ||
225 isUnaryLogicalNotOperator(Statement: S) ||
226 (isa<BinaryOperator>(Val: S) && cast<BinaryOperator>(Val: S)->isLogicalOp())) {
227 Q.push(x: S);
228 } else {
229 return false;
230 }
231 }
232 Q.pop();
233 }
234 return false;
235}
236
237} // anonymous namespace
238
239ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(
240 StringRef Name, ClangTidyContext *Context)
241 : ClangTidyCheck(Name, Context),
242 AllowIntegerConditions(Options.get(LocalName: "AllowIntegerConditions", Default: false)),
243 AllowPointerConditions(Options.get(LocalName: "AllowPointerConditions", Default: false)) {}
244
245void ImplicitBoolConversionCheck::storeOptions(
246 ClangTidyOptions::OptionMap &Opts) {
247 Options.store(Options&: Opts, LocalName: "AllowIntegerConditions", Value: AllowIntegerConditions);
248 Options.store(Options&: Opts, LocalName: "AllowPointerConditions", Value: AllowPointerConditions);
249}
250
251void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
252 auto ExceptionCases =
253 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
254 has(ignoringImplicit(
255 InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(hasBitWidth(Width: 1)))))),
256 hasParent(explicitCastExpr()),
257 expr(hasType(InnerMatcher: qualType().bind(ID: "type")),
258 hasParent(initListExpr(hasParent(explicitCastExpr(
259 hasType(InnerMatcher: qualType(equalsBoundNode(ID: "type"))))))))));
260 auto ImplicitCastFromBool = implicitCastExpr(
261 anyOf(hasCastKind(Kind: CK_IntegralCast), hasCastKind(Kind: CK_IntegralToFloating),
262 // Prior to C++11 cast from bool literal to pointer was allowed.
263 allOf(anyOf(hasCastKind(Kind: CK_NullToPointer),
264 hasCastKind(Kind: CK_NullToMemberPointer)),
265 hasSourceExpression(InnerMatcher: cxxBoolLiteral()))),
266 hasSourceExpression(InnerMatcher: expr(hasType(InnerMatcher: booleanType()))));
267 auto BoolXor =
268 binaryOperator(hasOperatorName(Name: "^"), hasLHS(InnerMatcher: ImplicitCastFromBool),
269 hasRHS(InnerMatcher: ImplicitCastFromBool));
270 Finder->addMatcher(
271 NodeMatch: traverse(TK: TK_AsIs,
272 InnerMatcher: implicitCastExpr(
273 anyOf(hasCastKind(Kind: CK_IntegralToBoolean),
274 hasCastKind(Kind: CK_FloatingToBoolean),
275 hasCastKind(Kind: CK_PointerToBoolean),
276 hasCastKind(Kind: 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(ID: "parentStmt"))),
287 unless(isInTemplateInstantiation()),
288 unless(hasAncestor(functionTemplateDecl())))
289 .bind(ID: "implicitCastToBool")),
290 Action: this);
291
292 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
293 hasLHS(InnerMatcher: ImplicitCastFromBool),
294 hasRHS(InnerMatcher: ImplicitCastFromBool));
295 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
296 hasLHS(InnerMatcher: expr(hasType(InnerMatcher: booleanType()))));
297 auto BitfieldAssignment = binaryOperator(
298 hasLHS(InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(hasBitWidth(Width: 1))))));
299 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
300 withInitializer(InnerMatcher: equalsBoundNode(ID: "implicitCastFromBool")),
301 forField(InnerMatcher: hasBitWidth(Width: 1)))));
302 Finder->addMatcher(
303 NodeMatch: traverse(
304 TK: TK_AsIs,
305 InnerMatcher: 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(ID: "implicitCastFromBool"),
315 unless(hasParent(BitfieldConstruct)),
316 // Check also for nested casts, for example: bool -> int -> float.
317 anyOf(hasParent(implicitCastExpr().bind(ID: "furtherImplicitCast")),
318 anything()),
319 unless(isInTemplateInstantiation()),
320 unless(hasAncestor(functionTemplateDecl())))),
321 Action: this);
322}
323
324void ImplicitBoolConversionCheck::check(
325 const MatchFinder::MatchResult &Result) {
326
327 if (const auto *CastToBool =
328 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "implicitCastToBool")) {
329 const auto *Parent = Result.Nodes.getNodeAs<Stmt>(ID: "parentStmt");
330 return handleCastToBool(CastExpression: CastToBool, ParentStatement: Parent, Context&: *Result.Context);
331 }
332
333 if (const auto *CastFromBool =
334 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "implicitCastFromBool")) {
335 const auto *NextImplicitCast =
336 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "furtherImplicitCast");
337 return handleCastFromBool(Cast: CastFromBool, NextImplicitCast, Context&: *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(Loc: Cast->getBeginLoc(), Description: "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(Destination: *Cast, Source: 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(Loc: Cast->getBeginLoc(), Description: "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
386

source code of clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp