ConfusableIdentifierCheck.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. //===--- ConfusableIdentifierCheck.cpp -
  2. // clang-tidy--------------------------===//
  3. //
  4. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  5. // See https://llvm.org/LICENSE.txt for license information.
  6. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  7. //
  8. //===----------------------------------------------------------------------===//
  9. #include "ConfusableIdentifierCheck.h"
  10. #include "clang/Frontend/CompilerInstance.h"
  11. #include "clang/Lex/Preprocessor.h"
  12. #include "llvm/Support/ConvertUTF.h"
  13. namespace {
  14. // Preprocessed version of
  15. // https://www.unicode.org/Public/security/latest/confusables.txt
  16. //
  17. // This contains a sorted array of { UTF32 codepoint; UTF32 values[N];}
  18. #include "Confusables.inc"
  19. } // namespace
  20. namespace clang::tidy::misc {
  21. ConfusableIdentifierCheck::ConfusableIdentifierCheck(StringRef Name,
  22. ClangTidyContext *Context)
  23. : ClangTidyCheck(Name, Context) {}
  24. ConfusableIdentifierCheck::~ConfusableIdentifierCheck() = default;
  25. // Build a skeleton out of the Original identifier, inspired by the algorithm
  26. // described in http://www.unicode.org/reports/tr39/#def-skeleton
  27. //
  28. // FIXME: TR39 mandates:
  29. //
  30. // For an input string X, define skeleton(X) to be the following transformation
  31. // on the string:
  32. //
  33. // 1. Convert X to NFD format, as described in [UAX15].
  34. // 2. Concatenate the prototypes for each character in X according to the
  35. // specified data, producing a string of exemplar characters.
  36. // 3. Reapply NFD.
  37. //
  38. // We're skipping 1. and 3. for the sake of simplicity, but this can lead to
  39. // false positive.
  40. std::string ConfusableIdentifierCheck::skeleton(StringRef Name) {
  41. using namespace llvm;
  42. std::string SName = Name.str();
  43. std::string Skeleton;
  44. Skeleton.reserve(1 + Name.size());
  45. const char *Curr = SName.c_str();
  46. const char *End = Curr + SName.size();
  47. while (Curr < End) {
  48. const char *Prev = Curr;
  49. UTF32 CodePoint;
  50. ConversionResult Result = convertUTF8Sequence(
  51. reinterpret_cast<const UTF8 **>(&Curr),
  52. reinterpret_cast<const UTF8 *>(End), &CodePoint, strictConversion);
  53. if (Result != conversionOK) {
  54. errs() << "Unicode conversion issue\n";
  55. break;
  56. }
  57. StringRef Key(Prev, Curr - Prev);
  58. auto Where = llvm::lower_bound(ConfusableEntries, CodePoint,
  59. [](decltype(ConfusableEntries[0]) x,
  60. UTF32 y) { return x.codepoint < y; });
  61. if (Where == std::end(ConfusableEntries) || CodePoint != Where->codepoint) {
  62. Skeleton.append(Prev, Curr);
  63. } else {
  64. UTF8 Buffer[32];
  65. UTF8 *BufferStart = std::begin(Buffer);
  66. UTF8 *IBuffer = BufferStart;
  67. const UTF32 *ValuesStart = std::begin(Where->values);
  68. const UTF32 *ValuesEnd = llvm::find(Where->values, '\0');
  69. if (ConvertUTF32toUTF8(&ValuesStart, ValuesEnd, &IBuffer,
  70. std::end(Buffer),
  71. strictConversion) != conversionOK) {
  72. errs() << "Unicode conversion issue\n";
  73. break;
  74. }
  75. Skeleton.append((char *)BufferStart, (char *)IBuffer);
  76. }
  77. }
  78. return Skeleton;
  79. }
  80. static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1) {
  81. const DeclContext *DC0 = ND0->getDeclContext()->getPrimaryContext();
  82. const DeclContext *DC1 = ND1->getDeclContext()->getPrimaryContext();
  83. if (isa<TemplateTypeParmDecl>(ND0) || isa<TemplateTypeParmDecl>(ND0))
  84. return true;
  85. while (DC0->isTransparentContext())
  86. DC0 = DC0->getParent();
  87. while (DC1->isTransparentContext())
  88. DC1 = DC1->getParent();
  89. if (DC0->Equals(DC1))
  90. return true;
  91. return false;
  92. }
  93. static bool isMemberOf(const NamedDecl *ND, const CXXRecordDecl *RD) {
  94. const DeclContext *NDParent = ND->getDeclContext();
  95. if (!NDParent || !isa<CXXRecordDecl>(NDParent))
  96. return false;
  97. if (NDParent == RD)
  98. return true;
  99. return !RD->forallBases(
  100. [NDParent](const CXXRecordDecl *Base) { return NDParent != Base; });
  101. }
  102. static bool mayShadow(const NamedDecl *ND0, const NamedDecl *ND1) {
  103. const DeclContext *DC0 = ND0->getDeclContext()->getPrimaryContext();
  104. const DeclContext *DC1 = ND1->getDeclContext()->getPrimaryContext();
  105. if (const CXXRecordDecl *RD0 = dyn_cast<CXXRecordDecl>(DC0)) {
  106. RD0 = RD0->getDefinition();
  107. if (RD0 && ND1->getAccess() != AS_private && isMemberOf(ND1, RD0))
  108. return true;
  109. }
  110. if (const CXXRecordDecl *RD1 = dyn_cast<CXXRecordDecl>(DC1)) {
  111. RD1 = RD1->getDefinition();
  112. if (RD1 && ND0->getAccess() != AS_private && isMemberOf(ND0, RD1))
  113. return true;
  114. }
  115. if (DC0->Encloses(DC1))
  116. return mayShadowImpl(ND0, ND1);
  117. if (DC1->Encloses(DC0))
  118. return mayShadowImpl(ND1, ND0);
  119. return false;
  120. }
  121. void ConfusableIdentifierCheck::check(
  122. const ast_matchers::MatchFinder::MatchResult &Result) {
  123. if (const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("nameddecl")) {
  124. if (IdentifierInfo *NDII = ND->getIdentifier()) {
  125. StringRef NDName = NDII->getName();
  126. llvm::SmallVector<const NamedDecl *> &Mapped = Mapper[skeleton(NDName)];
  127. for (const NamedDecl *OND : Mapped) {
  128. const IdentifierInfo *ONDII = OND->getIdentifier();
  129. if (mayShadow(ND, OND)) {
  130. StringRef ONDName = ONDII->getName();
  131. if (ONDName != NDName) {
  132. diag(ND->getLocation(), "%0 is confusable with %1") << ND << OND;
  133. diag(OND->getLocation(), "other declaration found here",
  134. DiagnosticIDs::Note);
  135. }
  136. }
  137. }
  138. Mapped.push_back(ND);
  139. }
  140. }
  141. }
  142. void ConfusableIdentifierCheck::registerMatchers(
  143. ast_matchers::MatchFinder *Finder) {
  144. Finder->addMatcher(ast_matchers::namedDecl().bind("nameddecl"), this);
  145. }
  146. } // namespace clang::tidy::misc