clang-tools 17.0.0git
ConfusableIdentifierCheck.cpp
Go to the documentation of this file.
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
11
12#include "clang/Frontend/CompilerInstance.h"
13#include "clang/Lex/Preprocessor.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
25
27 ClangTidyContext *Context)
28 : ClangTidyCheck(Name, Context) {}
29
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
48std::string ConfusableIdentifierCheck::skeleton(StringRef Name) {
49 using namespace llvm;
50 std::string SName = Name.str();
51 std::string Skeleton;
52 Skeleton.reserve(1 + Name.size());
53
54 const char *Curr = SName.c_str();
55 const char *End = Curr + SName.size();
56 while (Curr < End) {
57
58 const char *Prev = Curr;
59 UTF32 CodePoint;
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";
65 break;
66 }
67
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);
74 } else {
75 UTF8 Buffer[32];
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,
81 std::end(Buffer),
82 strictConversion) != conversionOK) {
83 errs() << "Unicode conversion issue\n";
84 break;
85 }
86 Skeleton.append((char *)BufferStart, (char *)IBuffer);
87 }
88 }
89 return Skeleton;
90}
91
92static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1) {
93 const DeclContext *DC0 = ND0->getDeclContext()->getPrimaryContext();
94 const DeclContext *DC1 = ND1->getDeclContext()->getPrimaryContext();
95
96 if (isa<TemplateTypeParmDecl>(ND0) || isa<TemplateTypeParmDecl>(ND0))
97 return true;
98
99 while (DC0->isTransparentContext())
100 DC0 = DC0->getParent();
101 while (DC1->isTransparentContext())
102 DC1 = DC1->getParent();
103
104 if (DC0->Equals(DC1))
105 return true;
106
107 return false;
108}
109
110static bool isMemberOf(const NamedDecl *ND, const CXXRecordDecl *RD) {
111 const DeclContext *NDParent = ND->getDeclContext();
112 if (!NDParent || !isa<CXXRecordDecl>(NDParent))
113 return false;
114 if (NDParent == RD)
115 return true;
116 return !RD->forallBases(
117 [NDParent](const CXXRecordDecl *Base) { return NDParent != Base; });
118}
119
120static bool mayShadow(const NamedDecl *ND0, const NamedDecl *ND1) {
121
122 const DeclContext *DC0 = ND0->getDeclContext()->getPrimaryContext();
123 const DeclContext *DC1 = ND1->getDeclContext()->getPrimaryContext();
124
125 if (const CXXRecordDecl *RD0 = dyn_cast<CXXRecordDecl>(DC0)) {
126 RD0 = RD0->getDefinition();
127 if (RD0 && ND1->getAccess() != AS_private && isMemberOf(ND1, RD0))
128 return true;
129 }
130 if (const CXXRecordDecl *RD1 = dyn_cast<CXXRecordDecl>(DC1)) {
131 RD1 = RD1->getDefinition();
132 if (RD1 && ND0->getAccess() != AS_private && isMemberOf(ND0, RD1))
133 return true;
134 }
135
136 if (DC0->Encloses(DC1))
137 return mayShadowImpl(ND0, ND1);
138 if (DC1->Encloses(DC0))
139 return mayShadowImpl(ND1, ND0);
140 return false;
141}
142
144 const ast_matchers::MatchFinder::MatchResult &Result) {
145 if (const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("nameddecl")) {
146 if (IdentifierInfo *NDII = ND->getIdentifier()) {
147 StringRef NDName = NDII->getName();
148 llvm::SmallVector<const NamedDecl *> &Mapped = Mapper[skeleton(NDName)];
149 for (const NamedDecl *OND : Mapped) {
150 const IdentifierInfo *ONDII = OND->getIdentifier();
151 if (mayShadow(ND, OND)) {
152 StringRef ONDName = ONDII->getName();
153 if (ONDName != NDName) {
154 diag(ND->getLocation(), "%0 is confusable with %1") << ND << OND;
155 diag(OND->getLocation(), "other declaration found here",
156 DiagnosticIDs::Note);
157 }
158 }
159 }
160 Mapped.push_back(ND);
161 }
162 }
163}
164
166 ast_matchers::MatchFinder *Finder) {
167 Finder->addMatcher(ast_matchers::namedDecl().bind("nameddecl"), this);
168}
169
170} // namespace clang::tidy::misc
Token Name
const CXXCtorInitializer * Where
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
ConfusableIdentifierCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1)
static bool isMemberOf(const NamedDecl *ND, const CXXRecordDecl *RD)
static bool mayShadow(const NamedDecl *ND0, const NamedDecl *ND1)
Some operations such as code completion produce a set of candidates.