Skip to content

Commit 3165ea5

Browse files
authored
Merge pull request #40971 from CodaFi/hitting-the-stacks
Add a Recursion Breaker to Opaque Type Substitution
2 parents 81cde0c + ad94cbd commit 3165ea5

File tree

3 files changed

+99
-21
lines changed

3 files changed

+99
-21
lines changed

include/swift/AST/Types.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5608,15 +5608,21 @@ enum class OpaqueSubstitutionKind {
56085608
/// archetypes with underlying types visible at a given resilience expansion
56095609
/// to their underlying types.
56105610
class ReplaceOpaqueTypesWithUnderlyingTypes {
5611-
public:
5612-
const DeclContext *inContext;
56135611
ResilienceExpansion contextExpansion;
5614-
bool isContextWholeModule;
5612+
llvm::PointerIntPair<const DeclContext *, 1, bool> inContextAndIsWholeModule;
5613+
llvm::SmallPtrSetImpl<OpaqueTypeDecl *> *seenDecls;
5614+
5615+
public:
56155616
ReplaceOpaqueTypesWithUnderlyingTypes(const DeclContext *inContext,
56165617
ResilienceExpansion contextExpansion,
56175618
bool isWholeModuleContext)
5618-
: inContext(inContext), contextExpansion(contextExpansion),
5619-
isContextWholeModule(isWholeModuleContext) {}
5619+
: contextExpansion(contextExpansion),
5620+
inContextAndIsWholeModule(inContext, isWholeModuleContext),
5621+
seenDecls(nullptr) {}
5622+
5623+
ReplaceOpaqueTypesWithUnderlyingTypes(
5624+
const DeclContext *inContext, ResilienceExpansion contextExpansion,
5625+
bool isWholeModuleContext, llvm::SmallPtrSetImpl<OpaqueTypeDecl *> &seen);
56205626

56215627
/// TypeSubstitutionFn
56225628
Type operator()(SubstitutableType *maybeOpaqueType) const;
@@ -5632,6 +5638,13 @@ class ReplaceOpaqueTypesWithUnderlyingTypes {
56325638
static OpaqueSubstitutionKind
56335639
shouldPerformSubstitution(OpaqueTypeDecl *opaque, ModuleDecl *contextModule,
56345640
ResilienceExpansion contextExpansion);
5641+
5642+
private:
5643+
const DeclContext *getContext() const {
5644+
return inContextAndIsWholeModule.getPointer();
5645+
}
5646+
5647+
bool isWholeModule() const { return inContextAndIsWholeModule.getInt(); }
56355648
};
56365649

56375650
/// An archetype that represents the dynamic type of an opened existential.

lib/AST/Type.cpp

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3381,6 +3381,7 @@ getArchetypeAndRootOpaqueArchetype(Type maybeOpaqueType) {
33813381
OpaqueSubstitutionKind
33823382
ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
33833383
OpaqueTypeDecl *opaque) const {
3384+
const auto *inContext = getContext();
33843385
auto inModule = inContext ? inContext->getParentModule()
33853386
: opaque->getParentModule();
33863387
return shouldPerformSubstitution(opaque, inModule, contextExpansion);
@@ -3424,12 +3425,11 @@ ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
34243425
return OpaqueSubstitutionKind::SubstituteNonResilientModule;
34253426
}
34263427

3427-
static Type
3428-
substOpaqueTypesWithUnderlyingTypes(Type ty, const DeclContext *inContext,
3429-
ResilienceExpansion contextExpansion,
3430-
bool isWholeModuleContext) {
3428+
static Type substOpaqueTypesWithUnderlyingTypesRec(
3429+
Type ty, const DeclContext *inContext, ResilienceExpansion contextExpansion,
3430+
bool isWholeModuleContext, SmallPtrSetImpl<OpaqueTypeDecl *> &decls) {
34313431
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
3432-
isWholeModuleContext);
3432+
isWholeModuleContext, decls);
34333433
return ty.subst(replacer, replacer, SubstFlags::SubstituteOpaqueArchetypes);
34343434
}
34353435

@@ -3489,6 +3489,13 @@ static bool canSubstituteTypeInto(Type ty, const DeclContext *dc,
34893489
llvm_unreachable("invalid subsitution kind");
34903490
}
34913491

3492+
ReplaceOpaqueTypesWithUnderlyingTypes::ReplaceOpaqueTypesWithUnderlyingTypes(
3493+
const DeclContext *inContext, ResilienceExpansion contextExpansion,
3494+
bool isWholeModuleContext, llvm::SmallPtrSetImpl<OpaqueTypeDecl *> &seen)
3495+
: contextExpansion(contextExpansion),
3496+
inContextAndIsWholeModule(inContext, isWholeModuleContext),
3497+
seenDecls(&seen) {}
3498+
34923499
Type ReplaceOpaqueTypesWithUnderlyingTypes::
34933500
operator()(SubstitutableType *maybeOpaqueType) const {
34943501
auto archetypeAndRoot = getArchetypeAndRootOpaqueArchetype(maybeOpaqueType);
@@ -3516,8 +3523,8 @@ operator()(SubstitutableType *maybeOpaqueType) const {
35163523

35173524
// Check that we are allowed to substitute the underlying type into the
35183525
// context.
3519-
auto inContext = this->inContext;
3520-
auto isContextWholeModule = this->isContextWholeModule;
3526+
auto inContext = this->getContext();
3527+
auto isContextWholeModule = this->isWholeModule();
35213528
if (inContext &&
35223529
partialSubstTy.findIf(
35233530
[inContext, substitutionKind, isContextWholeModule](Type t) -> bool {
@@ -3536,18 +3543,39 @@ operator()(SubstitutableType *maybeOpaqueType) const {
35363543

35373544
// If the type changed, but still contains opaque types, recur.
35383545
if (!substTy->isEqual(maybeOpaqueType) && substTy->hasOpaqueArchetype()) {
3539-
return ::substOpaqueTypesWithUnderlyingTypes(
3540-
substTy, inContext, contextExpansion, isContextWholeModule);
3546+
if (auto *alreadySeen = this->seenDecls) {
3547+
// Detect substitution loops. If we find one, just bounce the original
3548+
// type back to the caller. This substitution will fail at runtime
3549+
// instead.
3550+
if (!alreadySeen->insert(opaqueRoot->getDecl()).second) {
3551+
return maybeOpaqueType;
3552+
}
3553+
3554+
auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
3555+
substTy, inContext, contextExpansion, isContextWholeModule,
3556+
*alreadySeen);
3557+
alreadySeen->erase(opaqueRoot->getDecl());
3558+
return res;
3559+
} else {
3560+
// We're the top of the stack for the recursion check. Allocate a set of
3561+
// opaque result type decls we've already seen for the rest of the check.
3562+
SmallPtrSet<OpaqueTypeDecl *, 8> seenDecls;
3563+
seenDecls.insert(opaqueRoot->getDecl());
3564+
return ::substOpaqueTypesWithUnderlyingTypesRec(
3565+
substTy, inContext, contextExpansion, isContextWholeModule,
3566+
seenDecls);
3567+
}
35413568
}
35423569

35433570
return substTy;
35443571
}
35453572

3546-
static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypes(
3573+
static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypesRec(
35473574
ProtocolConformanceRef ref, Type origType, const DeclContext *inContext,
3548-
ResilienceExpansion contextExpansion, bool isWholeModuleContext) {
3575+
ResilienceExpansion contextExpansion, bool isWholeModuleContext,
3576+
SmallPtrSetImpl<OpaqueTypeDecl *> &decls) {
35493577
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
3550-
isWholeModuleContext);
3578+
isWholeModuleContext, decls);
35513579
return ref.subst(origType, replacer, replacer,
35523580
SubstFlags::SubstituteOpaqueArchetypes);
35533581
}
@@ -3575,6 +3603,7 @@ operator()(CanType maybeOpaqueType, Type replacementType,
35753603
// SIL type lowering may have already substituted away the opaque type, in
35763604
// which case we'll end up "substituting" the same type.
35773605
if (maybeOpaqueType->isEqual(replacementType)) {
3606+
const auto *inContext = getContext();
35783607
assert(inContext && "Need context for already-substituted opaque types");
35793608
return inContext->getParentModule()
35803609
->lookupConformance(replacementType, protocol);
@@ -3604,8 +3633,8 @@ operator()(CanType maybeOpaqueType, Type replacementType,
36043633

36053634
// Check that we are allowed to substitute the underlying type into the
36063635
// context.
3607-
auto inContext = this->inContext;
3608-
auto isContextWholeModule = this->isContextWholeModule;
3636+
auto inContext = this->getContext();
3637+
auto isContextWholeModule = this->isWholeModule();
36093638
if (partialSubstTy.findIf(
36103639
[inContext, substitutionKind, isContextWholeModule](Type t) -> bool {
36113640
if (!canSubstituteTypeInto(t, inContext, substitutionKind,
@@ -3628,8 +3657,28 @@ operator()(CanType maybeOpaqueType, Type replacementType,
36283657

36293658
// If the type still contains opaque types, recur.
36303659
if (substTy->hasOpaqueArchetype()) {
3631-
return ::substOpaqueTypesWithUnderlyingTypes(
3632-
substRef, substTy, inContext, contextExpansion, isContextWholeModule);
3660+
if (auto *alreadySeen = this->seenDecls) {
3661+
// Detect substitution loops. If we find one, just bounce the original
3662+
// type back to the caller. This substitution will fail at runtime
3663+
// instead.
3664+
if (!alreadySeen->insert(opaqueRoot->getDecl()).second) {
3665+
return abstractRef;
3666+
}
3667+
3668+
auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
3669+
substRef, substTy, inContext, contextExpansion, isContextWholeModule,
3670+
*alreadySeen);
3671+
alreadySeen->erase(opaqueRoot->getDecl());
3672+
return res;
3673+
} else {
3674+
// We're the top of the stack for the recursion check. Allocate a set of
3675+
// opaque result type decls we've already seen for the rest of the check.
3676+
SmallPtrSet<OpaqueTypeDecl *, 8> seenDecls;
3677+
seenDecls.insert(opaqueRoot->getDecl());
3678+
return ::substOpaqueTypesWithUnderlyingTypesRec(
3679+
substRef, substTy, inContext, contextExpansion, isContextWholeModule,
3680+
seenDecls);
3681+
}
36333682
}
36343683
return substRef;
36353684
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %target-swift-frontend -emit-ir -target %target-cpu-apple-macosx10.15 %s
2+
// REQUIRES: OS=macosx
3+
4+
protocol P {}
5+
6+
func one() -> some P {
7+
return two()
8+
}
9+
10+
func two() -> some P {
11+
return three()
12+
}
13+
14+
func three() -> some P {
15+
return one()
16+
}

0 commit comments

Comments
 (0)