clang-tools  14.0.0git
AvoidCStyleCastsCheck.cpp
Go to the documentation of this file.
1 //===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===//
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/ASTMatchers/ASTMatchers.h"
13 #include "clang/Lex/Lexer.h"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang {
18 namespace tidy {
19 namespace google {
20 namespace readability {
21 
22 void AvoidCStyleCastsCheck::registerMatchers(
23  ast_matchers::MatchFinder *Finder) {
24  Finder->addMatcher(
25  cStyleCastExpr(
26  // Filter out (EnumType)IntegerLiteral construct, which is generated
27  // for non-type template arguments of enum types.
28  // FIXME: Remove this once this is fixed in the AST.
29  unless(hasParent(substNonTypeTemplateParmExpr())),
30  // Avoid matches in template instantiations.
31  unless(isInTemplateInstantiation()))
32  .bind("cast"),
33  this);
34  Finder->addMatcher(
35  cxxFunctionalCastExpr(unless(hasDescendant(cxxConstructExpr())),
36  unless(hasDescendant(initListExpr())))
37  .bind("cast"),
38  this);
39 }
40 
41 static bool needsConstCast(QualType SourceType, QualType DestType) {
42  while ((SourceType->isPointerType() && DestType->isPointerType()) ||
43  (SourceType->isReferenceType() && DestType->isReferenceType())) {
44  SourceType = SourceType->getPointeeType();
45  DestType = DestType->getPointeeType();
46  if (SourceType.isConstQualified() && !DestType.isConstQualified()) {
47  return (SourceType->isPointerType() == DestType->isPointerType()) &&
48  (SourceType->isReferenceType() == DestType->isReferenceType());
49  }
50  }
51  return false;
52 }
53 
54 static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) {
55  while ((T1->isPointerType() && T2->isPointerType()) ||
56  (T1->isReferenceType() && T2->isReferenceType())) {
57  T1 = T1->getPointeeType();
58  T2 = T2->getPointeeType();
59  }
60  return T1.getUnqualifiedType() == T2.getUnqualifiedType();
61 }
62 
63 static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr) {
64  if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {
65  return CharSourceRange::getCharRange(
66  CastExpr->getLParenLoc(),
67  CastExpr->getSubExprAsWritten()->getBeginLoc());
68  } else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {
69  return CharSourceRange::getCharRange(CastExpr->getBeginLoc(),
70  CastExpr->getLParenLoc());
71  } else
72  llvm_unreachable("Unsupported CastExpr");
73 }
74 
75 static StringRef getDestTypeString(const SourceManager &SM,
76  const LangOptions &LangOpts,
77  const ExplicitCastExpr *Expr) {
78  SourceLocation BeginLoc;
79  SourceLocation EndLoc;
80 
81  if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {
82  BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(1);
83  EndLoc = CastExpr->getRParenLoc().getLocWithOffset(-1);
84  } else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {
85  BeginLoc = CastExpr->getBeginLoc();
86  EndLoc = CastExpr->getLParenLoc().getLocWithOffset(-1);
87  } else
88  llvm_unreachable("Unsupported CastExpr");
89 
90  return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),
91  SM, LangOpts);
92 }
93 
94 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
95  const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
96 
97  // Ignore casts in macros.
98  if (CastExpr->getExprLoc().isMacroID())
99  return;
100 
101  // Casting to void is an idiomatic way to mute "unused variable" and similar
102  // warnings.
103  if (CastExpr->getCastKind() == CK_ToVoid)
104  return;
105 
106  auto IsFunction = [](QualType T) {
107  T = T.getCanonicalType().getNonReferenceType();
108  return T->isFunctionType() || T->isFunctionPointerType() ||
109  T->isMemberFunctionPointerType();
110  };
111 
112  const QualType DestTypeAsWritten =
113  CastExpr->getTypeAsWritten().getUnqualifiedType();
114  const QualType SourceTypeAsWritten =
115  CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType();
116  const QualType SourceType = SourceTypeAsWritten.getCanonicalType();
117  const QualType DestType = DestTypeAsWritten.getCanonicalType();
118 
119  CharSourceRange ReplaceRange = getReplaceRange(CastExpr);
120 
121  bool FnToFnCast =
122  IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);
123 
124  const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() &&
125  DestTypeAsWritten->isRecordType() &&
126  !DestTypeAsWritten->isElaboratedTypeSpecifier();
127 
128  if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) {
129  // Function pointer/reference casts may be needed to resolve ambiguities in
130  // case of overloaded functions, so detection of redundant casts is trickier
131  // in this case. Don't emit "redundant cast" warnings for function
132  // pointer/reference types.
133  if (SourceTypeAsWritten == DestTypeAsWritten) {
134  diag(CastExpr->getBeginLoc(), "redundant cast to the same type")
135  << FixItHint::CreateRemoval(ReplaceRange);
136  return;
137  }
138  }
139 
140  // The rest of this check is only relevant to C++.
141  // We also disable it for Objective-C++.
142  if (!getLangOpts().CPlusPlus || getLangOpts().ObjC)
143  return;
144  // Ignore code inside extern "C" {} blocks.
145  if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context)
146  .empty())
147  return;
148  // Ignore code in .c files and headers included from them, even if they are
149  // compiled as C++.
150  if (getCurrentMainFile().endswith(".c"))
151  return;
152 
153  SourceManager &SM = *Result.SourceManager;
154 
155  // Ignore code in .c files #included in other files (which shouldn't be done,
156  // but people still do this for test and other purposes).
157  if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())).endswith(".c"))
158  return;
159 
160  // Leave type spelling exactly as it was (unlike
161  // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
162  StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr);
163 
164  auto Diag =
165  diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
166 
167  auto ReplaceWithCast = [&](std::string CastText) {
168  const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
169  if (!isa<ParenExpr>(SubExpr) && !isa<CXXFunctionalCastExpr>(CastExpr)) {
170  CastText.push_back('(');
171  Diag << FixItHint::CreateInsertion(
172  Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM,
173  getLangOpts()),
174  ")");
175  }
176  Diag << FixItHint::CreateReplacement(ReplaceRange, CastText);
177  };
178  auto ReplaceWithNamedCast = [&](StringRef CastType) {
179  Diag << CastType;
180  ReplaceWithCast((CastType + "<" + DestTypeString + ">").str());
181  };
182  auto ReplaceWithConstructorCall = [&]() {
183  Diag << "constructor call syntax";
184  // FIXME: Validate DestTypeString, maybe.
185  ReplaceWithCast(DestTypeString.str());
186  };
187  // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
188  switch (CastExpr->getCastKind()) {
189  case CK_FunctionToPointerDecay:
190  ReplaceWithNamedCast("static_cast");
191  return;
192  case CK_ConstructorConversion:
193  if (ConstructorCast) {
194  ReplaceWithConstructorCall();
195  } else {
196  ReplaceWithNamedCast("static_cast");
197  }
198  return;
199  case CK_NoOp:
200  if (FnToFnCast) {
201  ReplaceWithNamedCast("static_cast");
202  return;
203  }
204  if (SourceType == DestType) {
205  Diag << "static_cast (if needed, the cast may be redundant)";
206  ReplaceWithCast(("static_cast<" + DestTypeString + ">").str());
207  return;
208  }
209  if (needsConstCast(SourceType, DestType) &&
210  pointedUnqualifiedTypesAreEqual(SourceType, DestType)) {
211  ReplaceWithNamedCast("const_cast");
212  return;
213  }
214  if (ConstructorCast) {
215  ReplaceWithConstructorCall();
216  return;
217  }
218  if (DestType->isReferenceType()) {
219  QualType Dest = DestType.getNonReferenceType();
220  QualType Source = SourceType.getNonReferenceType();
221  if (Source == Dest.withConst() ||
222  SourceType.getNonReferenceType() == DestType.getNonReferenceType()) {
223  ReplaceWithNamedCast("const_cast");
224  return;
225  }
226  break;
227  }
228  LLVM_FALLTHROUGH;
229  case clang::CK_IntegralCast:
230  // Convert integral and no-op casts between builtin types and enums to
231  // static_cast. A cast from enum to integer may be unnecessary, but it's
232  // still retained.
233  if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) &&
234  (DestType->isBuiltinType() || DestType->isEnumeralType())) {
235  ReplaceWithNamedCast("static_cast");
236  return;
237  }
238  break;
239  case CK_BitCast:
240  // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
241  if (!needsConstCast(SourceType, DestType)) {
242  if (SourceType->isVoidPointerType())
243  ReplaceWithNamedCast("static_cast");
244  else
245  ReplaceWithNamedCast("reinterpret_cast");
246  return;
247  }
248  break;
249  default:
250  break;
251  }
252 
253  Diag << "static_cast/const_cast/reinterpret_cast";
254 }
255 
256 } // namespace readability
257 } // namespace google
258 } // namespace tidy
259 } // namespace clang
clang::tidy::google::readability::needsConstCast
static bool needsConstCast(QualType SourceType, QualType DestType)
Definition: AvoidCStyleCastsCheck.cpp:41
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:112
clang::tidy::google::readability::pointedUnqualifiedTypesAreEqual
static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2)
Definition: AvoidCStyleCastsCheck.cpp:54
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::clangd::match
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:98
clang::tidy::google::readability::getDestTypeString
static StringRef getDestTypeString(const SourceManager &SM, const LangOptions &LangOpts, const ExplicitCastExpr *Expr)
Definition: AvoidCStyleCastsCheck.cpp:75
AvoidCStyleCastsCheck.h
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:258
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::google::readability::getReplaceRange
static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr)
Definition: AvoidCStyleCastsCheck.cpp:63