clang-tools 23.0.0git
ConfusableIdentifierCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
11#include "clang/ASTMatchers/ASTMatchers.h"
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 code points and slices into a shared UTF-8
21// replacement array.
22struct ConfusableEntry {
23 llvm::UTF32 CodePoint;
24 uint16_t Offset;
25 uint16_t Length;
26};
27static_assert(sizeof(ConfusableEntry) == 8);
28
29#include "Confusables.inc"
30static_assert(sizeof(ConfusableReplacementData) <= 1ULL << 16);
31} // namespace
32
33namespace clang::tidy::misc {
34
38
40
41// Build a skeleton out of the Original identifier, inspired by the algorithm
42// described in https://www.unicode.org/reports/tr39/#def-skeleton
43//
44// FIXME: TR39 mandates:
45//
46// For an input string X, define skeleton(X) to be the following transformation
47// on the string:
48//
49// 1. Convert X to NFD format, as described in [UAX15].
50// 2. Concatenate the prototypes for each character in X according to the
51// specified data, producing a string of exemplar characters.
52// 3. Reapply NFD.
53//
54// We're skipping 1. and 3. for the sake of simplicity, but this can lead to
55// false positive.
56
57static SmallString<64U> skeleton(StringRef Name) {
58 using namespace llvm;
59 SmallString<64U> Skeleton;
60 Skeleton.reserve(1U + Name.size());
61
62 const char *Curr = Name.data();
63 const char *End = Curr + Name.size();
64 while (Curr < End) {
65 const char *Prev = Curr;
66 UTF32 CodePoint = 0;
67 const ConversionResult Result = convertUTF8Sequence(
68 reinterpret_cast<const UTF8 **>(&Curr),
69 reinterpret_cast<const UTF8 *>(End), &CodePoint, strictConversion);
70 if (Result != conversionOK) {
71 errs() << "Unicode conversion issue\n";
72 break;
73 }
74
75 auto *Where = llvm::lower_bound(
76 ConfusableEntries, CodePoint,
77 [](const ConfusableEntry &X, UTF32 Y) { return X.CodePoint < Y; });
78 if (Where == std::end(ConfusableEntries) || CodePoint != Where->CodePoint) {
79 Skeleton.append(Prev, Curr);
80 } else {
81 Skeleton.append(
82 StringRef(ConfusableReplacementData + Where->Offset, Where->Length));
83 }
84 }
85 return Skeleton;
86}
87
88namespace {
89struct Entry {
90 const NamedDecl *ND;
91 const Decl *Parent;
92 bool FromDerivedClass;
93};
94} // namespace
95
96// Map from a context to the declarations in that context with the current
97// skeleton. At most one entry per distinct identifier is tracked. The
98// context is usually a `DeclContext`, but can also be a template declaration
99// that has no corresponding context, such as an alias template or variable
100// template.
102 llvm::DenseMap<const Decl *, SmallVector<Entry, 1>>;
103
104static bool addToContext(DeclsWithinContextMap &DeclsWithinContext,
105 const Decl *Context, Entry E) {
106 auto &Decls = DeclsWithinContext[Context];
107 if (!Decls.empty() &&
108 Decls.back().ND->getIdentifier() == E.ND->getIdentifier()) {
109 // Already have a declaration with this identifier in this context. Don't
110 // track another one. This means that if an outer name is confusable with an
111 // inner name, we'll only diagnose the outer name once, pointing at the
112 // first inner declaration with that name.
113 if (Decls.back().FromDerivedClass && !E.FromDerivedClass) {
114 // Prefer the declaration that's not from the derived class, because that
115 // conflicts with more declarations.
116 Decls.back() = E;
117 return true;
118 }
119 return false;
120 }
121 Decls.push_back(E);
122 return true;
123}
124
125static void addToEnclosingContexts(DeclsWithinContextMap &DeclsWithinContext,
126 const Decl *Parent, const NamedDecl *ND) {
127 const Decl *Outer = Parent;
128 while (Outer) {
129 if (const auto *NS = dyn_cast<NamespaceDecl>(Outer))
130 Outer = NS->getCanonicalDecl();
131
132 if (!addToContext(DeclsWithinContext, Outer, {ND, Parent, false}))
133 return;
134
135 if (const auto *RD = dyn_cast<CXXRecordDecl>(Outer)) {
136 RD = RD->getDefinition();
137 if (RD) {
138 RD->forallBases([&](const CXXRecordDecl *Base) {
139 addToContext(DeclsWithinContext, Base, {ND, Parent, true});
140 return true;
141 });
142 }
143 }
144
145 auto *OuterDC = Outer->getDeclContext();
146 if (!OuterDC)
147 break;
148 Outer = cast_or_null<Decl>(OuterDC->getNonTransparentContext());
149 }
150}
151
153 const ast_matchers::MatchFinder::MatchResult &Result) {
154 const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("nameddecl");
155 if (!ND)
156 return;
157
158 addDeclToCheck(ND,
159 cast<Decl>(ND->getDeclContext()->getNonTransparentContext()));
160
161 // Associate template parameters with this declaration of this template.
162 if (const auto *TD = dyn_cast<TemplateDecl>(ND))
163 for (const NamedDecl *Param : *TD->getTemplateParameters())
164 addDeclToCheck(Param, TD->getTemplatedDecl());
165
166 // Associate function parameters with this declaration of this function.
167 if (const auto *FD = dyn_cast<FunctionDecl>(ND))
168 for (const NamedDecl *Param : FD->parameters())
169 addDeclToCheck(Param, ND);
170}
171
172void ConfusableIdentifierCheck::addDeclToCheck(const NamedDecl *ND,
173 const Decl *Parent) {
174 if (!ND || !Parent)
175 return;
176
177 const IdentifierInfo *NDII = ND->getIdentifier();
178 if (!NDII)
179 return;
180
181 const StringRef NDName = NDII->getName();
182 if (NDName.empty())
183 return;
184
185 NameToDecls[NDII].push_back({ND, Parent});
186}
187
189 llvm::StringMap<SmallVector<const IdentifierInfo *, 1>> SkeletonToNames;
190 // Compute the skeleton for each identifier.
191 for (auto &[Ident, Decls] : NameToDecls)
192 SkeletonToNames[skeleton(Ident->getName())].push_back(Ident);
193
194 // Visit each skeleton with more than one identifier.
195 for (auto &[Skel, Idents] : SkeletonToNames) {
196 if (Idents.size() < 2)
197 continue;
198
199 // Find the declaration contexts that transitively contain each identifier.
200 DeclsWithinContextMap DeclsWithinContext;
201 for (const IdentifierInfo *II : Idents)
202 for (auto [ND, Parent] : NameToDecls[II])
203 addToEnclosingContexts(DeclsWithinContext, Parent, ND);
204
205 // Check to see if any declaration is declared in a context that
206 // transitively contains another declaration with a different identifier but
207 // the same skeleton.
208 for (const IdentifierInfo *II : Idents) {
209 for (auto [OuterND, OuterParent] : NameToDecls[II]) {
210 for (const Entry Inner : DeclsWithinContext[OuterParent]) {
211 // Don't complain if the identifiers are the same.
212 if (OuterND->getIdentifier() == Inner.ND->getIdentifier())
213 continue;
214
215 // Don't complain about a derived-class name shadowing a base class
216 // private member.
217 if (OuterND->getAccess() == AS_private && Inner.FromDerivedClass)
218 continue;
219
220 // If the declarations are in the same context, only diagnose the
221 // later one.
222 if (OuterParent == Inner.Parent &&
223 Inner.ND->getASTContext()
224 .getSourceManager()
225 .isBeforeInTranslationUnit(Inner.ND->getLocation(),
226 OuterND->getLocation()))
227 continue;
228
229 diag(Inner.ND->getLocation(), "%0 is confusable with %1")
230 << Inner.ND << OuterND;
231 diag(OuterND->getLocation(), "other declaration found here",
232 DiagnosticIDs::Note);
233 }
234 }
235 }
236 }
237
238 NameToDecls.clear();
239}
240
242 ast_matchers::MatchFinder *Finder) {
243 // Parameter declarations sometimes use the translation unit or some outer
244 // enclosing context as their `DeclContext`, instead of their parent, so
245 // we handle them specially in `check`.
246 auto AnyParamDecl = ast_matchers::anyOf(
247 ast_matchers::parmVarDecl(), ast_matchers::templateTypeParmDecl(),
248 ast_matchers::nonTypeTemplateParmDecl(),
249 ast_matchers::templateTemplateParmDecl());
250 Finder->addMatcher(ast_matchers::namedDecl(ast_matchers::unless(AnyParamDecl))
251 .bind("nameddecl"),
252 this);
253}
254
255} // namespace clang::tidy::misc
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ConfusableIdentifierCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static bool addToContext(DeclsWithinContextMap &DeclsWithinContext, const Decl *Context, Entry E)
static void addToEnclosingContexts(DeclsWithinContextMap &DeclsWithinContext, const Decl *Parent, const NamedDecl *ND)
static SmallString< 64U > skeleton(StringRef Name)
llvm::DenseMap< const Decl *, SmallVector< Entry, 1 > > DeclsWithinContextMap
static ClangTidyModuleRegistry::Add< altera::AlteraModule > X("altera-module", "Adds Altera FPGA OpenCL lint checks.")
Some operations such as code completion produce a set of candidates.
Definition Generators.h:152