Skip to content

Commit ad94cbd

Browse files
committed
Add a Recursion Breaker to Opaque Type Substitution
The current check in MiscDiagnostics can only handle local 2-cycles as in: func foo() -> some P { return bar() } func bar() -> some P { return foo() } Larger cycles, such as the 3-cycle present in the validation test in this patch cannot be detected without resolving all substitutions up front. Therefore, we take the tact that we can at least prevent the compiler from crashing. At runtime, instead, type resolution will blow out the stack. The check here is simple: Plumb through a set of opaque type decls that the resolution machinery has already seen, and if we encounter a recursive opaque type substitution bail with the original opaque type. rdar://87121502
1 parent 0e37ada commit ad94cbd

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
@@ -5598,15 +5598,21 @@ enum class OpaqueSubstitutionKind {
55985598
/// archetypes with underlying types visible at a given resilience expansion
55995599
/// to their underlying types.
56005600
class ReplaceOpaqueTypesWithUnderlyingTypes {
5601-
public:
5602-
const DeclContext *inContext;
56035601
ResilienceExpansion contextExpansion;
5604-
bool isContextWholeModule;
5602+
llvm::PointerIntPair<const DeclContext *, 1, bool> inContextAndIsWholeModule;
5603+
llvm::SmallPtrSetImpl<OpaqueTypeDecl *> *seenDecls;
5604+
5605+
public:
56055606
ReplaceOpaqueTypesWithUnderlyingTypes(const DeclContext *inContext,
56065607
ResilienceExpansion contextExpansion,
56075608
bool isWholeModuleContext)
5608-
: inContext(inContext), contextExpansion(contextExpansion),
5609-
isContextWholeModule(isWholeModuleContext) {}
5609+
: contextExpansion(contextExpansion),
5610+
inContextAndIsWholeModule(inContext, isWholeModuleContext),
5611+
seenDecls(nullptr) {}
5612+
5613+
ReplaceOpaqueTypesWithUnderlyingTypes(
5614+
const DeclContext *inContext, ResilienceExpansion contextExpansion,
5615+
bool isWholeModuleContext, llvm::SmallPtrSetImpl<OpaqueTypeDecl *> &seen);
56105616

56115617
/// TypeSubstitutionFn
56125618
Type operator()(SubstitutableType *maybeOpaqueType) const;
@@ -5622,6 +5628,13 @@ class ReplaceOpaqueTypesWithUnderlyingTypes {
56225628
static OpaqueSubstitutionKind
56235629
shouldPerformSubstitution(OpaqueTypeDecl *opaque, ModuleDecl *contextModule,
56245630
ResilienceExpansion contextExpansion);
5631+
5632+
private:
5633+
const DeclContext *getContext() const {
5634+
return inContextAndIsWholeModule.getPointer();
5635+
}
5636+
5637+
bool isWholeModule() const { return inContextAndIsWholeModule.getInt(); }
56255638
};
56265639

56275640
/// 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
@@ -3333,6 +3333,7 @@ getArchetypeAndRootOpaqueArchetype(Type maybeOpaqueType) {
33333333
OpaqueSubstitutionKind
33343334
ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
33353335
OpaqueTypeDecl *opaque) const {
3336+
const auto *inContext = getContext();
33363337
auto inModule = inContext ? inContext->getParentModule()
33373338
: opaque->getParentModule();
33383339
return shouldPerformSubstitution(opaque, inModule, contextExpansion);
@@ -3376,12 +3377,11 @@ ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
33763377
return OpaqueSubstitutionKind::SubstituteNonResilientModule;
33773378
}
33783379

3379-
static Type
3380-
substOpaqueTypesWithUnderlyingTypes(Type ty, const DeclContext *inContext,
3381-
ResilienceExpansion contextExpansion,
3382-
bool isWholeModuleContext) {
3380+
static Type substOpaqueTypesWithUnderlyingTypesRec(
3381+
Type ty, const DeclContext *inContext, ResilienceExpansion contextExpansion,
3382+
bool isWholeModuleContext, SmallPtrSetImpl<OpaqueTypeDecl *> &decls) {
33833383
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
3384-
isWholeModuleContext);
3384+
isWholeModuleContext, decls);
33853385
return ty.subst(replacer, replacer, SubstFlags::SubstituteOpaqueArchetypes);
33863386
}
33873387

@@ -3441,6 +3441,13 @@ static bool canSubstituteTypeInto(Type ty, const DeclContext *dc,
34413441
llvm_unreachable("invalid subsitution kind");
34423442
}
34433443

3444+
ReplaceOpaqueTypesWithUnderlyingTypes::ReplaceOpaqueTypesWithUnderlyingTypes(
3445+
const DeclContext *inContext, ResilienceExpansion contextExpansion,
3446+
bool isWholeModuleContext, llvm::SmallPtrSetImpl<OpaqueTypeDecl *> &seen)
3447+
: contextExpansion(contextExpansion),
3448+
inContextAndIsWholeModule(inContext, isWholeModuleContext),
3449+
seenDecls(&seen) {}
3450+
34443451
Type ReplaceOpaqueTypesWithUnderlyingTypes::
34453452
operator()(SubstitutableType *maybeOpaqueType) const {
34463453
auto archetypeAndRoot = getArchetypeAndRootOpaqueArchetype(maybeOpaqueType);
@@ -3468,8 +3475,8 @@ operator()(SubstitutableType *maybeOpaqueType) const {
34683475

34693476
// Check that we are allowed to substitute the underlying type into the
34703477
// context.
3471-
auto inContext = this->inContext;
3472-
auto isContextWholeModule = this->isContextWholeModule;
3478+
auto inContext = this->getContext();
3479+
auto isContextWholeModule = this->isWholeModule();
34733480
if (inContext &&
34743481
partialSubstTy.findIf(
34753482
[inContext, substitutionKind, isContextWholeModule](Type t) -> bool {
@@ -3488,18 +3495,39 @@ operator()(SubstitutableType *maybeOpaqueType) const {
34883495

34893496
// If the type changed, but still contains opaque types, recur.
34903497
if (!substTy->isEqual(maybeOpaqueType) && substTy->hasOpaqueArchetype()) {
3491-
return ::substOpaqueTypesWithUnderlyingTypes(
3492-
substTy, inContext, contextExpansion, isContextWholeModule);
3498+
if (auto *alreadySeen = this->seenDecls) {
3499+
// Detect substitution loops. If we find one, just bounce the original
3500+
// type back to the caller. This substitution will fail at runtime
3501+
// instead.
3502+
if (!alreadySeen->insert(opaqueRoot->getDecl()).second) {
3503+
return maybeOpaqueType;
3504+
}
3505+
3506+
auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
3507+
substTy, inContext, contextExpansion, isContextWholeModule,
3508+
*alreadySeen);
3509+
alreadySeen->erase(opaqueRoot->getDecl());
3510+
return res;
3511+
} else {
3512+
// We're the top of the stack for the recursion check. Allocate a set of
3513+
// opaque result type decls we've already seen for the rest of the check.
3514+
SmallPtrSet<OpaqueTypeDecl *, 8> seenDecls;
3515+
seenDecls.insert(opaqueRoot->getDecl());
3516+
return ::substOpaqueTypesWithUnderlyingTypesRec(
3517+
substTy, inContext, contextExpansion, isContextWholeModule,
3518+
seenDecls);
3519+
}
34933520
}
34943521

34953522
return substTy;
34963523
}
34973524

3498-
static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypes(
3525+
static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypesRec(
34993526
ProtocolConformanceRef ref, Type origType, const DeclContext *inContext,
3500-
ResilienceExpansion contextExpansion, bool isWholeModuleContext) {
3527+
ResilienceExpansion contextExpansion, bool isWholeModuleContext,
3528+
SmallPtrSetImpl<OpaqueTypeDecl *> &decls) {
35013529
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
3502-
isWholeModuleContext);
3530+
isWholeModuleContext, decls);
35033531
return ref.subst(origType, replacer, replacer,
35043532
SubstFlags::SubstituteOpaqueArchetypes);
35053533
}
@@ -3527,6 +3555,7 @@ operator()(CanType maybeOpaqueType, Type replacementType,
35273555
// SIL type lowering may have already substituted away the opaque type, in
35283556
// which case we'll end up "substituting" the same type.
35293557
if (maybeOpaqueType->isEqual(replacementType)) {
3558+
const auto *inContext = getContext();
35303559
assert(inContext && "Need context for already-substituted opaque types");
35313560
return inContext->getParentModule()
35323561
->lookupConformance(replacementType, protocol);
@@ -3556,8 +3585,8 @@ operator()(CanType maybeOpaqueType, Type replacementType,
35563585

35573586
// Check that we are allowed to substitute the underlying type into the
35583587
// context.
3559-
auto inContext = this->inContext;
3560-
auto isContextWholeModule = this->isContextWholeModule;
3588+
auto inContext = this->getContext();
3589+
auto isContextWholeModule = this->isWholeModule();
35613590
if (partialSubstTy.findIf(
35623591
[inContext, substitutionKind, isContextWholeModule](Type t) -> bool {
35633592
if (!canSubstituteTypeInto(t, inContext, substitutionKind,
@@ -3580,8 +3609,28 @@ operator()(CanType maybeOpaqueType, Type replacementType,
35803609

35813610
// If the type still contains opaque types, recur.
35823611
if (substTy->hasOpaqueArchetype()) {
3583-
return ::substOpaqueTypesWithUnderlyingTypes(
3584-
substRef, substTy, inContext, contextExpansion, isContextWholeModule);
3612+
if (auto *alreadySeen = this->seenDecls) {
3613+
// Detect substitution loops. If we find one, just bounce the original
3614+
// type back to the caller. This substitution will fail at runtime
3615+
// instead.
3616+
if (!alreadySeen->insert(opaqueRoot->getDecl()).second) {
3617+
return abstractRef;
3618+
}
3619+
3620+
auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
3621+
substRef, substTy, inContext, contextExpansion, isContextWholeModule,
3622+
*alreadySeen);
3623+
alreadySeen->erase(opaqueRoot->getDecl());
3624+
return res;
3625+
} else {
3626+
// We're the top of the stack for the recursion check. Allocate a set of
3627+
// opaque result type decls we've already seen for the rest of the check.
3628+
SmallPtrSet<OpaqueTypeDecl *, 8> seenDecls;
3629+
seenDecls.insert(opaqueRoot->getDecl());
3630+
return ::substOpaqueTypesWithUnderlyingTypesRec(
3631+
substRef, substTy, inContext, contextExpansion, isContextWholeModule,
3632+
seenDecls);
3633+
}
35853634
}
35863635
return substRef;
35873636
}
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)