Skip to content

Commit e822910

Browse files
committed
[Serialization] Drop enums if a case payload can't be deserialized.
Layout for an enum depends very intimately on its cases---both their existence and what their payload types are. That means there's no way to "partly" recover from failure to deserialize an individual case's payload type, the way we can partly recover from failing to deserialize an initializer in a class. Add deserialization recovery to enums by validating all of their payload types up front, and dropping the enum if we can't import all of the cases. This is the first time where we're trying to do deserialization recovery for a /type/, and that could have many more ripple effects than for a var/func/subscript/init. A better answer here might be to still import the enum but mark it as unavailable, but in that case we'd have to make sure to propagate that unavailability to anything that /used/ the enum as well. (In Swift, availability is checked based on use of the name, so if someone manages to refer to an enum using inferred types we'd be in trouble.) There is one case here that's not covered: if an enum case has a payload that references a type declaration nested within the enum, but then that nested type /itself/ can't be loaded for some reason, we have no way to check that up front, because we can't even try to load the nested type without loading its parent DeclContext (the enum). I can't think of an easy solution for this right now. (In the future, we'll be able to support dropping a single case for resilient enums. But we're not there right now.) rdar://problem/31920901
1 parent 33e78be commit e822910

File tree

4 files changed

+156
-16
lines changed

4 files changed

+156
-16
lines changed

include/swift/Serialization/ModuleFormat.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const uint16_t VERSION_MAJOR = 0;
5454
/// in source control, you should also update the comment to briefly
5555
/// describe what change you made. The content of this comment isn't important;
5656
/// it just ensures a conflict if two people change the module format.
57-
const uint16_t VERSION_MINOR = 345; // Last change: list of dependency types
57+
const uint16_t VERSION_MINOR = 346; // Last change: dependency types for enums
5858

5959
using DeclID = PointerEmbeddedInt<unsigned, 31>;
6060
using DeclIDField = BCFixed<31>;
@@ -802,7 +802,8 @@ namespace decls_block {
802802
TypeIDField, // raw type
803803
AccessibilityKindField, // accessibility
804804
BCVBR<4>, // number of conformances
805-
BCArray<TypeIDField> // inherited types
805+
BCVBR<4>, // number of inherited types
806+
BCArray<TypeIDField> // inherited types, followed by dependency types
806807
// Trailed by the generic parameters (if any), the members record, and
807808
// finally conformance info (if any).
808809
>;

lib/Serialization/Deserialization.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3265,24 +3265,35 @@ ModuleFile::getDeclChecked(DeclID DID, Optional<DeclContext *> ForcedContext) {
32653265
GenericEnvironmentID genericEnvID;
32663266
TypeID rawTypeID;
32673267
uint8_t rawAccessLevel;
3268-
unsigned numConformances;
3269-
ArrayRef<uint64_t> rawInheritedIDs;
3268+
unsigned numConformances, numInheritedTypes;
3269+
ArrayRef<uint64_t> rawInheritedAndDependencyIDs;
32703270

32713271
decls_block::EnumLayout::readRecord(scratch, nameID, contextID,
32723272
isImplicit, genericEnvID, rawTypeID,
32733273
rawAccessLevel,
3274-
numConformances, rawInheritedIDs);
3274+
numConformances, numInheritedTypes,
3275+
rawInheritedAndDependencyIDs);
32753276

32763277
auto DC = getDeclContext(contextID);
32773278
if (declOrOffset.isComplete())
32783279
return declOrOffset;
32793280

3281+
Identifier name = getIdentifier(nameID);
3282+
for (TypeID dependencyID :
3283+
rawInheritedAndDependencyIDs.slice(numInheritedTypes)) {
3284+
auto dependency = getTypeChecked(dependencyID);
3285+
if (!dependency) {
3286+
return llvm::make_error<TypeError>(
3287+
name, takeErrorInfo(dependency.takeError()));
3288+
}
3289+
}
3290+
32803291
auto genericParams = maybeReadGenericParams(DC);
32813292
if (declOrOffset.isComplete())
32823293
return declOrOffset;
32833294

3284-
auto theEnum = createDecl<EnumDecl>(SourceLoc(), getIdentifier(nameID),
3285-
SourceLoc(), None, genericParams, DC);
3295+
auto theEnum = createDecl<EnumDecl>(SourceLoc(), name, SourceLoc(), None,
3296+
genericParams, DC);
32863297

32873298
declOrOffset = theEnum;
32883299

@@ -3301,7 +3312,8 @@ ModuleFile::getDeclChecked(DeclID DID, Optional<DeclContext *> ForcedContext) {
33013312

33023313
theEnum->computeType();
33033314

3304-
handleInherited(theEnum, rawInheritedIDs);
3315+
handleInherited(theEnum,
3316+
rawInheritedAndDependencyIDs.slice(0, numInheritedTypes));
33053317

33063318
theEnum->setMemberLoader(this, DeclTypeCursor.GetCurrentBitNo());
33073319
skipRecord(DeclTypeCursor, decls_block::MEMBERS);

lib/Serialization/Serialization.cpp

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,12 +2315,46 @@ void Serializer::writeForeignErrorConvention(const ForeignErrorConvention &fec){
23152315
resultTypeID);
23162316
}
23172317

2318-
static SmallVector<Type, 4> collectDependenciesFromType(CanType ty) {
2319-
llvm::SmallSetVector<Type, 4> result;
2320-
ty.visit([&](CanType next) {
2321-
if (auto *nominal = next->getAnyNominal())
2322-
result.insert(nominal->getDeclaredInterfaceType());
2318+
/// Returns true if the declaration of \p decl depends on \p problemContext
2319+
/// based on lexical nesting.
2320+
///
2321+
/// - \p decl is \p problemContext
2322+
/// - \p decl is declared within \p problemContext
2323+
/// - \p decl is declared in an extension of a type that depends on
2324+
/// \p problemContext
2325+
static bool contextDependsOn(const NominalTypeDecl *decl,
2326+
const DeclContext *problemContext) {
2327+
const DeclContext *dc = decl;
2328+
do {
2329+
if (dc == problemContext)
2330+
return true;
2331+
if (auto *extension = dyn_cast<ExtensionDecl>(dc))
2332+
dc = extension->getAsNominalTypeOrNominalTypeExtensionContext();
2333+
else
2334+
dc = dc->getParent();
2335+
} while (!dc->isModuleScopeContext());
2336+
return false;
2337+
}
2338+
2339+
static void collectDependenciesFromType(llvm::SmallSetVector<Type, 4> &seen,
2340+
Type ty,
2341+
const DeclContext *excluding) {
2342+
ty.visit([&](Type next) {
2343+
auto *nominal = next->getAnyNominal();
2344+
if (!nominal)
2345+
return;
2346+
// FIXME: The child context case is still important for enums. It's
2347+
// possible an enum element has a payload that references a type declaration
2348+
// nested within the enum that can't be imported (for whatever reason).
2349+
if (contextDependsOn(nominal, excluding))
2350+
return;
2351+
seen.insert(nominal->getDeclaredInterfaceType());
23232352
});
2353+
}
2354+
2355+
static SmallVector<Type, 4> collectDependenciesFromType(Type ty) {
2356+
llvm::SmallSetVector<Type, 4> result;
2357+
collectDependenciesFromType(result, ty, /*excluding*/nullptr);
23242358
return result.takeVector();
23252359
}
23262360

@@ -2668,9 +2702,20 @@ void Serializer::writeDecl(const Decl *D) {
26682702
ConformanceLookupKind::All,
26692703
nullptr, /*sorted=*/true);
26702704

2671-
SmallVector<TypeID, 4> inheritedTypes;
2705+
SmallVector<TypeID, 4> inheritedAndDependencyTypes;
26722706
for (auto inherited : theEnum->getInherited())
2673-
inheritedTypes.push_back(addTypeRef(inherited.getType()));
2707+
inheritedAndDependencyTypes.push_back(addTypeRef(inherited.getType()));
2708+
2709+
llvm::SmallSetVector<Type, 4> dependencyTypes;
2710+
for (const EnumElementDecl *nextElt : theEnum->getAllElements()) {
2711+
if (!nextElt->hasAssociatedValues())
2712+
continue;
2713+
collectDependenciesFromType(dependencyTypes,
2714+
nextElt->getArgumentInterfaceType(),
2715+
/*excluding*/theEnum);
2716+
}
2717+
for (Type ty : dependencyTypes)
2718+
inheritedAndDependencyTypes.push_back(addTypeRef(ty));
26742719

26752720
uint8_t rawAccessLevel =
26762721
getRawStableAccessibility(theEnum->getFormalAccess());
@@ -2685,7 +2730,8 @@ void Serializer::writeDecl(const Decl *D) {
26852730
addTypeRef(theEnum->getRawType()),
26862731
rawAccessLevel,
26872732
conformances.size(),
2688-
inheritedTypes);
2733+
theEnum->getInherited().size(),
2734+
inheritedAndDependencyTypes);
26892735

26902736
writeGenericParams(theEnum->getGenericParams());
26912737
writeMembers(theEnum->getMembers(), false);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: %target-swift-frontend -emit-sil -o - -emit-module-path %t/Lib.swiftmodule -module-name Lib -I %S/Inputs/custom-modules -disable-objc-attr-requires-foundation-module %s > /dev/null
3+
4+
// RUN: %target-swift-ide-test -source-filename=x -print-module -module-to-print Lib -I %t -I %S/Inputs/custom-modules | %FileCheck %s
5+
6+
// RUN: %target-swift-ide-test -source-filename=x -print-module -module-to-print Lib -I %t -I %S/Inputs/custom-modules -Xcc -DBAD | %FileCheck -check-prefix CHECK-RECOVERY %s
7+
8+
// RUN: %target-swift-frontend -typecheck -I %t -I %S/Inputs/custom-modules -Xcc -DBAD -DTEST %s -verify
9+
10+
#if TEST
11+
12+
import Typedefs
13+
import Lib
14+
15+
func use(_: OkayEnum) {}
16+
// FIXME: Better to import the enum and make it unavailable.
17+
func use(_: BadEnum) {} // expected-error {{use of undeclared type 'BadEnum'}}
18+
19+
func test() {
20+
_ = producesOkayEnum()
21+
_ = producesBadEnum() // expected-error {{use of unresolved identifier 'producesBadEnum'}}
22+
}
23+
24+
#else // TEST
25+
26+
import Typedefs
27+
28+
public enum BadEnum {
29+
case noPayload
30+
case perfectlyOkayPayload(Int)
31+
case problematic(Any, WrappedInt)
32+
case alsoOkay(Any, Any, Any)
33+
}
34+
// CHECK-LABEL: enum BadEnum {
35+
// CHECK-RECOVERY-NOT: enum BadEnum
36+
37+
public enum OkayEnum {
38+
case noPayload
39+
case plainOldAlias(Any, UnwrappedInt)
40+
case other(Int)
41+
}
42+
// CHECK-LABEL: enum OkayEnum {
43+
// CHECK-NEXT: case noPayload
44+
// CHECK-NEXT: case plainOldAlias(Any, UnwrappedInt)
45+
// CHECK-NEXT: case other(Int)
46+
// CHECK-NEXT: }
47+
// CHECK-RECOVERY-LABEL: enum OkayEnum {
48+
// CHECK-RECOVERY-NEXT: case noPayload
49+
// CHECK-RECOVERY-NEXT: case plainOldAlias(Any, Int32)
50+
// CHECK-RECOVERY-NEXT: case other(Int)
51+
// CHECK-RECOVERY-NEXT: }
52+
53+
public enum OkayEnumWithSelfRefs {
54+
public struct Nested {}
55+
indirect case selfRef(OkayEnumWithSelfRefs)
56+
case nested(Nested)
57+
}
58+
// CHECK-LABEL: enum OkayEnumWithSelfRefs {
59+
// CHECK-NEXT: struct Nested {
60+
// CHECK-NEXT: init()
61+
// CHECK-NEXT: }
62+
// CHECK-NEXT: indirect case selfRef(OkayEnumWithSelfRefs)
63+
// CHECK-NEXT: case nested(OkayEnumWithSelfRefs.Nested)
64+
// CHECK-NEXT: }
65+
// CHECK-RECOVERY-LABEL: enum OkayEnumWithSelfRefs {
66+
// CHECK-RECOVERY-NEXT: struct Nested {
67+
// CHECK-RECOVERY-NEXT: init()
68+
// CHECK-RECOVERY-NEXT: }
69+
// CHECK-RECOVERY-NEXT: indirect case selfRef(OkayEnumWithSelfRefs)
70+
// CHECK-RECOVERY-NEXT: case nested(OkayEnumWithSelfRefs.Nested)
71+
// CHECK-RECOVERY-NEXT: }
72+
73+
public func producesBadEnum() -> BadEnum { return .noPayload }
74+
// CHECK-LABEL: func producesBadEnum() -> BadEnum
75+
// CHECK-RECOVERY-NOT: func producesBadEnum() -> BadEnum
76+
77+
public func producesOkayEnum() -> OkayEnum { return .noPayload }
78+
// CHECK-LABEL: func producesOkayEnum() -> OkayEnum
79+
// CHECK-RECOVERY-LABEL: func producesOkayEnum() -> OkayEnum
80+
81+
#endif // TEST

0 commit comments

Comments
 (0)