UpgradeDurationConversionsCheck.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. //===--- UpgradeDurationConversionsCheck.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. #include "UpgradeDurationConversionsCheck.h"
  9. #include "DurationRewriter.h"
  10. #include "clang/AST/ASTContext.h"
  11. #include "clang/ASTMatchers/ASTMatchFinder.h"
  12. #include "clang/Lex/Lexer.h"
  13. using namespace clang::ast_matchers;
  14. namespace clang::tidy::abseil {
  15. void UpgradeDurationConversionsCheck::registerMatchers(MatchFinder *Finder) {
  16. // For the arithmetic calls, we match only the uses of the templated operators
  17. // where the template parameter is not a built-in type. This means the
  18. // instantiation makes use of an available user defined conversion to
  19. // `int64_t`.
  20. //
  21. // The implementation of these templates will be updated to fail SFINAE for
  22. // non-integral types. We match them to suggest an explicit cast.
  23. // Match expressions like `a *= b` and `a /= b` where `a` has type
  24. // `absl::Duration` and `b` is not of a built-in type.
  25. Finder->addMatcher(
  26. cxxOperatorCallExpr(
  27. argumentCountIs(2),
  28. hasArgument(
  29. 0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))),
  30. hasArgument(1, expr().bind("arg")),
  31. callee(functionDecl(
  32. hasParent(functionTemplateDecl()),
  33. unless(hasTemplateArgument(0, refersToType(builtinType()))),
  34. hasAnyName("operator*=", "operator/="))))
  35. .bind("OuterExpr"),
  36. this);
  37. // Match expressions like `a.operator*=(b)` and `a.operator/=(b)` where `a`
  38. // has type `absl::Duration` and `b` is not of a built-in type.
  39. Finder->addMatcher(
  40. cxxMemberCallExpr(
  41. callee(cxxMethodDecl(
  42. ofClass(cxxRecordDecl(hasName("::absl::Duration"))),
  43. hasParent(functionTemplateDecl()),
  44. unless(hasTemplateArgument(0, refersToType(builtinType()))),
  45. hasAnyName("operator*=", "operator/="))),
  46. argumentCountIs(1), hasArgument(0, expr().bind("arg")))
  47. .bind("OuterExpr"),
  48. this);
  49. // Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and
  50. // `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a
  51. // built-in type.
  52. Finder->addMatcher(
  53. callExpr(callee(functionDecl(
  54. hasParent(functionTemplateDecl()),
  55. unless(hasTemplateArgument(0, refersToType(builtinType()))),
  56. hasAnyName("::absl::operator*", "::absl::operator/"))),
  57. argumentCountIs(2),
  58. hasArgument(0, expr(hasType(
  59. cxxRecordDecl(hasName("::absl::Duration"))))),
  60. hasArgument(1, expr().bind("arg")))
  61. .bind("OuterExpr"),
  62. this);
  63. // Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a
  64. // built-in type and `b` has type `absl::Duration`.
  65. Finder->addMatcher(
  66. callExpr(callee(functionDecl(
  67. hasParent(functionTemplateDecl()),
  68. unless(hasTemplateArgument(0, refersToType(builtinType()))),
  69. hasName("::absl::operator*"))),
  70. argumentCountIs(2), hasArgument(0, expr().bind("arg")),
  71. hasArgument(1, expr(hasType(
  72. cxxRecordDecl(hasName("::absl::Duration"))))))
  73. .bind("OuterExpr"),
  74. this);
  75. // For the factory functions, we match only the non-templated overloads that
  76. // take an `int64_t` parameter. Within these calls, we care about implicit
  77. // casts through a user defined conversion to `int64_t`.
  78. //
  79. // The factory functions will be updated to be templated and SFINAE on whether
  80. // the template parameter is an integral type. This complements the already
  81. // existing templated overloads that only accept floating point types.
  82. // Match calls like:
  83. // `absl::Nanoseconds(x)`
  84. // `absl::Microseconds(x)`
  85. // `absl::Milliseconds(x)`
  86. // `absl::Seconds(x)`
  87. // `absl::Minutes(x)`
  88. // `absl::Hours(x)`
  89. // where `x` is not of a built-in type.
  90. Finder->addMatcher(
  91. traverse(TK_AsIs, implicitCastExpr(
  92. anyOf(hasCastKind(CK_UserDefinedConversion),
  93. has(implicitCastExpr(
  94. hasCastKind(CK_UserDefinedConversion)))),
  95. hasParent(callExpr(
  96. callee(functionDecl(
  97. DurationFactoryFunction(),
  98. unless(hasParent(functionTemplateDecl())))),
  99. hasArgument(0, expr().bind("arg")))))
  100. .bind("OuterExpr")),
  101. this);
  102. }
  103. void UpgradeDurationConversionsCheck::check(
  104. const MatchFinder::MatchResult &Result) {
  105. const llvm::StringRef Message =
  106. "implicit conversion to 'int64_t' is deprecated in this context; use an "
  107. "explicit cast instead";
  108. TraversalKindScope RAII(*Result.Context, TK_AsIs);
  109. const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>("arg");
  110. SourceLocation Loc = ArgExpr->getBeginLoc();
  111. const auto *OuterExpr = Result.Nodes.getNodeAs<Expr>("OuterExpr");
  112. if (!match(isInTemplateInstantiation(), *OuterExpr, *Result.Context)
  113. .empty()) {
  114. if (MatchedTemplateLocations.count(Loc) == 0) {
  115. // For each location matched in a template instantiation, we check if the
  116. // location can also be found in `MatchedTemplateLocations`. If it is not
  117. // found, that means the expression did not create a match without the
  118. // instantiation and depends on template parameters. A manual fix is
  119. // probably required so we provide only a warning.
  120. diag(Loc, Message);
  121. }
  122. return;
  123. }
  124. // We gather source locations from template matches not in template
  125. // instantiations for future matches.
  126. internal::Matcher<Stmt> IsInsideTemplate =
  127. hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
  128. if (!match(IsInsideTemplate, *ArgExpr, *Result.Context).empty())
  129. MatchedTemplateLocations.insert(Loc);
  130. DiagnosticBuilder Diag = diag(Loc, Message);
  131. CharSourceRange SourceRange = Lexer::makeFileCharRange(
  132. CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
  133. *Result.SourceManager, Result.Context->getLangOpts());
  134. if (SourceRange.isInvalid())
  135. // An invalid source range likely means we are inside a macro body. A manual
  136. // fix is likely needed so we do not create a fix-it hint.
  137. return;
  138. Diag << FixItHint::CreateInsertion(SourceRange.getBegin(),
  139. "static_cast<int64_t>(")
  140. << FixItHint::CreateInsertion(SourceRange.getEnd(), ")");
  141. }
  142. } // namespace clang::tidy::abseil