Skip to content

Commit 103428f

Browse files
committed
AST: More robust recursion check for opaque types with infinite underlying types
Tracking seen declarations and substitution maps only detects the situation where the opaque type's underlying type contains itself with the same substitution map. However, it is also possible to recurse with a different substitution map. In general termination is undecidable with a problem like this, so instead of trying to catch cycles, just impose a termination limit. This converts a stack overflow into an assertion, which is still not ideal; we should really diagnose something instead. But this is a first step.
1 parent 7a0ff5c commit 103428f

File tree

4 files changed

+113
-111
lines changed

4 files changed

+113
-111
lines changed

include/swift/AST/InFlightSubstitution.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,27 @@ namespace swift {
2828
class SubstitutionMap;
2929

3030
class InFlightSubstitution {
31-
SubstOptions Options;
3231
TypeSubstitutionFn BaselineSubstType;
3332
LookupConformanceFn BaselineLookupConformance;
33+
SubstOptions Options;
3434
RecursiveTypeProperties Props;
35+
unsigned RemainingCount : 16;
36+
unsigned RemainingDepth : 15;
37+
unsigned LimitReached : 1;
3538

3639
struct ActivePackExpansion {
3740
bool isSubstExpansion = false;
3841
unsigned expansionIndex = 0;
3942
};
40-
SmallVector<ActivePackExpansion, 4> ActivePackExpansions;
43+
llvm::SmallVector<ActivePackExpansion, 4> ActivePackExpansions;
4144

4245
Type projectLaneFromPackType(
4346
Type substType, unsigned level);
4447
ProtocolConformanceRef projectLaneFromPackConformance(
4548
PackConformance *substPackConf, unsigned level);
4649

50+
bool checkLimits();
51+
4752
public:
4853
InFlightSubstitution(TypeSubstitutionFn substType,
4954
LookupConformanceFn lookupConformance,
@@ -150,6 +155,10 @@ class InFlightSubstitution {
150155

151156
/// Is the given type invariant to substitution?
152157
bool isInvariant(Type type) const;
158+
159+
bool wasLimitReached() const {
160+
return LimitReached;
161+
}
153162
};
154163

155164
/// A helper classes that provides stable storage for the query

include/swift/AST/Types.h

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7077,24 +7077,16 @@ enum class OpaqueSubstitutionKind {
70777077
/// archetypes with underlying types visible at a given resilience expansion
70787078
/// to their underlying types.
70797079
class ReplaceOpaqueTypesWithUnderlyingTypes {
7080-
public:
7081-
using SeenDecl = std::pair<OpaqueTypeDecl *, SubstitutionMap>;
70827080
private:
70837081
ResilienceExpansion contextExpansion;
70847082
llvm::PointerIntPair<const DeclContext *, 1, bool> inContextAndIsWholeModule;
7085-
llvm::DenseSet<SeenDecl> *seenDecls;
70867083

70877084
public:
70887085
ReplaceOpaqueTypesWithUnderlyingTypes(const DeclContext *inContext,
70897086
ResilienceExpansion contextExpansion,
70907087
bool isWholeModuleContext)
70917088
: contextExpansion(contextExpansion),
7092-
inContextAndIsWholeModule(inContext, isWholeModuleContext),
7093-
seenDecls(nullptr) {}
7094-
7095-
ReplaceOpaqueTypesWithUnderlyingTypes(
7096-
const DeclContext *inContext, ResilienceExpansion contextExpansion,
7097-
bool isWholeModuleContext, llvm::DenseSet<SeenDecl> &seen);
7089+
inContextAndIsWholeModule(inContext, isWholeModuleContext) {}
70987090

70997091
/// TypeSubstitutionFn
71007092
Type operator()(SubstitutableType *maybeOpaqueType) const;

lib/AST/SubstitutionMap.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,9 +624,23 @@ SubstitutionMap swift::substOpaqueTypesWithUnderlyingTypes(
624624
ReplaceOpaqueTypesWithUnderlyingTypes replacer(
625625
context.getContext(), context.getResilienceExpansion(),
626626
context.isWholeModuleContext());
627-
return subs.subst(replacer, replacer,
628-
SubstFlags::SubstituteOpaqueArchetypes |
629-
SubstFlags::PreservePackExpansionLevel);
627+
InFlightSubstitution IFS(replacer, replacer,
628+
SubstFlags::SubstituteOpaqueArchetypes |
629+
SubstFlags::PreservePackExpansionLevel);
630+
631+
auto substSubs = subs.subst(IFS);
632+
633+
if (IFS.wasLimitReached()) {
634+
ABORT([&](auto &out) {
635+
out << "Possible non-terminating type substitution detected\n\n";
636+
out << "Original substitution map:\n";
637+
subs.dump(out, SubstitutionMap::DumpStyle::NoConformances);
638+
out << "Substituted substitution map:\n";
639+
substSubs.dump(out, SubstitutionMap::DumpStyle::NoConformances);
640+
});
641+
}
642+
643+
return substSubs;
630644
}
631645

632646
Type OuterSubstitutions::operator()(SubstitutableType *type) const {

lib/AST/TypeSubstitution.cpp

Lines changed: 84 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,16 @@ operator()(InFlightSubstitution &IFS, Type dependentType,
123123
InFlightSubstitution::InFlightSubstitution(TypeSubstitutionFn substType,
124124
LookupConformanceFn lookupConformance,
125125
SubstOptions options)
126-
: Options(options),
127-
BaselineSubstType(substType),
128-
BaselineLookupConformance(lookupConformance) {
126+
: BaselineSubstType(substType),
127+
BaselineLookupConformance(lookupConformance),
128+
Options(options) {
129+
130+
LimitReached = 0;
131+
132+
// FIXME: Make these configurable
133+
RemainingCount = 1000;
134+
RemainingDepth = 20;
135+
129136
// FIXME: Don't substitute type parameters if one of the special flags is set.
130137
Props |= RecursiveTypeProperties::HasTypeParameter;
131138

@@ -233,6 +240,14 @@ Type InFlightSubstitution::projectLaneFromPackType(Type substType,
233240
}
234241
}
235242

243+
bool InFlightSubstitution::checkLimits() {
244+
if (RemainingCount == 0 || RemainingDepth == 0) {
245+
LimitReached = true;
246+
return true;
247+
}
248+
return false;
249+
}
250+
236251
Type InFlightSubstitution::substType(SubstitutableType *origType,
237252
unsigned level) {
238253
auto substType = BaselineSubstType(origType);
@@ -244,6 +259,20 @@ Type InFlightSubstitution::substType(SubstitutableType *origType,
244259
else
245260
substType = substType->increasePackElementLevel(level);
246261

262+
// Opaque type replacement must iterate until fixed point.
263+
if (shouldSubstituteOpaqueArchetypes() &&
264+
substType->hasOpaqueArchetype() &&
265+
!substType->isEqual(origType)) {
266+
if (checkLimits()) {
267+
return ErrorType::get(substType);
268+
}
269+
270+
--RemainingCount;
271+
--RemainingDepth;
272+
substType = substType.subst(*this);
273+
++RemainingDepth;
274+
}
275+
247276
return substType;
248277
}
249278

@@ -271,6 +300,19 @@ InFlightSubstitution::lookupConformance(Type dependentType,
271300
substConfRef.getPack(), level);
272301
}
273302

303+
// Opaque type replacement must iterate until fixed point.
304+
if (shouldSubstituteOpaqueArchetypes() && substConfRef &&
305+
substConfRef.getType()->hasOpaqueArchetype() &&
306+
!substConfRef.getType()->isEqual(dependentType)) {
307+
if (checkLimits()) {
308+
return ProtocolConformanceRef::forInvalid();
309+
}
310+
--RemainingCount;
311+
--RemainingDepth;
312+
substConfRef = substConfRef.subst(*this);
313+
++RemainingDepth;
314+
}
315+
274316
return substConfRef;
275317
}
276318

@@ -906,17 +948,6 @@ ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
906948
return OpaqueSubstitutionKind::SubstituteNonResilientModule;
907949
}
908950

909-
static Type substOpaqueTypesWithUnderlyingTypesRec(
910-
Type ty, const DeclContext *inContext, ResilienceExpansion contextExpansion,
911-
bool isWholeModuleContext,
912-
llvm::DenseSet<ReplaceOpaqueTypesWithUnderlyingTypes::SeenDecl> &decls) {
913-
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
914-
isWholeModuleContext, decls);
915-
return ty.subst(replacer, replacer,
916-
SubstFlags::SubstituteOpaqueArchetypes |
917-
SubstFlags::PreservePackExpansionLevel);
918-
}
919-
920951
/// Checks that \p dc has access to \p ty for the purposes of an opaque
921952
/// substitution described by \p kind.
922953
///
@@ -973,13 +1004,6 @@ static bool canSubstituteTypeInto(Type ty, const DeclContext *dc,
9731004
llvm_unreachable("invalid substitution kind");
9741005
}
9751006

976-
ReplaceOpaqueTypesWithUnderlyingTypes::ReplaceOpaqueTypesWithUnderlyingTypes(
977-
const DeclContext *inContext, ResilienceExpansion contextExpansion,
978-
bool isWholeModuleContext, llvm::DenseSet<SeenDecl> &seen)
979-
: contextExpansion(contextExpansion),
980-
inContextAndIsWholeModule(inContext, isWholeModuleContext),
981-
seenDecls(&seen) {}
982-
9831007
Type ReplaceOpaqueTypesWithUnderlyingTypes::
9841008
operator()(SubstitutableType *maybeOpaqueType) const {
9851009
auto *archetype = dyn_cast<OpaqueTypeArchetypeType>(maybeOpaqueType);
@@ -1027,36 +1051,7 @@ operator()(SubstitutableType *maybeOpaqueType) const {
10271051
// for its type arguments. We perform this substitution after checking for
10281052
// visibility, since we do not want the result of the visibility check to
10291053
// depend on the substitutions previously applied.
1030-
auto substTy = partialSubstTy.subst(outerSubs);
1031-
1032-
// If the type changed, but still contains opaque types, recur.
1033-
if (!substTy->isEqual(maybeOpaqueType) && substTy->hasOpaqueArchetype()) {
1034-
SeenDecl seenKey(decl, outerSubs);
1035-
if (auto *alreadySeen = this->seenDecls) {
1036-
// Detect substitution loops. If we find one, just bounce the original
1037-
// type back to the caller. This substitution will fail at runtime
1038-
// instead.
1039-
if (!alreadySeen->insert(seenKey).second) {
1040-
return maybeOpaqueType;
1041-
}
1042-
1043-
auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
1044-
substTy, inContext, contextExpansion, isContextWholeModule,
1045-
*alreadySeen);
1046-
alreadySeen->erase(seenKey);
1047-
return res;
1048-
} else {
1049-
// We're the top of the stack for the recursion check. Allocate a set of
1050-
// opaque result type decls we've already seen for the rest of the check.
1051-
llvm::DenseSet<SeenDecl> seenDecls;
1052-
seenDecls.insert(seenKey);
1053-
return ::substOpaqueTypesWithUnderlyingTypesRec(
1054-
substTy, inContext, contextExpansion, isContextWholeModule,
1055-
seenDecls);
1056-
}
1057-
}
1058-
1059-
return substTy;
1054+
return partialSubstTy.subst(outerSubs);
10601055
}
10611056

10621057
Type swift::substOpaqueTypesWithUnderlyingTypes(Type ty,
@@ -1068,9 +1063,25 @@ Type swift::substOpaqueTypesWithUnderlyingTypes(Type ty,
10681063
ReplaceOpaqueTypesWithUnderlyingTypes replacer(
10691064
context.getContext(), context.getResilienceExpansion(),
10701065
context.isWholeModuleContext());
1071-
SubstOptions flags = (SubstFlags::SubstituteOpaqueArchetypes |
1072-
SubstFlags::PreservePackExpansionLevel);
1073-
return ty.subst(replacer, replacer, flags);
1066+
InFlightSubstitution IFS(replacer, replacer,
1067+
SubstFlags::SubstituteOpaqueArchetypes |
1068+
SubstFlags::PreservePackExpansionLevel);
1069+
1070+
auto substTy = ty.subst(IFS);
1071+
1072+
// FIXME: This should be a diagnostic at the source location of the innermost
1073+
// request or something.
1074+
if (IFS.wasLimitReached()) {
1075+
ABORT([&](auto &out) {
1076+
out << "Possible non-terminating type substitution detected\n\n";
1077+
out << "Original type:\n";
1078+
ty.dump(out);
1079+
out << "Substituted type:\n";
1080+
substTy.dump(out);
1081+
});
1082+
}
1083+
1084+
return substTy;
10741085
}
10751086

10761087
CanType
@@ -1080,24 +1091,29 @@ swift::substOpaqueTypesWithUnderlyingTypes(CanType ty,
10801091
->getCanonicalType();
10811092
}
10821093

1083-
static ProtocolConformanceRef substOpaqueTypesWithUnderlyingTypesRec(
1084-
ProtocolConformanceRef ref, const DeclContext *inContext,
1085-
ResilienceExpansion contextExpansion, bool isWholeModuleContext,
1086-
llvm::DenseSet<ReplaceOpaqueTypesWithUnderlyingTypes::SeenDecl> &decls) {
1087-
ReplaceOpaqueTypesWithUnderlyingTypes replacer(inContext, contextExpansion,
1088-
isWholeModuleContext, decls);
1089-
return ref.subst(replacer, replacer,
1090-
SubstFlags::SubstituteOpaqueArchetypes |
1091-
SubstFlags::PreservePackExpansionLevel);
1092-
}
1093-
10941094
ProtocolConformanceRef swift::substOpaqueTypesWithUnderlyingTypes(
10951095
ProtocolConformanceRef ref, TypeExpansionContext context) {
10961096
ReplaceOpaqueTypesWithUnderlyingTypes replacer(
10971097
context.getContext(), context.getResilienceExpansion(),
10981098
context.isWholeModuleContext());
1099-
return ref.subst(replacer, replacer,
1100-
SubstFlags::SubstituteOpaqueArchetypes);
1099+
InFlightSubstitution IFS(replacer, replacer,
1100+
SubstFlags::SubstituteOpaqueArchetypes);
1101+
1102+
auto substRef = ref.subst(IFS);
1103+
1104+
// FIXME: This should be a diagnostic at the source location of the innermost
1105+
// request or something.
1106+
if (IFS.wasLimitReached()) {
1107+
ABORT([&](auto &out) {
1108+
out << "Possible non-terminating conformance substitution detected\n\n";
1109+
out << "Original conformance:\n";
1110+
ref.dump(out);
1111+
out << "\nSubstituted conformance:\n";
1112+
substRef.dump(out);
1113+
});
1114+
}
1115+
1116+
return substRef;
11011117
}
11021118

11031119
ProtocolConformanceRef ReplaceOpaqueTypesWithUnderlyingTypes::
@@ -1151,36 +1167,7 @@ operator()(InFlightSubstitution &IFS, Type maybeOpaqueType,
11511167
auto partialSubstRef =
11521168
subs->lookupConformance(archetype->getInterfaceType()->getCanonicalType(),
11531169
protocol);
1154-
auto substRef = partialSubstRef.subst(outerSubs);
1155-
1156-
// If the type still contains opaque types, recur.
1157-
if (substRef.getType()->hasOpaqueArchetype()) {
1158-
SeenDecl seenKey(decl, outerSubs);
1159-
1160-
if (auto *alreadySeen = this->seenDecls) {
1161-
// Detect substitution loops. If we find one, just bounce the original
1162-
// type back to the caller. This substitution will fail at runtime
1163-
// instead.
1164-
if (!alreadySeen->insert(seenKey).second) {
1165-
return lookupConformance(maybeOpaqueType.subst(IFS), protocol);
1166-
}
1167-
1168-
auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
1169-
substRef, inContext, contextExpansion, isContextWholeModule,
1170-
*alreadySeen);
1171-
alreadySeen->erase(seenKey);
1172-
return res;
1173-
} else {
1174-
// We're the top of the stack for the recursion check. Allocate a set of
1175-
// opaque result type decls we've already seen for the rest of the check.
1176-
llvm::DenseSet<SeenDecl> seenDecls;
1177-
seenDecls.insert(seenKey);
1178-
return ::substOpaqueTypesWithUnderlyingTypesRec(
1179-
substRef, inContext, contextExpansion, isContextWholeModule,
1180-
seenDecls);
1181-
}
1182-
}
1183-
return substRef;
1170+
return partialSubstRef.subst(outerSubs);
11841171
}
11851172

11861173
Type ReplaceExistentialArchetypesWithConcreteTypes::getInterfaceType(

0 commit comments

Comments
 (0)