[clang] Fix non-deterministic infinite recursion... (#118288)

...in `ASTContext::getAutoTypeInternal`

Given

```cpp
template < typename >
concept C1 = true;

template < typename , auto >
concept C2 = true;

template < C1 auto V, C2< V > auto>
struct S;
```

Both `C1 auto V` and `C2<V> auto` end on the set `AutoType`, the former
being a template parameter for the latter.

Since the hashing is not deterministic (i.e., pointers are hashed),
every now and then, both will end on the same bucket. Given that
`FoldingSet` recomputes the `FoldingSetID` for each node in the target
bucket on lookup, this triggers an infinite recursion:

1. Look for `X` in `AutoTypes`
2. Let's assume it would be in bucket N, so it iterates over nodes in
that bucket. Let's assume the first is `C2<V> auto`.
3. Computes the `FoldingSetID` for this one, which requires the profile
of its template parameters, so they are visited.
4. In some frames below, we end on the same `FoldingSet`, and, by
chance, `C1 auto V` would be in bucket N too.
5. But the first node in the bucket is `C2<V> auto` for which we need to
profile `C1 auto V`
6. ... stack overflow!

No step individually does anything wrong, but in general, `FoldingSet`
seems not to be re-entrant, and this fact is hidden behind many nested
calls.

With this change, we store the `AutoType`s inside a `DenseMap` instead.
The `FoldingSetID` is computed once only and then kept as the map's key,
avoiding the need to do recursive lookups.

We also now make sure the key for the inserted `AutoType` is the same as
the key used for lookup. Before, this was not the case, and it caused
also non-deterministic parsing errors.

Fixes https://github.com/llvm/llvm-project/issues/110231
This commit is contained in:
Alejandro Álvarez Ayllón 2024-12-10 09:17:41 +01:00 committed by GitHub
parent eadc0c901b
commit c2d7e96cde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 13 deletions

View File

@ -245,7 +245,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
mutable llvm::FoldingSet<ObjCObjectPointerType> ObjCObjectPointerTypes; mutable llvm::FoldingSet<ObjCObjectPointerType> ObjCObjectPointerTypes;
mutable llvm::FoldingSet<DependentUnaryTransformType> mutable llvm::FoldingSet<DependentUnaryTransformType>
DependentUnaryTransformTypes; DependentUnaryTransformTypes;
mutable llvm::ContextualFoldingSet<AutoType, ASTContext&> AutoTypes; // An AutoType can have a dependency on another AutoType via its template
// arguments. Since both dependent and dependency are on the same set,
// we can end up in an infinite recursion when looking for a node if we used
// a `FoldingSet`, since both could end up in the same bucket.
mutable llvm::DenseMap<llvm::FoldingSetNodeID, AutoType *> AutoTypes;
mutable llvm::FoldingSet<DeducedTemplateSpecializationType> mutable llvm::FoldingSet<DeducedTemplateSpecializationType>
DeducedTemplateSpecializationTypes; DeducedTemplateSpecializationTypes;
mutable llvm::FoldingSet<AtomicType> AtomicTypes; mutable llvm::FoldingSet<AtomicType> AtomicTypes;

View File

@ -6553,7 +6553,7 @@ public:
/// Represents a C++11 auto or C++14 decltype(auto) type, possibly constrained /// Represents a C++11 auto or C++14 decltype(auto) type, possibly constrained
/// by a type-constraint. /// by a type-constraint.
class AutoType : public DeducedType, public llvm::FoldingSetNode { class AutoType : public DeducedType {
friend class ASTContext; // ASTContext creates these friend class ASTContext; // ASTContext creates these
ConceptDecl *TypeConstraintConcept; ConceptDecl *TypeConstraintConcept;

View File

@ -112,6 +112,27 @@ enum FloatingRank {
Ibm128Rank Ibm128Rank
}; };
template <> struct llvm::DenseMapInfo<llvm::FoldingSetNodeID> {
static FoldingSetNodeID getEmptyKey() { return FoldingSetNodeID{}; }
static FoldingSetNodeID getTombstoneKey() {
FoldingSetNodeID id;
for (size_t i = 0; i < sizeof(id) / sizeof(unsigned); ++i) {
id.AddInteger(std::numeric_limits<unsigned>::max());
}
return id;
}
static unsigned getHashValue(const FoldingSetNodeID &Val) {
return Val.ComputeHash();
}
static bool isEqual(const FoldingSetNodeID &LHS,
const FoldingSetNodeID &RHS) {
return LHS == RHS;
}
};
/// \returns The locations that are relevant when searching for Doc comments /// \returns The locations that are relevant when searching for Doc comments
/// related to \p D. /// related to \p D.
static SmallVector<SourceLocation, 2> static SmallVector<SourceLocation, 2>
@ -899,7 +920,7 @@ ASTContext::ASTContext(LangOptions &LOpts, SourceManager &SM,
FunctionProtoTypes(this_(), FunctionProtoTypesLog2InitSize), FunctionProtoTypes(this_(), FunctionProtoTypesLog2InitSize),
DependentTypeOfExprTypes(this_()), DependentDecltypeTypes(this_()), DependentTypeOfExprTypes(this_()), DependentDecltypeTypes(this_()),
TemplateSpecializationTypes(this_()), TemplateSpecializationTypes(this_()),
DependentTemplateSpecializationTypes(this_()), AutoTypes(this_()), DependentTemplateSpecializationTypes(this_()),
DependentBitIntTypes(this_()), SubstTemplateTemplateParmPacks(this_()), DependentBitIntTypes(this_()), SubstTemplateTemplateParmPacks(this_()),
DeducedTemplates(this_()), ArrayParameterTypes(this_()), DeducedTemplates(this_()), ArrayParameterTypes(this_()),
CanonTemplateTemplateParms(this_()), SourceMgr(SM), LangOpts(LOpts), CanonTemplateTemplateParms(this_()), SourceMgr(SM), LangOpts(LOpts),
@ -6294,12 +6315,14 @@ QualType ASTContext::getAutoTypeInternal(
return getAutoDeductType(); return getAutoDeductType();
// Look in the folding set for an existing type. // Look in the folding set for an existing type.
void *InsertPos = nullptr;
llvm::FoldingSetNodeID ID; llvm::FoldingSetNodeID ID;
AutoType::Profile(ID, *this, DeducedType, Keyword, IsDependent, bool IsDeducedDependent =
TypeConstraintConcept, TypeConstraintArgs); !DeducedType.isNull() && DeducedType->isDependentType();
if (AutoType *AT = AutoTypes.FindNodeOrInsertPos(ID, InsertPos)) AutoType::Profile(ID, *this, DeducedType, Keyword,
return QualType(AT, 0); IsDependent || IsDeducedDependent, TypeConstraintConcept,
TypeConstraintArgs);
if (auto const AT_iter = AutoTypes.find(ID); AT_iter != AutoTypes.end())
return QualType(AT_iter->getSecond(), 0);
QualType Canon; QualType Canon;
if (!IsCanon) { if (!IsCanon) {
@ -6314,10 +6337,6 @@ QualType ASTContext::getAutoTypeInternal(
Canon = Canon =
getAutoTypeInternal(QualType(), Keyword, IsDependent, IsPack, getAutoTypeInternal(QualType(), Keyword, IsDependent, IsPack,
CanonicalConcept, CanonicalConceptArgs, true); CanonicalConcept, CanonicalConceptArgs, true);
// Find the insert position again.
[[maybe_unused]] auto *Nothing =
AutoTypes.FindNodeOrInsertPos(ID, InsertPos);
assert(!Nothing && "canonical type broken");
} }
} }
} }
@ -6331,8 +6350,13 @@ QualType ASTContext::getAutoTypeInternal(
: TypeDependence::None) | : TypeDependence::None) |
(IsPack ? TypeDependence::UnexpandedPack : TypeDependence::None), (IsPack ? TypeDependence::UnexpandedPack : TypeDependence::None),
Canon, TypeConstraintConcept, TypeConstraintArgs); Canon, TypeConstraintConcept, TypeConstraintArgs);
#ifndef NDEBUG
llvm::FoldingSetNodeID InsertedID;
AT->Profile(InsertedID, *this);
assert(InsertedID == ID && "ID does not match");
#endif
Types.push_back(AT); Types.push_back(AT);
AutoTypes.InsertNode(AT, InsertPos); AutoTypes.try_emplace(ID, AT);
return QualType(AT, 0); return QualType(AT, 0);
} }

View File

@ -0,0 +1,12 @@
// RUN: seq 100 | xargs -Ifoo %clang_cc1 -std=c++20 -fsyntax-only -verify %s
// expected-no-diagnostics
// This is a regression test for a non-deterministic stack-overflow.
template < typename >
concept C1 = true;
template < typename , auto >
concept C2 = true;
template < C1 auto V, C2< V > auto>
struct S;