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 
ConfusableIdentifierCheck(StringRef Name,ClangTidyContext * Context)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 
skeleton(StringRef Name)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 
mayShadowImpl(const NamedDecl * ND0,const NamedDecl * ND1)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 
isMemberOf(const NamedDecl * ND,const CXXRecordDecl * RD)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 
mayShadow(const NamedDecl * ND0,const NamedDecl * ND1)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 
check(const ast_matchers::MatchFinder::MatchResult & Result)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 
registerMatchers(ast_matchers::MatchFinder * Finder)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