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/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
20namespace clang::tidy::readability {
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, LangOpts: LO) == "NULL";
38}
39
40AST_MATCHER(Stmt, isNULLMacroExpansion) {
41 return isNULLMacroExpansion(Statement: &Node, Context&: 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>(Val: 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(Statement: Parent);
78 if (InvertComparison) {
79 SourceLocation ParentStartLoc = Parent->getBeginLoc();
80 SourceLocation ParentEndLoc =
81 cast<UnaryOperator>(Val: Parent)->getSubExpr()->getBeginLoc();
82 Diag << FixItHint::CreateRemoval(
83 RemoveRange: CharSourceRange::getCharRange(B: ParentStartLoc, E: ParentEndLoc));
84
85 Parent = Context.getParents(Node: *Parent)[0].get<Stmt>();
86 }
87
88 const Expr *SubExpr = Cast->getSubExpr();
89
90 bool NeedInnerParens = utils::fixit::areParensNeededForStatement(*SubExpr);
91 bool NeedOuterParens =
92 Parent != nullptr && utils::fixit::areParensNeededForStatement(Node: *Parent);
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(InsertionLoc: Cast->getBeginLoc(), Code: 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 Loc: Cast->getEndLoc(), Offset: 0, SM: Context.getSourceManager(), LangOpts: Context.getLangOpts());
133 Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: 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>(Val: Expression->IgnoreParens())) {
144 return (IntLit->getValue() == 0) ? "false" : "true";
145 }
146
147 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Val: 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>(Val: Expression)) {
154 return (CharLit->getValue() == 0) ? "false" : "true";
155 }
156
157 if (isa<StringLiteral>(Val: Expression->IgnoreCasts())) {
158 return "true";
159 }
160
161 return {};
162}
163
164bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {
165 SourceRange PrefixRange(Loc.getLocWithOffset(Offset: -1), Loc);
166 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
167 Range: CharSourceRange::getCharRange(R: PrefixRange), SM: Context.getSourceManager(),
168 LangOpts: Context.getLangOpts(), Invalid: nullptr);
169 if (SpaceBeforeStmtStr.empty())
170 return true;
171
172 const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
173 return !AllowedCharacters.contains(C: 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(InsertionLoc: Cast->getBeginLoc(),
181 Code: (Twine("(") + OtherType + ")").str());
182 return;
183 }
184
185 const Expr *SubExpr = Cast->getSubExpr();
186 const bool NeedParens = !isa<ParenExpr>(Val: SubExpr->IgnoreImplicit());
187 const bool NeedSpace = needsSpacePrefix(Loc: Cast->getBeginLoc(), Context);
188
189 Diag << FixItHint::CreateInsertion(
190 InsertionLoc: Cast->getBeginLoc(), Code: (Twine() + (NeedSpace ? " " : "") + "static_cast<" +
191 OtherType + ">" + (NeedParens ? "(" : ""))
192 .str());
193
194 if (NeedParens) {
195 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
196 Loc: Cast->getEndLoc(), Offset: 0, SM: Context.getSourceManager(),
197 LangOpts: Context.getLangOpts());
198
199 Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")");
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(Node: *Q.front())) {
234 const Stmt *S = N.get<Stmt>();
235 if (!S)
236 return false;
237 if (isa<IfStmt>(Val: S) || isa<ConditionalOperator>(Val: S) || isa<ForStmt>(Val: S) ||
238 isa<WhileStmt>(Val: S) || isa<DoStmt>(Val: S) ||
239 isa<BinaryConditionalOperator>(Val: S))
240 return true;
241 if (isa<ParenExpr>(Val: S) || isa<ImplicitCastExpr>(Val: S) ||
242 isUnaryLogicalNotOperator(Statement: S) ||
243 (isa<BinaryOperator>(Val: S) && cast<BinaryOperator>(Val: S)->isLogicalOp())) {
244 Q.push(x: S);
245 } else {
246 return false;
247 }
248 }
249 Q.pop();
250 }
251 return false;
252}
253
254} // anonymous namespace
255
256ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(
257 StringRef Name, ClangTidyContext *Context)
258 : ClangTidyCheck(Name, Context),
259 AllowIntegerConditions(Options.get(LocalName: "AllowIntegerConditions", Default: false)),
260 AllowPointerConditions(Options.get(LocalName: "AllowPointerConditions", Default: false)),
261 UseUpperCaseLiteralSuffix(
262 Options.get(LocalName: "UseUpperCaseLiteralSuffix", Default: false)) {}
263
264void ImplicitBoolConversionCheck::storeOptions(
265 ClangTidyOptions::OptionMap &Opts) {
266 Options.store(Options&: Opts, LocalName: "AllowIntegerConditions", Value: AllowIntegerConditions);
267 Options.store(Options&: Opts, LocalName: "AllowPointerConditions", Value: AllowPointerConditions);
268 Options.store(Options&: Opts, LocalName: "UseUpperCaseLiteralSuffix", Value: UseUpperCaseLiteralSuffix);
269}
270
271void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
272 auto ExceptionCases =
273 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
274 has(ignoringImplicit(
275 InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(hasBitWidth(Width: 1)))))),
276 hasParent(explicitCastExpr()),
277 expr(hasType(InnerMatcher: qualType().bind(ID: "type")),
278 hasParent(initListExpr(hasParent(explicitCastExpr(
279 hasType(InnerMatcher: qualType(equalsBoundNode(ID: "type"))))))))));
280 auto ImplicitCastFromBool = implicitCastExpr(
281 anyOf(hasCastKind(Kind: CK_IntegralCast), hasCastKind(Kind: CK_IntegralToFloating),
282 // Prior to C++11 cast from bool literal to pointer was allowed.
283 allOf(anyOf(hasCastKind(Kind: CK_NullToPointer),
284 hasCastKind(Kind: CK_NullToMemberPointer)),
285 hasSourceExpression(InnerMatcher: cxxBoolLiteral()))),
286 hasSourceExpression(InnerMatcher: expr(hasType(InnerMatcher: booleanType()))));
287 auto BoolXor =
288 binaryOperator(hasOperatorName(Name: "^"), hasLHS(InnerMatcher: ImplicitCastFromBool),
289 hasRHS(InnerMatcher: ImplicitCastFromBool));
290 auto ComparisonInCall = allOf(
291 hasParent(callExpr()),
292 hasSourceExpression(InnerMatcher: binaryOperator(hasAnyOperatorName("==", "!="))));
293
294 auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf(
295 isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));
296
297 Finder->addMatcher(
298 NodeMatch: traverse(TK: TK_AsIs,
299 InnerMatcher: implicitCastExpr(
300 anyOf(hasCastKind(Kind: CK_IntegralToBoolean),
301 hasCastKind(Kind: CK_FloatingToBoolean),
302 hasCastKind(Kind: CK_PointerToBoolean),
303 hasCastKind(Kind: CK_MemberPointerToBoolean)),
304 // Exclude cases of C23 comparison result.
305 unless(allOf(isC23(),
306 hasSourceExpression(InnerMatcher: ignoringParens(
307 InnerMatcher: 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(ID: "parentStmt"))),
321 unless(isInTemplateInstantiation()),
322 unless(IsInCompilerGeneratedFunction))
323 .bind(ID: "implicitCastToBool")),
324 Action: this);
325
326 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
327 hasLHS(InnerMatcher: ImplicitCastFromBool),
328 hasRHS(InnerMatcher: ImplicitCastFromBool));
329 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
330 hasLHS(InnerMatcher: expr(hasType(InnerMatcher: booleanType()))));
331 auto BitfieldAssignment = binaryOperator(
332 hasLHS(InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(hasBitWidth(Width: 1))))));
333 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
334 withInitializer(InnerMatcher: equalsBoundNode(ID: "implicitCastFromBool")),
335 forField(InnerMatcher: hasBitWidth(Width: 1)))));
336 Finder->addMatcher(
337 NodeMatch: traverse(
338 TK: TK_AsIs,
339 InnerMatcher: 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(ID: "implicitCastFromBool"),
349 unless(hasParent(BitfieldConstruct)),
350 // Check also for nested casts, for example: bool -> int -> float.
351 anyOf(hasParent(implicitCastExpr().bind(ID: "furtherImplicitCast")),
352 anything()),
353 unless(isInTemplateInstantiation()),
354 unless(IsInCompilerGeneratedFunction))),
355 Action: this);
356}
357
358void ImplicitBoolConversionCheck::check(
359 const MatchFinder::MatchResult &Result) {
360
361 if (const auto *CastToBool =
362 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "implicitCastToBool")) {
363 const auto *Parent = Result.Nodes.getNodeAs<Stmt>(ID: "parentStmt");
364 return handleCastToBool(CastExpression: CastToBool, ParentStatement: Parent, Context&: *Result.Context);
365 }
366
367 if (const auto *CastFromBool =
368 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "implicitCastFromBool")) {
369 const auto *NextImplicitCast =
370 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "furtherImplicitCast");
371 return handleCastFromBool(Cast: CastFromBool, NextImplicitCast, Context&: *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(Loc: Cast->getBeginLoc(), Description: "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(Destination: *Cast, Source: 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(Loc: Cast->getBeginLoc(), Description: "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
429

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

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