AvoidCStyleCastsCheck.cpp 9.4 KB

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