ExplicitConstructorCheck.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. //===--- ExplicitConstructorCheck.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 "ExplicitConstructorCheck.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 {
  15. void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) {
  16. Finder->addMatcher(
  17. cxxConstructorDecl(unless(anyOf(isImplicit(), // Compiler-generated.
  18. isDeleted(), isInstantiated())))
  19. .bind("ctor"),
  20. this);
  21. Finder->addMatcher(
  22. cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit.
  23. isImplicit(), // Compiler-generated.
  24. isDeleted(), isInstantiated())))
  25. .bind("conversion"),
  26. this);
  27. }
  28. // Looks for the token matching the predicate and returns the range of the found
  29. // token including trailing whitespace.
  30. static SourceRange findToken(const SourceManager &Sources,
  31. const LangOptions &LangOpts,
  32. SourceLocation StartLoc, SourceLocation EndLoc,
  33. bool (*Pred)(const Token &)) {
  34. if (StartLoc.isMacroID() || EndLoc.isMacroID())
  35. return SourceRange();
  36. FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc));
  37. StringRef Buf = Sources.getBufferData(File);
  38. const char *StartChar = Sources.getCharacterData(StartLoc);
  39. Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end());
  40. Lex.SetCommentRetentionState(true);
  41. Token Tok;
  42. do {
  43. Lex.LexFromRawLexer(Tok);
  44. if (Pred(Tok)) {
  45. Token NextTok;
  46. Lex.LexFromRawLexer(NextTok);
  47. return SourceRange(Tok.getLocation(), NextTok.getLocation());
  48. }
  49. } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc);
  50. return SourceRange();
  51. }
  52. static bool declIsStdInitializerList(const NamedDecl *D) {
  53. // First use the fast getName() method to avoid unnecessary calls to the
  54. // slow getQualifiedNameAsString().
  55. return D->getName() == "initializer_list" &&
  56. D->getQualifiedNameAsString() == "std::initializer_list";
  57. }
  58. static bool isStdInitializerList(QualType Type) {
  59. Type = Type.getCanonicalType();
  60. if (const auto *TS = Type->getAs<TemplateSpecializationType>()) {
  61. if (const TemplateDecl *TD = TS->getTemplateName().getAsTemplateDecl())
  62. return declIsStdInitializerList(TD);
  63. }
  64. if (const auto *RT = Type->getAs<RecordType>()) {
  65. if (const auto *Specialization =
  66. dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl()))
  67. return declIsStdInitializerList(Specialization->getSpecializedTemplate());
  68. }
  69. return false;
  70. }
  71. void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
  72. constexpr char WarningMessage[] =
  73. "%0 must be marked explicit to avoid unintentional implicit conversions";
  74. if (const auto *Conversion =
  75. Result.Nodes.getNodeAs<CXXConversionDecl>("conversion")) {
  76. if (Conversion->isOutOfLine())
  77. return;
  78. SourceLocation Loc = Conversion->getLocation();
  79. // Ignore all macros until we learn to ignore specific ones (e.g. used in
  80. // gmock to define matchers).
  81. if (Loc.isMacroID())
  82. return;
  83. diag(Loc, WarningMessage)
  84. << Conversion << FixItHint::CreateInsertion(Loc, "explicit ");
  85. return;
  86. }
  87. const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
  88. if (Ctor->isOutOfLine() || Ctor->getNumParams() == 0 ||
  89. Ctor->getMinRequiredArguments() > 1)
  90. return;
  91. bool TakesInitializerList = isStdInitializerList(
  92. Ctor->getParamDecl(0)->getType().getNonReferenceType());
  93. if (Ctor->isExplicit() &&
  94. (Ctor->isCopyOrMoveConstructor() || TakesInitializerList)) {
  95. auto IsKwExplicit = [](const Token &Tok) {
  96. return Tok.is(tok::raw_identifier) &&
  97. Tok.getRawIdentifier() == "explicit";
  98. };
  99. SourceRange ExplicitTokenRange =
  100. findToken(*Result.SourceManager, getLangOpts(),
  101. Ctor->getOuterLocStart(), Ctor->getEndLoc(), IsKwExplicit);
  102. StringRef ConstructorDescription;
  103. if (Ctor->isMoveConstructor())
  104. ConstructorDescription = "move";
  105. else if (Ctor->isCopyConstructor())
  106. ConstructorDescription = "copy";
  107. else
  108. ConstructorDescription = "initializer-list";
  109. auto Diag = diag(Ctor->getLocation(),
  110. "%0 constructor should not be declared explicit")
  111. << ConstructorDescription;
  112. if (ExplicitTokenRange.isValid()) {
  113. Diag << FixItHint::CreateRemoval(
  114. CharSourceRange::getCharRange(ExplicitTokenRange));
  115. }
  116. return;
  117. }
  118. if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() ||
  119. TakesInitializerList)
  120. return;
  121. bool SingleArgument =
  122. Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack();
  123. SourceLocation Loc = Ctor->getLocation();
  124. diag(Loc, WarningMessage)
  125. << (SingleArgument
  126. ? "single-argument constructors"
  127. : "constructors that are callable with a single argument")
  128. << FixItHint::CreateInsertion(Loc, "explicit ");
  129. }
  130. } // namespace clang::tidy::google