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