Skip to content

Add a Recursion Breaker to Opaque Type Substitution #40971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -5598,15 +5598,21 @@ enum class OpaqueSubstitutionKind {
/// archetypes with underlying types visible at a given resilience expansion
/// to their underlying types.
class ReplaceOpaqueTypesWithUnderlyingTypes {
public:
const DeclContext *inContext;
ResilienceExpansion contextExpansion;
bool isContextWholeModule;
llvm::PointerIntPair<const DeclContext *, 1, bool> inContextAndIsWholeModule;
llvm::SmallPtrSetImpl<OpaqueTypeDecl *> *seenDecls;

public:
ReplaceOpaqueTypesWithUnderlyingTypes(const DeclContext *inContext,
ResilienceExpansion contextExpansion,
bool isWholeModuleContext)
: inContext(inContext), contextExpansion(contextExpansion),
isContextWholeModule(isWholeModuleContext) {}
: contextExpansion(contextExpansion),
inContextAndIsWholeModule(inContext, isWholeModuleContext),
seenDecls(nullptr) {}

ReplaceOpaqueTypesWithUnderlyingTypes(
const DeclContext *inContext, ResilienceExpansion contextExpansion,
bool isWholeModuleContext, llvm::SmallPtrSetImpl<OpaqueTypeDecl *> &seen);

/// TypeSubstitutionFn
Type operator()(SubstitutableType *maybeOpaqueType) const;
Expand All @@ -5622,6 +5628,13 @@ class ReplaceOpaqueTypesWithUnderlyingTypes {
static OpaqueSubstitutionKind
shouldPerformSubstitution(OpaqueTypeDecl *opaque, ModuleDecl *contextModule,
ResilienceExpansion contextExpansion);

private:
const DeclContext *getContext() const {
return inContextAndIsWholeModule.getPointer();
}

bool isWholeModule() const { return inContextAndIsWholeModule.getInt(); }
};

/// An archetype that represents the dynamic type of an opened existential.
Expand Down
81 changes: 65 additions & 16 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3333,6 +3333,7 @@ getArchetypeAndRootOpaqueArchetype(Type maybeOpaqueType) {
OpaqueSubstitutionKind
ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
OpaqueTypeDecl *opaque) const {
const auto *inContext = getContext();
auto inModule = inContext ? inContext->getParentModule()
: opaque->getParentModule();
return shouldPerformSubstitution(opaque, inModule, contextExpansion);
Expand Down Expand Up @@ -3376,12 +3377,11 @@ ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
return OpaqueSubstitutionKind::SubstituteNonResilientModule;
}

static Type
substOpaqueTypesWithUnderlyingTypes(Type ty, const DeclContext *inContext,
ResilienceExpansion contextExpansion,
bool isWholeModuleContext) {
static Type substOpaqueTypesWithUnderlyingTypesRec(
Type ty, const DeclContext *inContext, ResilienceExpansion contextExpansion,
bool isWholeModuleContext, SmallPtrSetImpl<OpaqueTypeDecl *> &decls) {
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
isWholeModuleContext);
isWholeModuleContext, decls);
return ty.subst(replacer, replacer, SubstFlags::SubstituteOpaqueArchetypes);
}

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

ReplaceOpaqueTypesWithUnderlyingTypes::ReplaceOpaqueTypesWithUnderlyingTypes(
const DeclContext *inContext, ResilienceExpansion contextExpansion,
bool isWholeModuleContext, llvm::SmallPtrSetImpl<OpaqueTypeDecl *> &seen)
: contextExpansion(contextExpansion),
inContextAndIsWholeModule(inContext, isWholeModuleContext),
seenDecls(&seen) {}

Type ReplaceOpaqueTypesWithUnderlyingTypes::
operator()(SubstitutableType *maybeOpaqueType) const {
auto archetypeAndRoot = getArchetypeAndRootOpaqueArchetype(maybeOpaqueType);
Expand Down Expand Up @@ -3468,8 +3475,8 @@ operator()(SubstitutableType *maybeOpaqueType) const {

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

// If the type changed, but still contains opaque types, recur.
if (!substTy->isEqual(maybeOpaqueType) && substTy->hasOpaqueArchetype()) {
return ::substOpaqueTypesWithUnderlyingTypes(
substTy, inContext, contextExpansion, isContextWholeModule);
if (auto *alreadySeen = this->seenDecls) {
// Detect substitution loops. If we find one, just bounce the original
// type back to the caller. This substitution will fail at runtime
// instead.
if (!alreadySeen->insert(opaqueRoot->getDecl()).second) {
return maybeOpaqueType;
}

auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
substTy, inContext, contextExpansion, isContextWholeModule,
*alreadySeen);
alreadySeen->erase(opaqueRoot->getDecl());
return res;
} else {
// We're the top of the stack for the recursion check. Allocate a set of
// opaque result type decls we've already seen for the rest of the check.
SmallPtrSet<OpaqueTypeDecl *, 8> seenDecls;
seenDecls.insert(opaqueRoot->getDecl());
return ::substOpaqueTypesWithUnderlyingTypesRec(
substTy, inContext, contextExpansion, isContextWholeModule,
seenDecls);
}
}

return substTy;
}

static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypes(
static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypesRec(
ProtocolConformanceRef ref, Type origType, const DeclContext *inContext,
ResilienceExpansion contextExpansion, bool isWholeModuleContext) {
ResilienceExpansion contextExpansion, bool isWholeModuleContext,
SmallPtrSetImpl<OpaqueTypeDecl *> &decls) {
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
isWholeModuleContext);
isWholeModuleContext, decls);
return ref.subst(origType, replacer, replacer,
SubstFlags::SubstituteOpaqueArchetypes);
}
Expand Down Expand Up @@ -3527,6 +3555,7 @@ operator()(CanType maybeOpaqueType, Type replacementType,
// SIL type lowering may have already substituted away the opaque type, in
// which case we'll end up "substituting" the same type.
if (maybeOpaqueType->isEqual(replacementType)) {
const auto *inContext = getContext();
assert(inContext && "Need context for already-substituted opaque types");
return inContext->getParentModule()
->lookupConformance(replacementType, protocol);
Expand Down Expand Up @@ -3556,8 +3585,8 @@ operator()(CanType maybeOpaqueType, Type replacementType,

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

// If the type still contains opaque types, recur.
if (substTy->hasOpaqueArchetype()) {
return ::substOpaqueTypesWithUnderlyingTypes(
substRef, substTy, inContext, contextExpansion, isContextWholeModule);
if (auto *alreadySeen = this->seenDecls) {
// Detect substitution loops. If we find one, just bounce the original
// type back to the caller. This substitution will fail at runtime
// instead.
if (!alreadySeen->insert(opaqueRoot->getDecl()).second) {
return abstractRef;
}

auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
substRef, substTy, inContext, contextExpansion, isContextWholeModule,
*alreadySeen);
alreadySeen->erase(opaqueRoot->getDecl());
return res;
} else {
// We're the top of the stack for the recursion check. Allocate a set of
// opaque result type decls we've already seen for the rest of the check.
SmallPtrSet<OpaqueTypeDecl *, 8> seenDecls;
seenDecls.insert(opaqueRoot->getDecl());
return ::substOpaqueTypesWithUnderlyingTypesRec(
substRef, substTy, inContext, contextExpansion, isContextWholeModule,
seenDecls);
}
}
return substRef;
}
Expand Down
16 changes: 16 additions & 0 deletions validation-test/compiler_crashers_2_fixed/rdar87121502.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// RUN: %target-swift-frontend -emit-ir -target %target-cpu-apple-macosx10.15 %s
// REQUIRES: OS=macosx

protocol P {}

func one() -> some P {
return two()
}

func two() -> some P {
return three()
}

func three() -> some P {
return one()
}