clang-tools  14.0.0git
ImplicitBoolConversionCheck.cpp
Go to the documentation of this file.
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 
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 
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 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 
38 AST_MATCHER(Stmt, isNULLMacroExpansion) {
39  return isNULLMacroExpansion(&Node, Finder->getASTContext());
40 }
41 
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 
61 bool isUnaryLogicalNotOperator(const Stmt *Statement) {
62  const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
63  return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
64 }
65 
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 
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 
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 
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 
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 
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 
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 
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 
258  Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
259  Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
260 }
261 
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 
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 
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 
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
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
ImplicitBoolConversionCheck.h
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
Type
NodeType Type
Definition: HTMLGenerator.cpp:73
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::tidy::readability::ImplicitBoolConversionCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: ImplicitBoolConversionCheck.cpp:333
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::readability::ImplicitBoolConversionCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ImplicitBoolConversionCheck.cpp:262
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:416
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:72
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
Parent
const Node * Parent
Definition: ExtractFunction.cpp:152
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::ClangTidyCheck::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidyCheck.cpp:120
clang::tidy::readability::ImplicitBoolConversionCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: ImplicitBoolConversionCheck.cpp:256