Skip to content

Commit 78c9d5d

Browse files
committed
Switch misc-confusable-identifiers check to a faster algorithm.
- Only build the skeleton for each identifier once. - Only compute the contexts in which identifiers are declared for identifiers that actually have collisions. - Only compare pairs of declarations that are declared in related contexts, rather than comparing all pairs of declarations with the same skeleton. This also fixes several bugs: - The old check skipped comparisons of declarations from different contexts unless both declarations were type template parameters. - The old check skipped comparisons of declarations in all base classes other than the first one found by the traversal. This decreases the runtime of this check, especially in the common case where there are few or no skeletons with two or more different identifiers. From a sample invocation: ``` ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- 0.1556 ( 1.4%) 0.1513 ( 1.8%) 0.3069 ( 1.5%) 0.3033 ( 1.5%) before 0.0746 ( 0.7%) 0.0751 ( 0.8%) 0.1498 ( 0.8%) 0.1491 ( 0.8%) after ```
1 parent 1b75b9e commit 78c9d5d

File tree

3 files changed

+133
-110
lines changed

3 files changed

+133
-110
lines changed

clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp

Lines changed: 98 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -89,90 +89,56 @@ static llvm::SmallString<64U> skeleton(StringRef Name) {
8989
return Skeleton;
9090
}
9191

92-
static bool mayShadowImpl(const DeclContext *DC0, const DeclContext *DC1) {
93-
return DC0 && DC0 == DC1;
94-
}
95-
96-
static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1) {
97-
return isa<TemplateTypeParmDecl>(ND0) || isa<TemplateTypeParmDecl>(ND1);
98-
}
99-
100-
static bool isMemberOf(const ConfusableIdentifierCheck::ContextInfo *DC0,
101-
const ConfusableIdentifierCheck::ContextInfo *DC1) {
102-
return llvm::is_contained(DC1->Bases, DC0->PrimaryContext);
103-
}
104-
105-
static bool enclosesContext(const ConfusableIdentifierCheck::ContextInfo *DC0,
106-
const ConfusableIdentifierCheck::ContextInfo *DC1) {
107-
if (DC0->PrimaryContext == DC1->PrimaryContext)
108-
return true;
109-
110-
return llvm::is_contained(DC0->PrimaryContexts, DC1->PrimaryContext) ||
111-
llvm::is_contained(DC1->PrimaryContexts, DC0->PrimaryContext);
92+
namespace {
93+
struct Entry {
94+
const NamedDecl *ND;
95+
bool FromDerivedClass;
96+
};
11297
}
11398

114-
static bool mayShadow(const NamedDecl *ND0,
115-
const ConfusableIdentifierCheck::ContextInfo *DC0,
116-
const NamedDecl *ND1,
117-
const ConfusableIdentifierCheck::ContextInfo *DC1) {
118-
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
122-
123-
if (ND1->getAccess() != AS_private && isMemberOf(DC1, DC0))
124-
return true;
125-
126-
if (ND0->getAccess() != AS_private && isMemberOf(DC0, DC1))
99+
using DeclsWithinContextMap =
100+
llvm::DenseMap<const DeclContext *, llvm::SmallVector<Entry, 1>>;
101+
102+
static bool addToContext(DeclsWithinContextMap &DeclsWithinContext,
103+
const DeclContext *DC, Entry E) {
104+
auto &Decls = DeclsWithinContext[DC];
105+
if (!Decls.empty() &&
106+
Decls.back().ND->getIdentifier() == E.ND->getIdentifier()) {
107+
// Already have a declaration with this identifier in this context. Don't
108+
// track another one. This means that if an outer name is confusable with an
109+
// inner name, we'll only diagnose the outer name once, pointing at the
110+
// first inner declaration with that name.
111+
if (Decls.back().FromDerivedClass && !E.FromDerivedClass) {
112+
// Prefer the declaration that's not from the derived class, because that
113+
// conflicts with more declarations.
114+
Decls.back() = E;
127115
return true;
128-
}
129-
130-
if (!mayShadowImpl(DC0->NonTransparentContext, DC1->NonTransparentContext) &&
131-
!mayShadowImpl(ND0, ND1))
116+
}
132117
return false;
133-
134-
return enclosesContext(DC0, DC1);
135-
}
136-
137-
const ConfusableIdentifierCheck::ContextInfo *
138-
ConfusableIdentifierCheck::getContextInfo(const DeclContext *DC) {
139-
const DeclContext *PrimaryContext = DC->getPrimaryContext();
140-
auto [It, Inserted] = ContextInfos.try_emplace(PrimaryContext);
141-
if (!Inserted)
142-
return &It->second;
143-
144-
ContextInfo &Info = It->second;
145-
Info.PrimaryContext = PrimaryContext;
146-
Info.NonTransparentContext = PrimaryContext;
147-
148-
while (Info.NonTransparentContext->isTransparentContext()) {
149-
Info.NonTransparentContext = Info.NonTransparentContext->getParent();
150-
if (!Info.NonTransparentContext)
151-
break;
152118
}
119+
Decls.push_back(E);
120+
return true;
121+
}
153122

154-
if (Info.NonTransparentContext)
155-
Info.NonTransparentContext =
156-
Info.NonTransparentContext->getPrimaryContext();
157-
123+
static void addToEnclosingContexts(DeclsWithinContextMap &DeclsWithinContext,
124+
const DeclContext *DC, const NamedDecl *ND) {
158125
while (DC) {
159-
if (!isa<LinkageSpecDecl>(DC) && !isa<ExportDecl>(DC))
160-
Info.PrimaryContexts.push_back(DC->getPrimaryContext());
161-
DC = DC->getParent();
162-
}
163-
164-
if (const auto *RD = dyn_cast<CXXRecordDecl>(PrimaryContext)) {
165-
RD = RD->getDefinition();
166-
if (RD) {
167-
Info.Bases.push_back(RD);
168-
RD->forallBases([&](const CXXRecordDecl *Base) {
169-
Info.Bases.push_back(Base);
170-
return false;
171-
});
126+
DC = DC->getNonTransparentContext()->getPrimaryContext();
127+
if (!addToContext(DeclsWithinContext, DC, {ND, false}))
128+
return;
129+
130+
if (const auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
131+
RD = RD->getDefinition();
132+
if (RD) {
133+
RD->forallBases([&](const CXXRecordDecl *Base) {
134+
addToContext(DeclsWithinContext, Base, {ND, true});
135+
return true;
136+
});
137+
}
172138
}
173-
}
174139

175-
return &Info;
140+
DC = DC->getParent();
141+
}
176142
}
177143

178144
void ConfusableIdentifierCheck::check(
@@ -181,37 +147,76 @@ void ConfusableIdentifierCheck::check(
181147
if (!ND)
182148
return;
183149

184-
IdentifierInfo *NDII = ND->getIdentifier();
150+
const IdentifierInfo *NDII = ND->getIdentifier();
185151
if (!NDII)
186152
return;
187153

188154
StringRef NDName = NDII->getName();
189155
if (NDName.empty())
190156
return;
191157

192-
const ContextInfo *Info = getContextInfo(ND->getDeclContext());
158+
NameToDecls[NDII].push_back(ND);
159+
}
193160

194-
llvm::SmallVector<Entry> &Mapped = Mapper[skeleton(NDName)];
195-
for (const Entry &E : Mapped) {
196-
if (!mayShadow(ND, Info, E.Declaration, E.Info))
197-
continue;
161+
void ConfusableIdentifierCheck::onEndOfTranslationUnit() {
162+
llvm::StringMap<llvm::SmallVector<const IdentifierInfo*, 1>> SkeletonToNames;
163+
// Compute the skeleton for each identifier.
164+
for (auto &[Ident, Decls] : NameToDecls) {
165+
SkeletonToNames[skeleton(Ident->getName())].push_back(Ident);
166+
}
198167

199-
const IdentifierInfo *ONDII = E.Declaration->getIdentifier();
200-
StringRef ONDName = ONDII->getName();
201-
if (ONDName == NDName)
168+
// Visit each skeleton with more than one identifier.
169+
for (auto &[Skel, Idents] : SkeletonToNames) {
170+
if (Idents.size() < 2) {
202171
continue;
172+
}
203173

204-
diag(ND->getLocation(), "%0 is confusable with %1") << ND << E.Declaration;
205-
diag(E.Declaration->getLocation(), "other declaration found here",
206-
DiagnosticIDs::Note);
207-
}
174+
// Find the declaration contexts that transitively contain each identifier.
175+
DeclsWithinContextMap DeclsWithinContext;
176+
for (const IdentifierInfo *II : Idents) {
177+
for (const NamedDecl *ND : NameToDecls[II]) {
178+
addToEnclosingContexts(DeclsWithinContext, ND->getDeclContext(), ND);
179+
}
180+
}
208181

209-
Mapped.push_back({ND, Info});
210-
}
182+
// Check to see if any declaration is declared in a context that
183+
// transitively contains another declaration with a different identifier but
184+
// the same skeleton.
185+
for (const IdentifierInfo *II : Idents) {
186+
for (const NamedDecl *OuterND : NameToDecls[II]) {
187+
const DeclContext *OuterDC = OuterND->getDeclContext()
188+
->getNonTransparentContext()
189+
->getPrimaryContext();
190+
for (Entry Inner : DeclsWithinContext[OuterDC]) {
191+
// Don't complain if the identifiers are the same.
192+
if (OuterND->getIdentifier() == Inner.ND->getIdentifier())
193+
continue;
194+
195+
// Don't complain about a derived-class name shadowing a base class
196+
// private member.
197+
if (OuterND->getAccess() == AS_private && Inner.FromDerivedClass)
198+
continue;
199+
200+
// If the declarations are in the same context, only diagnose the
201+
// later one.
202+
if (OuterDC->Equals(
203+
Inner.ND->getDeclContext()->getNonTransparentContext()) &&
204+
Inner.ND->getASTContext()
205+
.getSourceManager()
206+
.isBeforeInTranslationUnit(Inner.ND->getLocation(),
207+
OuterND->getLocation()))
208+
continue;
209+
210+
diag(Inner.ND->getLocation(), "%0 is confusable with %1")
211+
<< Inner.ND << OuterND;
212+
diag(OuterND->getLocation(), "other declaration found here",
213+
DiagnosticIDs::Note);
214+
}
215+
}
216+
}
217+
}
211218

212-
void ConfusableIdentifierCheck::onEndOfTranslationUnit() {
213-
Mapper.clear();
214-
ContextInfos.clear();
219+
NameToDecls.clear();
215220
}
216221

217222
void ConfusableIdentifierCheck::registerMatchers(

clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.h

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CONFUSABLE_IDENTIFIER_CHECK_H
1212

1313
#include "../ClangTidyCheck.h"
14-
#include <unordered_map>
14+
#include "llvm/ADT/DenseMap.h"
1515

1616
namespace clang::tidy::misc {
1717

@@ -31,23 +31,10 @@ class ConfusableIdentifierCheck : public ClangTidyCheck {
3131
return TK_IgnoreUnlessSpelledInSource;
3232
}
3333

34-
struct ContextInfo {
35-
const DeclContext *PrimaryContext;
36-
const DeclContext *NonTransparentContext;
37-
llvm::SmallVector<const DeclContext *> PrimaryContexts;
38-
llvm::SmallVector<const CXXRecordDecl *> Bases;
39-
};
40-
4134
private:
42-
struct Entry {
43-
const NamedDecl *Declaration;
44-
const ContextInfo *Info;
45-
};
46-
47-
const ContextInfo *getContextInfo(const DeclContext *DC);
48-
49-
llvm::StringMap<llvm::SmallVector<Entry>> Mapper;
50-
std::unordered_map<const DeclContext *, ContextInfo> ContextInfos;
35+
llvm::DenseMap<const IdentifierInfo *,
36+
llvm::SmallVector<const NamedDecl *, 1>>
37+
NameToDecls;
5138
};
5239

5340
} // namespace clang::tidy::misc

clang-tools-extra/test/clang-tidy/checkers/misc/confusable-identifiers.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ template <typename t1, typename tl>
7474
// CHECK-MESSAGES: :[[#@LINE-2]]:20: note: other declaration found here
7575
void f9();
7676

77+
namespace f10 {
78+
int il;
79+
namespace inner {
80+
int i1;
81+
// CHECK-MESSAGES: :[[#@LINE-1]]:7: warning: 'i1' is confusable with 'il' [misc-confusable-identifiers]
82+
// CHECK-MESSAGES: :[[#@LINE-4]]:5: note: other declaration found here
83+
int j1;
84+
// CHECK-MESSAGES: :[[#@LINE-1]]:7: warning: 'j1' is confusable with 'jl' [misc-confusable-identifiers]
85+
// CHECK-MESSAGES: :[[#@LINE+2]]:5: note: other declaration found here
86+
}
87+
int jl;
88+
}
89+
7790
struct Base0 {
7891
virtual void mO0();
7992

@@ -103,3 +116,21 @@ struct Derived1 : Base1 {
103116

104117
long mI1(); // no warning: mII is private
105118
};
119+
120+
struct Base2 {
121+
long nO0;
122+
123+
private:
124+
long nII;
125+
};
126+
127+
struct Mid2 : Base0, Base1, Base2 {
128+
};
129+
130+
struct Derived2 : Mid2 {
131+
long nOO;
132+
// CHECK-MESSAGES: :[[#@LINE-1]]:8: warning: 'nOO' is confusable with 'nO0' [misc-confusable-identifiers]
133+
// CHECK-MESSAGES: :[[#@LINE-12]]:8: note: other declaration found here
134+
135+
long nI1(); // no warning: mII is private
136+
};

0 commit comments

Comments
 (0)