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