xref: /llvm-project/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp (revision 537d4e9d21be1f5e40a780f570663b04572765af)
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 
18 using namespace clang::ast_matchers;
19 
20 namespace clang::tidy::readability {
21 
22 namespace {
23 
24 AST_MATCHER(Stmt, isMacroExpansion) {
25   SourceManager &SM = Finder->getASTContext().getSourceManager();
26   SourceLocation Loc = Node.getBeginLoc();
27   return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
28 }
29 
30 AST_MATCHER(Stmt, isC23) { return Finder->getASTContext().getLangOpts().C23; }
31 
32 bool 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, LO) == "NULL";
38 }
39 
40 AST_MATCHER(Stmt, isNULLMacroExpansion) {
41   return isNULLMacroExpansion(&Node, Finder->getASTContext());
42 }
43 
44 StringRef 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 
65 bool isUnaryLogicalNotOperator(const Stmt *Statement) {
66   const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
67   return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
68 }
69 
70 void 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(Parent);
78   if (InvertComparison) {
79     SourceLocation ParentStartLoc = Parent->getBeginLoc();
80     SourceLocation ParentEndLoc =
81         cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();
82     Diag << FixItHint::CreateRemoval(
83         CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
84 
85     Parent = Context.getParents(*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(*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(Cast->getBeginLoc(), 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       Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
133   Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
134 }
135 
136 StringRef 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>(Expression->IgnoreParens())) {
144     return (IntLit->getValue() == 0) ? "false" : "true";
145   }
146 
147   if (const auto *FloatLit = dyn_cast<FloatingLiteral>(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>(Expression)) {
154     return (CharLit->getValue() == 0) ? "false" : "true";
155   }
156 
157   if (isa<StringLiteral>(Expression->IgnoreCasts())) {
158     return "true";
159   }
160 
161   return {};
162 }
163 
164 bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {
165   SourceRange PrefixRange(Loc.getLocWithOffset(-1), Loc);
166   StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
167       CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(),
168       Context.getLangOpts(), nullptr);
169   if (SpaceBeforeStmtStr.empty())
170     return true;
171 
172   const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
173   return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());
174 }
175 
176 void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
177                                 const ImplicitCastExpr *Cast,
178                                 ASTContext &Context, StringRef OtherType) {
179   if (!Context.getLangOpts().CPlusPlus) {
180     Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(),
181                                        (Twine("(") + OtherType + ")").str());
182     return;
183   }
184 
185   const Expr *SubExpr = Cast->getSubExpr();
186   const bool NeedParens = !isa<ParenExpr>(SubExpr->IgnoreImplicit());
187   const bool NeedSpace = needsSpacePrefix(Cast->getBeginLoc(), Context);
188 
189   Diag << FixItHint::CreateInsertion(
190       Cast->getBeginLoc(), (Twine() + (NeedSpace ? " " : "") + "static_cast<" +
191                             OtherType + ">" + (NeedParens ? "(" : ""))
192                                .str());
193 
194   if (NeedParens) {
195     SourceLocation EndLoc = Lexer::getLocForEndOfToken(
196         Cast->getEndLoc(), 0, Context.getSourceManager(),
197         Context.getLangOpts());
198 
199     Diag << FixItHint::CreateInsertion(EndLoc, ")");
200   }
201 }
202 
203 StringRef 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 
225 bool 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(*Q.front())) {
234       const Stmt *S = N.get<Stmt>();
235       if (!S)
236         return false;
237       if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
238           isa<WhileStmt>(S) || isa<DoStmt>(S) ||
239           isa<BinaryConditionalOperator>(S))
240         return true;
241       if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
242           isUnaryLogicalNotOperator(S) ||
243           (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
244         Q.push(S);
245       } else {
246         return false;
247       }
248     }
249     Q.pop();
250   }
251   return false;
252 }
253 
254 } // anonymous namespace
255 
256 ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(
257     StringRef Name, ClangTidyContext *Context)
258     : ClangTidyCheck(Name, Context),
259       AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),
260       AllowPointerConditions(Options.get("AllowPointerConditions", false)),
261       UseUpperCaseLiteralSuffix(
262           Options.get("UseUpperCaseLiteralSuffix", false)) {}
263 
264 void ImplicitBoolConversionCheck::storeOptions(
265     ClangTidyOptions::OptionMap &Opts) {
266   Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
267   Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
268   Options.store(Opts, "UseUpperCaseLiteralSuffix", UseUpperCaseLiteralSuffix);
269 }
270 
271 void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
272   auto ExceptionCases =
273       expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
274                  has(ignoringImplicit(
275                      memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
276                  hasParent(explicitCastExpr()),
277                  expr(hasType(qualType().bind("type")),
278                       hasParent(initListExpr(hasParent(explicitCastExpr(
279                           hasType(qualType(equalsBoundNode("type"))))))))));
280   auto ImplicitCastFromBool = implicitCastExpr(
281       anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
282             // Prior to C++11 cast from bool literal to pointer was allowed.
283             allOf(anyOf(hasCastKind(CK_NullToPointer),
284                         hasCastKind(CK_NullToMemberPointer)),
285                   hasSourceExpression(cxxBoolLiteral()))),
286       hasSourceExpression(expr(hasType(booleanType()))));
287   auto BoolXor =
288       binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),
289                      hasRHS(ImplicitCastFromBool));
290   auto ComparisonInCall = allOf(
291       hasParent(callExpr()),
292       hasSourceExpression(binaryOperator(hasAnyOperatorName("==", "!="))));
293 
294   auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf(
295       isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));
296 
297   Finder->addMatcher(
298       traverse(TK_AsIs,
299                implicitCastExpr(
300                    anyOf(hasCastKind(CK_IntegralToBoolean),
301                          hasCastKind(CK_FloatingToBoolean),
302                          hasCastKind(CK_PointerToBoolean),
303                          hasCastKind(CK_MemberPointerToBoolean)),
304                    // Exclude cases of C23 comparison result.
305                    unless(allOf(isC23(),
306                                 hasSourceExpression(ignoringParens(
307                                     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("parentStmt"))),
321                    unless(isInTemplateInstantiation()),
322                    unless(IsInCompilerGeneratedFunction))
323                    .bind("implicitCastToBool")),
324       this);
325 
326   auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
327                                        hasLHS(ImplicitCastFromBool),
328                                        hasRHS(ImplicitCastFromBool));
329   auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
330                                          hasLHS(expr(hasType(booleanType()))));
331   auto BitfieldAssignment = binaryOperator(
332       hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
333   auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
334       withInitializer(equalsBoundNode("implicitCastFromBool")),
335       forField(hasBitWidth(1)))));
336   Finder->addMatcher(
337       traverse(
338           TK_AsIs,
339           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("implicitCastFromBool"),
349               unless(hasParent(BitfieldConstruct)),
350               // Check also for nested casts, for example: bool -> int -> float.
351               anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
352                     anything()),
353               unless(isInTemplateInstantiation()),
354               unless(IsInCompilerGeneratedFunction))),
355       this);
356 }
357 
358 void ImplicitBoolConversionCheck::check(
359     const MatchFinder::MatchResult &Result) {
360 
361   if (const auto *CastToBool =
362           Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
363     const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
364     return handleCastToBool(CastToBool, Parent, *Result.Context);
365   }
366 
367   if (const auto *CastFromBool =
368           Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
369     const auto *NextImplicitCast =
370         Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
371     return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
372   }
373 }
374 
375 void 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(Cast->getBeginLoc(), "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(*Cast, EquivalentLiteral);
397   } else {
398     fixGenericExprCastToBool(Diag, Cast, Parent, Context,
399                              UseUpperCaseLiteralSuffix);
400   }
401 }
402 
403 void 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(Cast->getBeginLoc(), "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