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