Skip to content

Commit 0c1dcd6

Browse files
HighCommander4llvmbot
authored andcommitted
[clangd] [HeuristicResolver] Protect against infinite recursion on DependentNameTypes (#83542)
When resolving names inside templates that implement recursive compile-time functions (e.g. waldo<N>::type is defined in terms of waldo<N-1>::type), HeuristicResolver could get into an infinite recursion, specifically one where resolveDependentNameType() can be called recursively with the same DependentNameType*. To guard against this, HeuristicResolver tracks, for each external call into a HeuristicResolver function, the set of DependentNameTypes that it has seen, and bails if it sees the same DependentNameType again. To implement this, a helper class HeuristicResolverImpl is introduced to store state that persists for the duration of an external call into HeuristicResolver (but does not persist between such calls). Fixes clangd/clangd#1951 (cherry picked from commit e6e53ca)
1 parent a649e0a commit 0c1dcd6

File tree

3 files changed

+165
-69
lines changed

3 files changed

+165
-69
lines changed

clang-tools-extra/clangd/HeuristicResolver.cpp

+138-32
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,80 @@
1616
namespace clang {
1717
namespace clangd {
1818

19+
namespace {
20+
21+
// Helper class for implementing HeuristicResolver.
22+
// Unlike HeuristicResolver which is a long-lived class,
23+
// a new instance of this class is created for every external
24+
// call into a HeuristicResolver operation. That allows this
25+
// class to store state that's local to such a top-level call,
26+
// particularly "recursion protection sets" that keep track of
27+
// nodes that have already been seen to avoid infinite recursion.
28+
class HeuristicResolverImpl {
29+
public:
30+
HeuristicResolverImpl(ASTContext &Ctx) : Ctx(Ctx) {}
31+
32+
// These functions match the public interface of HeuristicResolver
33+
// (but aren't const since they may modify the recursion protection sets).
34+
std::vector<const NamedDecl *>
35+
resolveMemberExpr(const CXXDependentScopeMemberExpr *ME);
36+
std::vector<const NamedDecl *>
37+
resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE);
38+
std::vector<const NamedDecl *> resolveTypeOfCallExpr(const CallExpr *CE);
39+
std::vector<const NamedDecl *> resolveCalleeOfCallExpr(const CallExpr *CE);
40+
std::vector<const NamedDecl *>
41+
resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD);
42+
std::vector<const NamedDecl *>
43+
resolveDependentNameType(const DependentNameType *DNT);
44+
std::vector<const NamedDecl *> resolveTemplateSpecializationType(
45+
const DependentTemplateSpecializationType *DTST);
46+
const Type *resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS);
47+
const Type *getPointeeType(const Type *T);
48+
49+
private:
50+
ASTContext &Ctx;
51+
52+
// Recursion protection sets
53+
llvm::SmallSet<const DependentNameType *, 4> SeenDependentNameTypes;
54+
55+
// Given a tag-decl type and a member name, heuristically resolve the
56+
// name to one or more declarations.
57+
// The current heuristic is simply to look up the name in the primary
58+
// template. This is a heuristic because the template could potentially
59+
// have specializations that declare different members.
60+
// Multiple declarations could be returned if the name is overloaded
61+
// (e.g. an overloaded method in the primary template).
62+
// This heuristic will give the desired answer in many cases, e.g.
63+
// for a call to vector<T>::size().
64+
std::vector<const NamedDecl *>
65+
resolveDependentMember(const Type *T, DeclarationName Name,
66+
llvm::function_ref<bool(const NamedDecl *ND)> Filter);
67+
68+
// Try to heuristically resolve the type of a possibly-dependent expression
69+
// `E`.
70+
const Type *resolveExprToType(const Expr *E);
71+
std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E);
72+
73+
// Helper function for HeuristicResolver::resolveDependentMember()
74+
// which takes a possibly-dependent type `T` and heuristically
75+
// resolves it to a CXXRecordDecl in which we can try name lookup.
76+
CXXRecordDecl *resolveTypeToRecordDecl(const Type *T);
77+
78+
// This is a reimplementation of CXXRecordDecl::lookupDependentName()
79+
// so that the implementation can call into other HeuristicResolver helpers.
80+
// FIXME: Once HeuristicResolver is upstreamed to the clang libraries
81+
// (https://github.com/clangd/clangd/discussions/1662),
82+
// CXXRecordDecl::lookupDepenedentName() can be removed, and its call sites
83+
// can be modified to benefit from the more comprehensive heuristics offered
84+
// by HeuristicResolver instead.
85+
std::vector<const NamedDecl *>
86+
lookupDependentName(CXXRecordDecl *RD, DeclarationName Name,
87+
llvm::function_ref<bool(const NamedDecl *ND)> Filter);
88+
bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier,
89+
CXXBasePath &Path,
90+
DeclarationName Name);
91+
};
92+
1993
// Convenience lambdas for use as the 'Filter' parameter of
2094
// HeuristicResolver::resolveDependentMember().
2195
const auto NoFilter = [](const NamedDecl *D) { return true; };
@@ -31,8 +105,6 @@ const auto TemplateFilter = [](const NamedDecl *D) {
31105
return isa<TemplateDecl>(D);
32106
};
33107

34-
namespace {
35-
36108
const Type *resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
37109
ASTContext &Ctx) {
38110
if (Decls.size() != 1) // Names an overload set -- just bail.
@@ -46,12 +118,10 @@ const Type *resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
46118
return nullptr;
47119
}
48120

49-
} // namespace
50-
51121
// Helper function for HeuristicResolver::resolveDependentMember()
52122
// which takes a possibly-dependent type `T` and heuristically
53123
// resolves it to a CXXRecordDecl in which we can try name lookup.
54-
CXXRecordDecl *HeuristicResolver::resolveTypeToRecordDecl(const Type *T) const {
124+
CXXRecordDecl *HeuristicResolverImpl::resolveTypeToRecordDecl(const Type *T) {
55125
assert(T);
56126

57127
// Unwrap type sugar such as type aliases.
@@ -84,7 +154,7 @@ CXXRecordDecl *HeuristicResolver::resolveTypeToRecordDecl(const Type *T) const {
84154
return TD->getTemplatedDecl();
85155
}
86156

87-
const Type *HeuristicResolver::getPointeeType(const Type *T) const {
157+
const Type *HeuristicResolverImpl::getPointeeType(const Type *T) {
88158
if (!T)
89159
return nullptr;
90160

@@ -117,8 +187,8 @@ const Type *HeuristicResolver::getPointeeType(const Type *T) const {
117187
return FirstArg.getAsType().getTypePtrOrNull();
118188
}
119189

120-
std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
121-
const CXXDependentScopeMemberExpr *ME) const {
190+
std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
191+
const CXXDependentScopeMemberExpr *ME) {
122192
// If the expression has a qualifier, try resolving the member inside the
123193
// qualifier's type.
124194
// Note that we cannot use a NonStaticFilter in either case, for a couple
@@ -164,14 +234,14 @@ std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
164234
return resolveDependentMember(BaseType, ME->getMember(), NoFilter);
165235
}
166236

167-
std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr(
168-
const DependentScopeDeclRefExpr *RE) const {
237+
std::vector<const NamedDecl *>
238+
HeuristicResolverImpl::resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) {
169239
return resolveDependentMember(RE->getQualifier()->getAsType(),
170240
RE->getDeclName(), StaticFilter);
171241
}
172242

173243
std::vector<const NamedDecl *>
174-
HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
244+
HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) {
175245
const auto *CalleeType = resolveExprToType(CE->getCallee());
176246
if (!CalleeType)
177247
return {};
@@ -187,37 +257,39 @@ HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
187257
}
188258

189259
std::vector<const NamedDecl *>
190-
HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const {
260+
HeuristicResolverImpl::resolveCalleeOfCallExpr(const CallExpr *CE) {
191261
if (const auto *ND = dyn_cast_or_null<NamedDecl>(CE->getCalleeDecl())) {
192262
return {ND};
193263
}
194264

195265
return resolveExprToDecls(CE->getCallee());
196266
}
197267

198-
std::vector<const NamedDecl *> HeuristicResolver::resolveUsingValueDecl(
199-
const UnresolvedUsingValueDecl *UUVD) const {
268+
std::vector<const NamedDecl *> HeuristicResolverImpl::resolveUsingValueDecl(
269+
const UnresolvedUsingValueDecl *UUVD) {
200270
return resolveDependentMember(UUVD->getQualifier()->getAsType(),
201271
UUVD->getNameInfo().getName(), ValueFilter);
202272
}
203273

204-
std::vector<const NamedDecl *> HeuristicResolver::resolveDependentNameType(
205-
const DependentNameType *DNT) const {
274+
std::vector<const NamedDecl *>
275+
HeuristicResolverImpl::resolveDependentNameType(const DependentNameType *DNT) {
276+
if (auto [_, inserted] = SeenDependentNameTypes.insert(DNT); !inserted)
277+
return {};
206278
return resolveDependentMember(
207279
resolveNestedNameSpecifierToType(DNT->getQualifier()),
208280
DNT->getIdentifier(), TypeFilter);
209281
}
210282

211283
std::vector<const NamedDecl *>
212-
HeuristicResolver::resolveTemplateSpecializationType(
213-
const DependentTemplateSpecializationType *DTST) const {
284+
HeuristicResolverImpl::resolveTemplateSpecializationType(
285+
const DependentTemplateSpecializationType *DTST) {
214286
return resolveDependentMember(
215287
resolveNestedNameSpecifierToType(DTST->getQualifier()),
216288
DTST->getIdentifier(), TemplateFilter);
217289
}
218290

219291
std::vector<const NamedDecl *>
220-
HeuristicResolver::resolveExprToDecls(const Expr *E) const {
292+
HeuristicResolverImpl::resolveExprToDecls(const Expr *E) {
221293
if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(E)) {
222294
return resolveMemberExpr(ME);
223295
}
@@ -236,16 +308,16 @@ HeuristicResolver::resolveExprToDecls(const Expr *E) const {
236308
return {};
237309
}
238310

239-
const Type *HeuristicResolver::resolveExprToType(const Expr *E) const {
311+
const Type *HeuristicResolverImpl::resolveExprToType(const Expr *E) {
240312
std::vector<const NamedDecl *> Decls = resolveExprToDecls(E);
241313
if (!Decls.empty())
242314
return resolveDeclsToType(Decls, Ctx);
243315

244316
return E->getType().getTypePtr();
245317
}
246318

247-
const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
248-
const NestedNameSpecifier *NNS) const {
319+
const Type *HeuristicResolverImpl::resolveNestedNameSpecifierToType(
320+
const NestedNameSpecifier *NNS) {
249321
if (!NNS)
250322
return nullptr;
251323

@@ -270,8 +342,6 @@ const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
270342
return nullptr;
271343
}
272344

273-
namespace {
274-
275345
bool isOrdinaryMember(const NamedDecl *ND) {
276346
return ND->isInIdentifierNamespace(Decl::IDNS_Ordinary | Decl::IDNS_Tag |
277347
Decl::IDNS_Member);
@@ -287,21 +357,19 @@ bool findOrdinaryMember(const CXXRecordDecl *RD, CXXBasePath &Path,
287357
return false;
288358
}
289359

290-
} // namespace
291-
292-
bool HeuristicResolver::findOrdinaryMemberInDependentClasses(
360+
bool HeuristicResolverImpl::findOrdinaryMemberInDependentClasses(
293361
const CXXBaseSpecifier *Specifier, CXXBasePath &Path,
294-
DeclarationName Name) const {
362+
DeclarationName Name) {
295363
CXXRecordDecl *RD =
296364
resolveTypeToRecordDecl(Specifier->getType().getTypePtr());
297365
if (!RD)
298366
return false;
299367
return findOrdinaryMember(RD, Path, Name);
300368
}
301369

302-
std::vector<const NamedDecl *> HeuristicResolver::lookupDependentName(
370+
std::vector<const NamedDecl *> HeuristicResolverImpl::lookupDependentName(
303371
CXXRecordDecl *RD, DeclarationName Name,
304-
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const {
372+
llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
305373
std::vector<const NamedDecl *> Results;
306374

307375
// Lookup in the class.
@@ -332,9 +400,9 @@ std::vector<const NamedDecl *> HeuristicResolver::lookupDependentName(
332400
return Results;
333401
}
334402

335-
std::vector<const NamedDecl *> HeuristicResolver::resolveDependentMember(
403+
std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
336404
const Type *T, DeclarationName Name,
337-
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const {
405+
llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
338406
if (!T)
339407
return {};
340408
if (auto *ET = T->getAs<EnumType>()) {
@@ -349,6 +417,44 @@ std::vector<const NamedDecl *> HeuristicResolver::resolveDependentMember(
349417
}
350418
return {};
351419
}
420+
} // namespace
421+
422+
std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
423+
const CXXDependentScopeMemberExpr *ME) const {
424+
return HeuristicResolverImpl(Ctx).resolveMemberExpr(ME);
425+
}
426+
std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr(
427+
const DependentScopeDeclRefExpr *RE) const {
428+
return HeuristicResolverImpl(Ctx).resolveDeclRefExpr(RE);
429+
}
430+
std::vector<const NamedDecl *>
431+
HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
432+
return HeuristicResolverImpl(Ctx).resolveTypeOfCallExpr(CE);
433+
}
434+
std::vector<const NamedDecl *>
435+
HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const {
436+
return HeuristicResolverImpl(Ctx).resolveCalleeOfCallExpr(CE);
437+
}
438+
std::vector<const NamedDecl *> HeuristicResolver::resolveUsingValueDecl(
439+
const UnresolvedUsingValueDecl *UUVD) const {
440+
return HeuristicResolverImpl(Ctx).resolveUsingValueDecl(UUVD);
441+
}
442+
std::vector<const NamedDecl *> HeuristicResolver::resolveDependentNameType(
443+
const DependentNameType *DNT) const {
444+
return HeuristicResolverImpl(Ctx).resolveDependentNameType(DNT);
445+
}
446+
std::vector<const NamedDecl *>
447+
HeuristicResolver::resolveTemplateSpecializationType(
448+
const DependentTemplateSpecializationType *DTST) const {
449+
return HeuristicResolverImpl(Ctx).resolveTemplateSpecializationType(DTST);
450+
}
451+
const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
452+
const NestedNameSpecifier *NNS) const {
453+
return HeuristicResolverImpl(Ctx).resolveNestedNameSpecifierToType(NNS);
454+
}
455+
const Type *HeuristicResolver::getPointeeType(const Type *T) const {
456+
return HeuristicResolverImpl(Ctx).getPointeeType(T);
457+
}
352458

353459
} // namespace clangd
354460
} // namespace clang

clang-tools-extra/clangd/HeuristicResolver.h

-37
Original file line numberDiff line numberDiff line change
@@ -77,43 +77,6 @@ class HeuristicResolver {
7777

7878
private:
7979
ASTContext &Ctx;
80-
81-
// Given a tag-decl type and a member name, heuristically resolve the
82-
// name to one or more declarations.
83-
// The current heuristic is simply to look up the name in the primary
84-
// template. This is a heuristic because the template could potentially
85-
// have specializations that declare different members.
86-
// Multiple declarations could be returned if the name is overloaded
87-
// (e.g. an overloaded method in the primary template).
88-
// This heuristic will give the desired answer in many cases, e.g.
89-
// for a call to vector<T>::size().
90-
std::vector<const NamedDecl *> resolveDependentMember(
91-
const Type *T, DeclarationName Name,
92-
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const;
93-
94-
// Try to heuristically resolve the type of a possibly-dependent expression
95-
// `E`.
96-
const Type *resolveExprToType(const Expr *E) const;
97-
std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E) const;
98-
99-
// Helper function for HeuristicResolver::resolveDependentMember()
100-
// which takes a possibly-dependent type `T` and heuristically
101-
// resolves it to a CXXRecordDecl in which we can try name lookup.
102-
CXXRecordDecl *resolveTypeToRecordDecl(const Type *T) const;
103-
104-
// This is a reimplementation of CXXRecordDecl::lookupDependentName()
105-
// so that the implementation can call into other HeuristicResolver helpers.
106-
// FIXME: Once HeuristicResolver is upstreamed to the clang libraries
107-
// (https://github.com/clangd/clangd/discussions/1662),
108-
// CXXRecordDecl::lookupDepenedentName() can be removed, and its call sites
109-
// can be modified to benefit from the more comprehensive heuristics offered
110-
// by HeuristicResolver instead.
111-
std::vector<const NamedDecl *> lookupDependentName(
112-
CXXRecordDecl *RD, DeclarationName Name,
113-
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const;
114-
bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier,
115-
CXXBasePath &Path,
116-
DeclarationName Name) const;
11780
};
11881

11982
} // namespace clangd

clang-tools-extra/clangd/unittests/FindTargetTests.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,33 @@ TEST_F(TargetDeclTest, DependentTypes) {
10091009
)cpp";
10101010
EXPECT_DECLS("DependentTemplateSpecializationTypeLoc",
10111011
"template <typename> struct B");
1012+
1013+
// Dependent name with recursive definition. We don't expect a
1014+
// result, but we shouldn't get into a stack overflow either.
1015+
Code = R"cpp(
1016+
template <int N>
1017+
struct waldo {
1018+
typedef typename waldo<N - 1>::type::[[next]] type;
1019+
};
1020+
)cpp";
1021+
EXPECT_DECLS("DependentNameTypeLoc");
1022+
1023+
// Similar to above but using mutually recursive templates.
1024+
Code = R"cpp(
1025+
template <int N>
1026+
struct odd;
1027+
1028+
template <int N>
1029+
struct even {
1030+
using type = typename odd<N - 1>::type::next;
1031+
};
1032+
1033+
template <int N>
1034+
struct odd {
1035+
using type = typename even<N - 1>::type::[[next]];
1036+
};
1037+
)cpp";
1038+
EXPECT_DECLS("DependentNameTypeLoc");
10121039
}
10131040

10141041
TEST_F(TargetDeclTest, TypedefCascade) {

0 commit comments

Comments
 (0)