Skip to content

Commit 3d7319a

Browse files
authored
Merge pull request #9730 from jrose-apple/4.0-deserialization-recovery-for-enums
[4.0] [Serialization] Drop enums if a case payload can't be deserialized.
2 parents 552c833 + 02a2aeb commit 3d7319a

File tree

5 files changed

+177
-17
lines changed

5 files changed

+177
-17
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 = 344; // Last change: ctor newly required flag
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/ModuleFile.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1330,7 +1330,14 @@ void ModuleFile::lookupValue(DeclName name,
13301330
auto iter = OperatorMethodDecls->find(name.getBaseName());
13311331
if (iter != OperatorMethodDecls->end()) {
13321332
for (auto item : *iter) {
1333-
auto VD = cast<ValueDecl>(getDecl(item.second));
1333+
Expected<Decl *> declOrError = getDeclChecked(item.second);
1334+
if (!declOrError) {
1335+
if (!getContext().LangOpts.EnableDeserializationRecovery)
1336+
fatal(declOrError.takeError());
1337+
llvm::consumeError(declOrError.takeError());
1338+
continue;
1339+
}
1340+
auto VD = cast<ValueDecl>(declOrError.get());
13341341
results.push_back(VD);
13351342
}
13361343
}

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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
// Force a lookup of the ==
24+
_ = Optional(OkayEnum.noPayload).map { $0 == .noPayload }
25+
}
26+
27+
#else // TEST
28+
29+
import Typedefs
30+
31+
public enum BadEnum {
32+
case noPayload
33+
case perfectlyOkayPayload(Int)
34+
case problematic(Any, WrappedInt)
35+
case alsoOkay(Any, Any, Any)
36+
37+
static public func ==(a: BadEnum, b: BadEnum) -> Bool {
38+
return false
39+
}
40+
}
41+
// CHECK-LABEL: enum BadEnum {
42+
// CHECK-RECOVERY-NOT: enum BadEnum
43+
44+
public enum OkayEnum {
45+
case noPayload
46+
case plainOldAlias(Any, UnwrappedInt)
47+
case other(Int)
48+
49+
static public func ==(a: OkayEnum, b: OkayEnum) -> Bool {
50+
return false
51+
}
52+
}
53+
// CHECK-LABEL: enum OkayEnum {
54+
// CHECK-NEXT: case noPayload
55+
// CHECK-NEXT: case plainOldAlias(Any, UnwrappedInt)
56+
// CHECK-NEXT: case other(Int)
57+
// CHECK-NEXT: static func ==(a: OkayEnum, b: OkayEnum) -> Bool
58+
// CHECK-NEXT: }
59+
// CHECK-RECOVERY-LABEL: enum OkayEnum {
60+
// CHECK-RECOVERY-NEXT: case noPayload
61+
// CHECK-RECOVERY-NEXT: case plainOldAlias(Any, Int32)
62+
// CHECK-RECOVERY-NEXT: case other(Int)
63+
// CHECK-RECOVERY-NEXT: static func ==(a: OkayEnum, b: OkayEnum) -> Bool
64+
// CHECK-RECOVERY-NEXT: }
65+
66+
public enum OkayEnumWithSelfRefs {
67+
public struct Nested {}
68+
indirect case selfRef(OkayEnumWithSelfRefs)
69+
case nested(Nested)
70+
}
71+
// CHECK-LABEL: enum OkayEnumWithSelfRefs {
72+
// CHECK-NEXT: struct Nested {
73+
// CHECK-NEXT: init()
74+
// CHECK-NEXT: }
75+
// CHECK-NEXT: indirect case selfRef(OkayEnumWithSelfRefs)
76+
// CHECK-NEXT: case nested(OkayEnumWithSelfRefs.Nested)
77+
// CHECK-NEXT: }
78+
// CHECK-RECOVERY-LABEL: enum OkayEnumWithSelfRefs {
79+
// CHECK-RECOVERY-NEXT: struct Nested {
80+
// CHECK-RECOVERY-NEXT: init()
81+
// CHECK-RECOVERY-NEXT: }
82+
// CHECK-RECOVERY-NEXT: indirect case selfRef(OkayEnumWithSelfRefs)
83+
// CHECK-RECOVERY-NEXT: case nested(OkayEnumWithSelfRefs.Nested)
84+
// CHECK-RECOVERY-NEXT: }
85+
86+
public func producesBadEnum() -> BadEnum { return .noPayload }
87+
// CHECK-LABEL: func producesBadEnum() -> BadEnum
88+
// CHECK-RECOVERY-NOT: func producesBadEnum() -> BadEnum
89+
90+
public func producesOkayEnum() -> OkayEnum { return .noPayload }
91+
// CHECK-LABEL: func producesOkayEnum() -> OkayEnum
92+
// CHECK-RECOVERY-LABEL: func producesOkayEnum() -> OkayEnum
93+
94+
#endif // TEST

0 commit comments

Comments
 (0)