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