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/Lex/Preprocessor.h"
13#include "llvm/ADT/SmallString.h"
14#include "llvm/Support/ConvertUTF.h"
15
16namespace {
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
24namespace clang::tidy::misc {
25
26ConfusableIdentifierCheck::ConfusableIdentifierCheck(StringRef Name,
27 ClangTidyContext *Context)
28 : ClangTidyCheck(Name, Context) {}
29
30ConfusableIdentifierCheck::~ConfusableIdentifierCheck() = default;
31
32// Build a skeleton out of the Original identifier, inspired by the algorithm
33// described in http://www.unicode.org/reports/tr39/#def-skeleton
34//
35// FIXME: TR39 mandates:
36//
37// For an input string X, define skeleton(X) to be the following transformation
38// on the string:
39//
40// 1. Convert X to NFD format, as described in [UAX15].
41// 2. Concatenate the prototypes for each character in X according to the
42// specified data, producing a string of exemplar characters.
43// 3. Reapply NFD.
44//
45// We're skipping 1. and 3. for the sake of simplicity, but this can lead to
46// false positive.
47
48static llvm::SmallString<64U> skeleton(StringRef Name) {
49 using namespace llvm;
50 SmallString<64U> Skeleton;
51 Skeleton.reserve(1U + Name.size());
52
53 const char *Curr = Name.data();
54 const char *End = Curr + Name.size();
55 while (Curr < End) {
56
57 const char *Prev = Curr;
58 UTF32 CodePoint = 0;
59 ConversionResult Result = convertUTF8Sequence(
60 source: reinterpret_cast<const UTF8 **>(&Curr),
61 sourceEnd: reinterpret_cast<const UTF8 *>(End), target: &CodePoint, flags: strictConversion);
62 if (Result != conversionOK) {
63 errs() << "Unicode conversion issue\n";
64 break;
65 }
66
67 StringRef Key(Prev, Curr - Prev);
68 auto *Where = llvm::lower_bound(ConfusableEntries, CodePoint,
69 [](decltype(ConfusableEntries[0]) X,
70 UTF32 Y) { return X.codepoint < Y; });
71 if (Where == std::end(ConfusableEntries) || CodePoint != Where->codepoint) {
72 Skeleton.append(Prev, Curr);
73 } else {
74 UTF8 Buffer[32];
75 UTF8 *BufferStart = std::begin(Buffer);
76 UTF8 *IBuffer = BufferStart;
77 const UTF32 *ValuesStart = std::begin(Where->values);
78 const UTF32 *ValuesEnd = llvm::find(Where->values, '\0');
79 if (ConvertUTF32toUTF8(&ValuesStart, ValuesEnd, &IBuffer,
80 std::end(Buffer),
81 strictConversion) != conversionOK) {
82 errs() << "Unicode conversion issue\n";
83 break;
84 }
85 Skeleton.append((char *)BufferStart, (char *)IBuffer);
86 }
87 }
88 return Skeleton;
89}
90
91static bool mayShadowImpl(const DeclContext *DC0, const DeclContext *DC1) {
92 return DC0 && DC0 == DC1;
93}
94
95static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1) {
96 return isa<TemplateTypeParmDecl>(ND0) || isa<TemplateTypeParmDecl>(ND1);
97}
98
99static bool isMemberOf(const ConfusableIdentifierCheck::ContextInfo *DC0,
100 const ConfusableIdentifierCheck::ContextInfo *DC1) {
101 return llvm::is_contained(DC1->Bases, DC0->PrimaryContext);
102}
103
104static bool enclosesContext(const ConfusableIdentifierCheck::ContextInfo *DC0,
105 const ConfusableIdentifierCheck::ContextInfo *DC1) {
106 if (DC0->PrimaryContext == DC1->PrimaryContext)
107 return true;
108
109 return llvm::is_contained(DC0->PrimaryContexts, DC1->PrimaryContext) ||
110 llvm::is_contained(DC1->PrimaryContexts, DC0->PrimaryContext);
111}
112
113static bool mayShadow(const NamedDecl *ND0,
114 const ConfusableIdentifierCheck::ContextInfo *DC0,
115 const NamedDecl *ND1,
116 const ConfusableIdentifierCheck::ContextInfo *DC1) {
117
118 if (!DC0->Bases.empty() && !DC1->Bases.empty()) {
119 // if any of the declaration is a non-private member of the other
120 // declaration, it's shadowed by the former
121
122 if (ND1->getAccess() != AS_private && isMemberOf(DC0: DC1, DC1: DC0))
123 return true;
124
125 if (ND0->getAccess() != AS_private && isMemberOf(DC0, DC1))
126 return true;
127 }
128
129 if (!mayShadowImpl(DC0: DC0->NonTransparentContext, DC1: DC1->NonTransparentContext) &&
130 !mayShadowImpl(ND0, ND1))
131 return false;
132
133 return enclosesContext(DC0, DC1);
134}
135
136const ConfusableIdentifierCheck::ContextInfo *
137ConfusableIdentifierCheck::getContextInfo(const DeclContext *DC) {
138 const DeclContext *PrimaryContext = DC->getPrimaryContext();
139 auto [It, Inserted] = ContextInfos.try_emplace(PrimaryContext);
140 if (!Inserted)
141 return &It->second;
142
143 ContextInfo &Info = It->second;
144 Info.PrimaryContext = PrimaryContext;
145 Info.NonTransparentContext = PrimaryContext;
146
147 while (Info.NonTransparentContext->isTransparentContext()) {
148 Info.NonTransparentContext = Info.NonTransparentContext->getParent();
149 if (!Info.NonTransparentContext)
150 break;
151 }
152
153 if (Info.NonTransparentContext)
154 Info.NonTransparentContext =
155 Info.NonTransparentContext->getPrimaryContext();
156
157 while (DC) {
158 if (!isa<LinkageSpecDecl>(DC) && !isa<ExportDecl>(DC))
159 Info.PrimaryContexts.push_back(Elt: DC->getPrimaryContext());
160 DC = DC->getParent();
161 }
162
163 if (const auto *RD = dyn_cast<CXXRecordDecl>(PrimaryContext)) {
164 RD = RD->getDefinition();
165 if (RD) {
166 Info.Bases.push_back(Elt: RD);
167 RD->forallBases([&](const CXXRecordDecl *Base) {
168 Info.Bases.push_back(Elt: Base);
169 return false;
170 });
171 }
172 }
173
174 return &Info;
175}
176
177void ConfusableIdentifierCheck::check(
178 const ast_matchers::MatchFinder::MatchResult &Result) {
179 const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("nameddecl");
180 if (!ND)
181 return;
182
183 IdentifierInfo *NDII = ND->getIdentifier();
184 if (!NDII)
185 return;
186
187 StringRef NDName = NDII->getName();
188 if (NDName.empty())
189 return;
190
191 const ContextInfo *Info = getContextInfo(DC: ND->getDeclContext());
192
193 llvm::SmallVector<Entry> &Mapped = Mapper[skeleton(NDName)];
194 for (const Entry &E : Mapped) {
195 if (!mayShadow(ND, Info, E.Declaration, E.Info))
196 continue;
197
198 const IdentifierInfo *ONDII = E.Declaration->getIdentifier();
199 StringRef ONDName = ONDII->getName();
200 if (ONDName == NDName)
201 continue;
202
203 diag(ND->getLocation(), "%0 is confusable with %1") << ND << E.Declaration;
204 diag(E.Declaration->getLocation(), "other declaration found here",
205 DiagnosticIDs::Note);
206 }
207
208 Mapped.push_back({ND, Info});
209}
210
211void ConfusableIdentifierCheck::onEndOfTranslationUnit() {
212 Mapper.clear();
213 ContextInfos.clear();
214}
215
216void ConfusableIdentifierCheck::registerMatchers(
217 ast_matchers::MatchFinder *Finder) {
218 Finder->addMatcher(ast_matchers::namedDecl().bind("nameddecl"), this);
219}
220
221} // namespace clang::tidy::misc
222

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp