1 //===--- ConfusableIdentifierCheck.cpp -
2 // clang-tidy--------------------------===//
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
8 //===----------------------------------------------------------------------===//
10 #include "ConfusableIdentifierCheck.h"
12 #include "clang/Frontend/CompilerInstance.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include "llvm/ADT/SmallString.h"
15 #include "llvm/Support/ConvertUTF.h"
18 // Preprocessed version of
19 // https://www.unicode.org/Public/security/latest/confusables.txt
21 // This contains a sorted array of { UTF32 codepoint; UTF32 values[N];}
22 #include "Confusables.inc"
25 namespace clang::tidy::misc
{
27 ConfusableIdentifierCheck::ConfusableIdentifierCheck(StringRef Name
,
28 ClangTidyContext
*Context
)
29 : ClangTidyCheck(Name
, Context
) {}
31 ConfusableIdentifierCheck::~ConfusableIdentifierCheck() = default;
33 // Build a skeleton out of the Original identifier, inspired by the algorithm
34 // described in http://www.unicode.org/reports/tr39/#def-skeleton
36 // FIXME: TR39 mandates:
38 // For an input string X, define skeleton(X) to be the following transformation
41 // 1. Convert X to NFD format, as described in [UAX15].
42 // 2. Concatenate the prototypes for each character in X according to the
43 // specified data, producing a string of exemplar characters.
46 // We're skipping 1. and 3. for the sake of simplicity, but this can lead to
49 static llvm::SmallString
<64U> skeleton(StringRef Name
) {
51 SmallString
<64U> Skeleton
;
52 Skeleton
.reserve(1U + Name
.size());
54 const char *Curr
= Name
.data();
55 const char *End
= Curr
+ Name
.size();
58 const char *Prev
= Curr
;
60 ConversionResult Result
= convertUTF8Sequence(
61 reinterpret_cast<const UTF8
**>(&Curr
),
62 reinterpret_cast<const UTF8
*>(End
), &CodePoint
, strictConversion
);
63 if (Result
!= conversionOK
) {
64 errs() << "Unicode conversion issue\n";
68 StringRef
Key(Prev
, Curr
- Prev
);
69 auto Where
= llvm::lower_bound(ConfusableEntries
, CodePoint
,
70 [](decltype(ConfusableEntries
[0]) x
,
71 UTF32 y
) { return x
.codepoint
< y
; });
72 if (Where
== std::end(ConfusableEntries
) || CodePoint
!= Where
->codepoint
) {
73 Skeleton
.append(Prev
, Curr
);
76 UTF8
*BufferStart
= std::begin(Buffer
);
77 UTF8
*IBuffer
= BufferStart
;
78 const UTF32
*ValuesStart
= std::begin(Where
->values
);
79 const UTF32
*ValuesEnd
= llvm::find(Where
->values
, '\0');
80 if (ConvertUTF32toUTF8(&ValuesStart
, ValuesEnd
, &IBuffer
,
82 strictConversion
) != conversionOK
) {
83 errs() << "Unicode conversion issue\n";
86 Skeleton
.append((char *)BufferStart
, (char *)IBuffer
);
92 static bool mayShadowImpl(const DeclContext
*DC0
, const DeclContext
*DC1
) {
93 return DC0
&& DC0
== DC1
;
96 static bool mayShadowImpl(const NamedDecl
*ND0
, const NamedDecl
*ND1
) {
97 return isa
<TemplateTypeParmDecl
>(ND0
) || isa
<TemplateTypeParmDecl
>(ND1
);
100 static bool isMemberOf(const ConfusableIdentifierCheck::ContextInfo
*DC0
,
101 const ConfusableIdentifierCheck::ContextInfo
*DC1
) {
102 return llvm::is_contained(DC1
->Bases
, DC0
->PrimaryContext
);
105 static bool enclosesContext(const ConfusableIdentifierCheck::ContextInfo
*DC0
,
106 const ConfusableIdentifierCheck::ContextInfo
*DC1
) {
107 if (DC0
->PrimaryContext
== DC1
->PrimaryContext
)
110 return llvm::is_contained(DC0
->PrimaryContexts
, DC1
->PrimaryContext
) ||
111 llvm::is_contained(DC1
->PrimaryContexts
, DC0
->PrimaryContext
);
114 static bool mayShadow(const NamedDecl
*ND0
,
115 const ConfusableIdentifierCheck::ContextInfo
*DC0
,
116 const NamedDecl
*ND1
,
117 const ConfusableIdentifierCheck::ContextInfo
*DC1
) {
119 if (!DC0
->Bases
.empty() && !DC1
->Bases
.empty()) {
120 // if any of the declaration is a non-private member of the other
121 // declaration, it's shadowed by the former
123 if (ND1
->getAccess() != AS_private
&& isMemberOf(DC1
, DC0
))
126 if (ND0
->getAccess() != AS_private
&& isMemberOf(DC0
, DC1
))
130 if (!mayShadowImpl(DC0
->NonTransparentContext
, DC1
->NonTransparentContext
) &&
131 !mayShadowImpl(ND0
, ND1
))
134 return enclosesContext(DC0
, DC1
);
137 const ConfusableIdentifierCheck::ContextInfo
*
138 ConfusableIdentifierCheck::getContextInfo(const DeclContext
*DC
) {
139 const DeclContext
*PrimaryContext
= DC
->getPrimaryContext();
140 auto [It
, Inserted
] = ContextInfos
.try_emplace(PrimaryContext
);
144 ContextInfo
&Info
= It
->second
;
145 Info
.PrimaryContext
= PrimaryContext
;
146 Info
.NonTransparentContext
= PrimaryContext
;
148 while (Info
.NonTransparentContext
->isTransparentContext()) {
149 Info
.NonTransparentContext
= Info
.NonTransparentContext
->getParent();
150 if (!Info
.NonTransparentContext
)
154 if (Info
.NonTransparentContext
)
155 Info
.NonTransparentContext
=
156 Info
.NonTransparentContext
->getPrimaryContext();
159 if (!isa
<LinkageSpecDecl
>(DC
) && !isa
<ExportDecl
>(DC
))
160 Info
.PrimaryContexts
.push_back(DC
->getPrimaryContext());
161 DC
= DC
->getParent();
164 if (const auto *RD
= dyn_cast
<CXXRecordDecl
>(PrimaryContext
)) {
165 RD
= RD
->getDefinition();
167 Info
.Bases
.push_back(RD
);
168 RD
->forallBases([&](const CXXRecordDecl
*Base
) {
169 Info
.Bases
.push_back(Base
);
178 void ConfusableIdentifierCheck::check(
179 const ast_matchers::MatchFinder::MatchResult
&Result
) {
180 const auto *ND
= Result
.Nodes
.getNodeAs
<NamedDecl
>("nameddecl");
184 IdentifierInfo
*NDII
= ND
->getIdentifier();
188 StringRef NDName
= NDII
->getName();
192 const ContextInfo
*Info
= getContextInfo(ND
->getDeclContext());
194 llvm::SmallVector
<Entry
> &Mapped
= Mapper
[skeleton(NDName
)];
195 for (const Entry
&E
: Mapped
) {
196 if (!mayShadow(ND
, Info
, E
.Declaration
, E
.Info
))
199 const IdentifierInfo
*ONDII
= E
.Declaration
->getIdentifier();
200 StringRef ONDName
= ONDII
->getName();
201 if (ONDName
== NDName
)
204 diag(ND
->getLocation(), "%0 is confusable with %1") << ND
<< E
.Declaration
;
205 diag(E
.Declaration
->getLocation(), "other declaration found here",
206 DiagnosticIDs::Note
);
209 Mapped
.push_back({ND
, Info
});
212 void ConfusableIdentifierCheck::onEndOfTranslationUnit() {
214 ContextInfos
.clear();
217 void ConfusableIdentifierCheck::registerMatchers(
218 ast_matchers::MatchFinder
*Finder
) {
219 Finder
->addMatcher(ast_matchers::namedDecl().bind("nameddecl"), this);
222 } // namespace clang::tidy::misc