|
1 |
| -//===--- ConfusableIdentifierCheck.cpp - |
2 |
| -// clang-tidy--------------------------===// |
| 1 | +//===--- ConfusableIdentifierCheck.cpp - clang-tidy -----------------------===// |
3 | 2 | //
|
4 | 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
5 | 4 | // See https://llvm.org/LICENSE.txt for license information.
|
|
9 | 8 |
|
10 | 9 | #include "ConfusableIdentifierCheck.h"
|
11 | 10 |
|
| 11 | +#include "clang/ASTMatchers/ASTMatchers.h" |
12 | 12 | #include "clang/Lex/Preprocessor.h"
|
13 | 13 | #include "llvm/ADT/SmallString.h"
|
14 | 14 | #include "llvm/Support/ConvertUTF.h"
|
@@ -88,134 +88,177 @@ static llvm::SmallString<64U> skeleton(StringRef Name) {
|
88 | 88 | return Skeleton;
|
89 | 89 | }
|
90 | 90 |
|
91 |
| -static bool mayShadowImpl(const DeclContext *DC0, const DeclContext *DC1) { |
92 |
| - return DC0 && DC0 == DC1; |
93 |
| -} |
94 |
| - |
95 |
| -static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1) { |
96 |
| - return isa<TemplateTypeParmDecl>(ND0) || isa<TemplateTypeParmDecl>(ND1); |
97 |
| -} |
98 |
| - |
99 |
| -static bool isMemberOf(const ConfusableIdentifierCheck::ContextInfo *DC0, |
100 |
| - const ConfusableIdentifierCheck::ContextInfo *DC1) { |
101 |
| - return llvm::is_contained(DC1->Bases, DC0->PrimaryContext); |
102 |
| -} |
103 |
| - |
104 |
| -static 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 |
| - |
113 |
| -static 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(DC1, DC0)) |
123 |
| - return true; |
| 91 | +namespace { |
| 92 | +struct Entry { |
| 93 | + const NamedDecl *ND; |
| 94 | + const Decl *Parent; |
| 95 | + bool FromDerivedClass; |
| 96 | +}; |
| 97 | +} // namespace |
124 | 98 |
|
125 |
| - if (ND0->getAccess() != AS_private && isMemberOf(DC0, DC1)) |
| 99 | +// Map from a context to the declarations in that context with the current |
| 100 | +// skeleton. At most one entry per distinct identifier is tracked. The |
| 101 | +// context is usually a `DeclContext`, but can also be a template declaration |
| 102 | +// that has no corresponding context, such as an alias template or variable |
| 103 | +// template. |
| 104 | +using DeclsWithinContextMap = |
| 105 | + llvm::DenseMap<const Decl *, llvm::SmallVector<Entry, 1>>; |
| 106 | + |
| 107 | +static bool addToContext(DeclsWithinContextMap &DeclsWithinContext, |
| 108 | + const Decl *Context, Entry E) { |
| 109 | + auto &Decls = DeclsWithinContext[Context]; |
| 110 | + if (!Decls.empty() && |
| 111 | + Decls.back().ND->getIdentifier() == E.ND->getIdentifier()) { |
| 112 | + // Already have a declaration with this identifier in this context. Don't |
| 113 | + // track another one. This means that if an outer name is confusable with an |
| 114 | + // inner name, we'll only diagnose the outer name once, pointing at the |
| 115 | + // first inner declaration with that name. |
| 116 | + if (Decls.back().FromDerivedClass && !E.FromDerivedClass) { |
| 117 | + // Prefer the declaration that's not from the derived class, because that |
| 118 | + // conflicts with more declarations. |
| 119 | + Decls.back() = E; |
126 | 120 | return true;
|
127 |
| - } |
128 |
| - |
129 |
| - if (!mayShadowImpl(DC0->NonTransparentContext, DC1->NonTransparentContext) && |
130 |
| - !mayShadowImpl(ND0, ND1)) |
| 121 | + } |
131 | 122 | return false;
|
132 |
| - |
133 |
| - return enclosesContext(DC0, DC1); |
| 123 | + } |
| 124 | + Decls.push_back(E); |
| 125 | + return true; |
134 | 126 | }
|
135 | 127 |
|
136 |
| -const ConfusableIdentifierCheck::ContextInfo * |
137 |
| -ConfusableIdentifierCheck::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; |
| 128 | +static void addToEnclosingContexts(DeclsWithinContextMap &DeclsWithinContext, |
| 129 | + const Decl *Parent, const NamedDecl *ND) { |
| 130 | + const Decl *Outer = Parent; |
| 131 | + while (Outer) { |
| 132 | + if (const auto *NS = dyn_cast<NamespaceDecl>(Outer)) |
| 133 | + Outer = NS->getCanonicalDecl(); |
| 134 | + |
| 135 | + if (!addToContext(DeclsWithinContext, Outer, {ND, Parent, false})) |
| 136 | + return; |
| 137 | + |
| 138 | + if (const auto *RD = dyn_cast<CXXRecordDecl>(Outer)) { |
| 139 | + RD = RD->getDefinition(); |
| 140 | + if (RD) { |
| 141 | + RD->forallBases([&](const CXXRecordDecl *Base) { |
| 142 | + addToContext(DeclsWithinContext, Base, {ND, Parent, true}); |
| 143 | + return true; |
| 144 | + }); |
| 145 | + } |
| 146 | + } |
146 | 147 |
|
147 |
| - while (Info.NonTransparentContext->isTransparentContext()) { |
148 |
| - Info.NonTransparentContext = Info.NonTransparentContext->getParent(); |
149 |
| - if (!Info.NonTransparentContext) |
| 148 | + auto *OuterDC = Outer->getDeclContext(); |
| 149 | + if (!OuterDC) |
150 | 150 | break;
|
| 151 | + Outer = cast_or_null<Decl>(OuterDC->getNonTransparentContext()); |
151 | 152 | }
|
| 153 | +} |
| 154 | + |
| 155 | +void ConfusableIdentifierCheck::check( |
| 156 | + const ast_matchers::MatchFinder::MatchResult &Result) { |
| 157 | + const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("nameddecl"); |
| 158 | + if (!ND) |
| 159 | + return; |
152 | 160 |
|
153 |
| - if (Info.NonTransparentContext) |
154 |
| - Info.NonTransparentContext = |
155 |
| - Info.NonTransparentContext->getPrimaryContext(); |
| 161 | + addDeclToCheck(ND, |
| 162 | + cast<Decl>(ND->getDeclContext()->getNonTransparentContext())); |
156 | 163 |
|
157 |
| - while (DC) { |
158 |
| - if (!isa<LinkageSpecDecl>(DC) && !isa<ExportDecl>(DC)) |
159 |
| - Info.PrimaryContexts.push_back(DC->getPrimaryContext()); |
160 |
| - DC = DC->getParent(); |
| 164 | + // Associate template parameters with this declaration of this template. |
| 165 | + if (const auto *TD = dyn_cast<TemplateDecl>(ND)) { |
| 166 | + for (const NamedDecl *Param : *TD->getTemplateParameters()) |
| 167 | + addDeclToCheck(Param, TD->getTemplatedDecl()); |
161 | 168 | }
|
162 | 169 |
|
163 |
| - if (const auto *RD = dyn_cast<CXXRecordDecl>(PrimaryContext)) { |
164 |
| - RD = RD->getDefinition(); |
165 |
| - if (RD) { |
166 |
| - Info.Bases.push_back(RD); |
167 |
| - RD->forallBases([&](const CXXRecordDecl *Base) { |
168 |
| - Info.Bases.push_back(Base); |
169 |
| - return false; |
170 |
| - }); |
171 |
| - } |
| 170 | + // Associate function parameters with this declaration of this function. |
| 171 | + if (const auto *FD = dyn_cast<FunctionDecl>(ND)) { |
| 172 | + for (const NamedDecl *Param : FD->parameters()) |
| 173 | + addDeclToCheck(Param, ND); |
172 | 174 | }
|
173 |
| - |
174 |
| - return &Info; |
175 | 175 | }
|
176 | 176 |
|
177 |
| -void ConfusableIdentifierCheck::check( |
178 |
| - const ast_matchers::MatchFinder::MatchResult &Result) { |
179 |
| - const auto *ND = Result.Nodes.getNodeAs<NamedDecl>("nameddecl"); |
180 |
| - if (!ND) |
| 177 | +void ConfusableIdentifierCheck::addDeclToCheck(const NamedDecl *ND, |
| 178 | + const Decl *Parent) { |
| 179 | + if (!ND || !Parent) |
181 | 180 | return;
|
182 | 181 |
|
183 |
| - IdentifierInfo *NDII = ND->getIdentifier(); |
| 182 | + const IdentifierInfo *NDII = ND->getIdentifier(); |
184 | 183 | if (!NDII)
|
185 | 184 | return;
|
186 | 185 |
|
187 | 186 | StringRef NDName = NDII->getName();
|
188 | 187 | if (NDName.empty())
|
189 | 188 | return;
|
190 | 189 |
|
191 |
| - const ContextInfo *Info = getContextInfo(ND->getDeclContext()); |
| 190 | + NameToDecls[NDII].push_back({ND, Parent}); |
| 191 | +} |
| 192 | + |
| 193 | +void ConfusableIdentifierCheck::onEndOfTranslationUnit() { |
| 194 | + llvm::StringMap<llvm::SmallVector<const IdentifierInfo *, 1>> SkeletonToNames; |
| 195 | + // Compute the skeleton for each identifier. |
| 196 | + for (auto &[Ident, Decls] : NameToDecls) { |
| 197 | + SkeletonToNames[skeleton(Ident->getName())].push_back(Ident); |
| 198 | + } |
192 | 199 |
|
193 |
| - llvm::SmallVector<Entry> &Mapped = Mapper[skeleton(NDName)]; |
194 |
| - for (const Entry &E : Mapped) { |
195 |
| - if (!mayShadow(ND, Info, E.Declaration, E.Info)) |
| 200 | + // Visit each skeleton with more than one identifier. |
| 201 | + for (auto &[Skel, Idents] : SkeletonToNames) { |
| 202 | + if (Idents.size() < 2) { |
196 | 203 | continue;
|
| 204 | + } |
197 | 205 |
|
198 |
| - const IdentifierInfo *ONDII = E.Declaration->getIdentifier(); |
199 |
| - StringRef ONDName = ONDII->getName(); |
200 |
| - if (ONDName == NDName) |
201 |
| - continue; |
| 206 | + // Find the declaration contexts that transitively contain each identifier. |
| 207 | + DeclsWithinContextMap DeclsWithinContext; |
| 208 | + for (const IdentifierInfo *II : Idents) { |
| 209 | + for (auto [ND, Parent] : NameToDecls[II]) { |
| 210 | + addToEnclosingContexts(DeclsWithinContext, Parent, ND); |
| 211 | + } |
| 212 | + } |
202 | 213 |
|
203 |
| - diag(ND->getLocation(), "%0 is confusable with %1") << ND << E.Declaration; |
204 |
| - diag(E.Declaration->getLocation(), "other declaration found here", |
205 |
| - DiagnosticIDs::Note); |
| 214 | + // Check to see if any declaration is declared in a context that |
| 215 | + // transitively contains another declaration with a different identifier but |
| 216 | + // the same skeleton. |
| 217 | + for (const IdentifierInfo *II : Idents) { |
| 218 | + for (auto [OuterND, OuterParent] : NameToDecls[II]) { |
| 219 | + for (Entry Inner : DeclsWithinContext[OuterParent]) { |
| 220 | + // Don't complain if the identifiers are the same. |
| 221 | + if (OuterND->getIdentifier() == Inner.ND->getIdentifier()) |
| 222 | + continue; |
| 223 | + |
| 224 | + // Don't complain about a derived-class name shadowing a base class |
| 225 | + // private member. |
| 226 | + if (OuterND->getAccess() == AS_private && Inner.FromDerivedClass) |
| 227 | + continue; |
| 228 | + |
| 229 | + // If the declarations are in the same context, only diagnose the |
| 230 | + // later one. |
| 231 | + if (OuterParent == Inner.Parent && |
| 232 | + Inner.ND->getASTContext() |
| 233 | + .getSourceManager() |
| 234 | + .isBeforeInTranslationUnit(Inner.ND->getLocation(), |
| 235 | + OuterND->getLocation())) |
| 236 | + continue; |
| 237 | + |
| 238 | + diag(Inner.ND->getLocation(), "%0 is confusable with %1") |
| 239 | + << Inner.ND << OuterND; |
| 240 | + diag(OuterND->getLocation(), "other declaration found here", |
| 241 | + DiagnosticIDs::Note); |
| 242 | + } |
| 243 | + } |
| 244 | + } |
206 | 245 | }
|
207 | 246 |
|
208 |
| - Mapped.push_back({ND, Info}); |
209 |
| -} |
210 |
| - |
211 |
| -void ConfusableIdentifierCheck::onEndOfTranslationUnit() { |
212 |
| - Mapper.clear(); |
213 |
| - ContextInfos.clear(); |
| 247 | + NameToDecls.clear(); |
214 | 248 | }
|
215 | 249 |
|
216 | 250 | void ConfusableIdentifierCheck::registerMatchers(
|
217 | 251 | ast_matchers::MatchFinder *Finder) {
|
218 |
| - Finder->addMatcher(ast_matchers::namedDecl().bind("nameddecl"), this); |
| 252 | + // Parameter declarations sometimes use the translation unit or some outer |
| 253 | + // enclosing context as their `DeclContext`, instead of their parent, so |
| 254 | + // we handle them specially in `check`. |
| 255 | + auto AnyParamDecl = ast_matchers::anyOf( |
| 256 | + ast_matchers::parmVarDecl(), ast_matchers::templateTypeParmDecl(), |
| 257 | + ast_matchers::nonTypeTemplateParmDecl(), |
| 258 | + ast_matchers::templateTemplateParmDecl()); |
| 259 | + Finder->addMatcher(ast_matchers::namedDecl(ast_matchers::unless(AnyParamDecl)) |
| 260 | + .bind("nameddecl"), |
| 261 | + this); |
219 | 262 | }
|
220 | 263 |
|
221 | 264 | } // namespace clang::tidy::misc
|
0 commit comments