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

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