Skip to content

Commit a52fac4

Browse files
authored
[Serialization] Store whether an override depends on its base for ABI (#27784)
In some circumstances, a Swift declaration in module A will depend on another declaration (usually from Objective-C) that can't be loaded, for whatever reason. If the Swift declaration is *overriding* the missing declaration, this can present a problem, because the way methods are dispatched in Swift can depend on knowing the original class that introduced the method. However, if the compiler can prove that the override can still be safely invoked/used in all cases, it doesn't need to worry about the overridden declaration being missing. This is especially relevant for property accessors, because there's currently no logic to recover from a property being successfully deserialized and then finding out that an accessor couldn't be. The decision of whether or not an override can be safely invoked without knowledge of the base method is something to be cautious about---a mistaken analysis would effectively be a miscompile. So up until now, this was limited to one case: when a method is known to be `@objc dynamic`, i.e. always dispatched through objc_msgSend. (Even this may become questionable if we have first-class method references, like we do for key paths.) This worked particularly well because the compiler infers 'dynamic' for any overload of an imported Objective-C method or accessor, in case it imports differently in a different -swift-version and a client ends up subclassing it. However...that inference does not apply if the class is final, because then there are no subclasses to worry about. This commit changes the test to be more careful: if the /missing/ declaration was `@objc dynamic`, we know that it can't affect ABI, because either the override is properly `@objc dynamic` as well, or the override has introduced its own calling ABI (in practice, a direct call for final methods) that doesn't depend on the superclass. Again, this isn't 100% correct in the presence of first-class methods, but it does fix the issue in practice where a property accessor in a parent class goes missing. And since Objective-C allows adding property setters separately from the original property declaration, that's something that can happen even under normal circumstances. Sadly. This approach could probably be extended to constructors as well. I'm a little more cautious about throwing vars and subscripts into the mix because of the presence of key paths, which do allow identity-based comparison of overrides and bases. rdar://problem/56388950
1 parent 6904dba commit a52fac4

File tree

5 files changed

+72
-14
lines changed

5 files changed

+72
-14
lines changed

lib/Serialization/Deserialization.cpp

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,7 +2822,7 @@ class swift::DeclDeserializer {
28222822
DeclID associatedDeclID;
28232823
DeclID overriddenID;
28242824
DeclID accessorStorageDeclID;
2825-
bool needsNewVTableEntry, isTransparent;
2825+
bool overriddenAffectsABI, needsNewVTableEntry, isTransparent;
28262826
DeclID opaqueReturnTypeID;
28272827
ArrayRef<uint64_t> nameAndDependencyIDs;
28282828

@@ -2835,6 +2835,7 @@ class swift::DeclDeserializer {
28352835
resultInterfaceTypeID,
28362836
isIUO,
28372837
associatedDeclID, overriddenID,
2838+
overriddenAffectsABI,
28382839
numNameComponentsBiased,
28392840
rawAccessLevel,
28402841
needsNewVTableEntry,
@@ -2849,6 +2850,7 @@ class swift::DeclDeserializer {
28492850
resultInterfaceTypeID,
28502851
isIUO,
28512852
overriddenID,
2853+
overriddenAffectsABI,
28522854
accessorStorageDeclID,
28532855
rawAccessorKind,
28542856
rawAccessLevel,
@@ -2914,20 +2916,11 @@ class swift::DeclDeserializer {
29142916
overridden = overriddenOrError.get();
29152917
} else {
29162918
llvm::consumeError(overriddenOrError.takeError());
2917-
// There's one case where we know it's safe to ignore a missing override:
2918-
// if this declaration is '@objc' and 'dynamic'.
2919-
bool canIgnoreMissingOverriddenDecl = false;
2920-
if (isObjC && ctx.LangOpts.EnableDeserializationRecovery) {
2921-
canIgnoreMissingOverriddenDecl =
2922-
std::any_of(DeclAttributes::iterator(DAttrs),
2923-
DeclAttributes::iterator(nullptr),
2924-
[](const DeclAttribute *attr) -> bool {
2925-
return isa<DynamicAttr>(attr);
2926-
});
2927-
}
2928-
if (!canIgnoreMissingOverriddenDecl)
2919+
2920+
if (overriddenAffectsABI || !ctx.LangOpts.EnableDeserializationRecovery) {
29292921
return llvm::make_error<OverrideError>(
29302922
name, errorFlags, numVTableEntries);
2923+
}
29312924

29322925
overridden = nullptr;
29332926
}

lib/Serialization/ModuleFormat.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5252
/// describe what change you made. The content of this comment isn't important;
5353
/// it just ensures a conflict if two people change the module format.
5454
/// Don't worry about adhering to the 80-column limit for this line.
55-
const uint16_t SWIFTMODULE_VERSION_MINOR = 519; // SIL function availability
55+
const uint16_t SWIFTMODULE_VERSION_MINOR = 521; // "overridden affects ABI" flag
5656

5757
/// A standard hash seed used for all string hashes in a serialized module.
5858
///
@@ -1200,6 +1200,7 @@ namespace decls_block {
12001200
BCFixed<1>, // IUO result?
12011201
DeclIDField, // operator decl
12021202
DeclIDField, // overridden function
1203+
BCFixed<1>, // whether the overridden decl affects ABI
12031204
BCVBR<5>, // 0 for a simple name, otherwise the number of parameter name
12041205
// components plus one
12051206
AccessLevelField, // access level
@@ -1241,6 +1242,7 @@ namespace decls_block {
12411242
TypeIDField, // result interface type
12421243
BCFixed<1>, // IUO result?
12431244
DeclIDField, // overridden function
1245+
BCFixed<1>, // whether the overridden decl affects ABI
12441246
DeclIDField, // AccessorStorageDecl
12451247
AccessorKindField, // accessor kind
12461248
AccessLevelField, // access level

lib/Serialization/Serialization.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,6 +2593,24 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
25932593
return count;
25942594
}
25952595

2596+
/// Returns true if a client can still use decls that override \p overridden
2597+
/// even if \p overridden itself isn't available (isn't found, can't be
2598+
/// imported, can't be deserialized, whatever).
2599+
///
2600+
/// This should be kept conservative. Compiler crashes are still better than
2601+
/// miscompiles.
2602+
static bool overriddenDeclAffectsABI(const ValueDecl *overridden) {
2603+
if (!overridden)
2604+
return false;
2605+
// There's one case where we know a declaration doesn't affect the ABI of
2606+
// its overrides after they've been compiled: if the declaration is '@objc'
2607+
// and 'dynamic'. In that case, all accesses to the method or property will
2608+
// go through the Objective-C method tables anyway.
2609+
if (overridden->hasClangNode() || overridden->isObjCDynamic())
2610+
return false;
2611+
return true;
2612+
}
2613+
25962614
public:
25972615
DeclSerializer(Serializer &S, DeclID id) : S(S), id(id) {}
25982616
~DeclSerializer() {
@@ -3212,6 +3230,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
32123230
fn->isImplicitlyUnwrappedOptional(),
32133231
S.addDeclRef(fn->getOperatorDecl()),
32143232
S.addDeclRef(fn->getOverriddenDecl()),
3233+
overriddenDeclAffectsABI(fn->getOverriddenDecl()),
32153234
fn->getFullName().getArgumentNames().size() +
32163235
fn->getFullName().isCompoundName(),
32173236
rawAccessLevel,
@@ -3267,6 +3286,9 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
32673286
uint8_t rawAccessorKind =
32683287
uint8_t(getStableAccessorKind(fn->getAccessorKind()));
32693288

3289+
bool overriddenAffectsABI =
3290+
overriddenDeclAffectsABI(fn->getOverriddenDecl());
3291+
32703292
Type ty = fn->getInterfaceType();
32713293
SmallVector<IdentifierID, 4> dependencies;
32723294
for (auto dependency : collectDependenciesFromType(ty->getCanonicalType()))
@@ -3288,6 +3310,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
32883310
S.addTypeRef(fn->getResultInterfaceType()),
32893311
fn->isImplicitlyUnwrappedOptional(),
32903312
S.addDeclRef(fn->getOverriddenDecl()),
3313+
overriddenAffectsABI,
32913314
S.addDeclRef(fn->getStorage()),
32923315
rawAccessorKind,
32933316
rawAccessLevel,

test/Serialization/Recovery/Inputs/custom-modules/Overrides.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
- (nullable id)nullabilityChangeMethod;
99
- (nonnull id)typeChangeMethod;
1010
@property (readonly) long disappearingProperty;
11+
@property (readwrite) long disappearingPropertySetter;
1112
#else
1213
//- (void)disappearingMethod;
1314
- (nonnull id)nullabilityChangeMethod;
1415
- (nonnull Base *)typeChangeMethod;
1516
// @property (readonly) long disappearingProperty;
17+
@property (readonly) long disappearingPropertySetter;
1618
#endif
1719
@end
1820

test/Serialization/Recovery/overrides.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,36 @@ public class A_Sub: Base {
7272
public override func typeChangeMethod() -> Any { return self }
7373
public override func disappearingMethodWithOverload() {}
7474
public override var disappearingProperty: Int { return 0 }
75+
public override var disappearingPropertySetter: Int {
76+
get { return 0 }
77+
set {}
78+
}
7579
}
7680

7781
public class A_Sub2: A_Sub {
7882
public override func disappearingMethod() {}
7983
}
8084

85+
public final class A_Sub3Final: Base {
86+
public override func disappearingMethod() {}
87+
public override func nullabilityChangeMethod() -> Any? { return nil }
88+
public override func typeChangeMethod() -> Any { return self }
89+
public override func disappearingMethodWithOverload() {}
90+
public override var disappearingProperty: Int { return 0 }
91+
public override var disappearingPropertySetter: Int {
92+
get { return 0 }
93+
set {}
94+
}
95+
}
96+
8197

8298
// CHECK-LABEL: class A_Sub : Base {
8399
// CHECK-NEXT: func disappearingMethod()
84100
// CHECK-NEXT: func nullabilityChangeMethod() -> Any?
85101
// CHECK-NEXT: func typeChangeMethod() -> Any
86102
// CHECK-NEXT: func disappearingMethodWithOverload()
87103
// CHECK-NEXT: var disappearingProperty: Int { get }
104+
// CHECK-NEXT: var disappearingPropertySetter: Int{{$}}
88105
// CHECK-NEXT: init()
89106
// CHECK-NEXT: {{^}$}}
90107

@@ -93,12 +110,23 @@ public class A_Sub2: A_Sub {
93110
// CHECK-NEXT: init()
94111
// CHECK-NEXT: {{^}$}}
95112

113+
// CHECK-LABEL: final class A_Sub3Final : Base {
114+
// CHECK-NEXT: func disappearingMethod()
115+
// CHECK-NEXT: func nullabilityChangeMethod() -> Any?
116+
// CHECK-NEXT: func typeChangeMethod() -> Any
117+
// CHECK-NEXT: func disappearingMethodWithOverload()
118+
// CHECK-NEXT: var disappearingProperty: Int { get }
119+
// CHECK-NEXT: var disappearingPropertySetter: Int{{$}}
120+
// CHECK-NEXT: init()
121+
// CHECK-NEXT: {{^}$}}
122+
96123
// CHECK-RECOVERY-LABEL: class A_Sub : Base {
97124
// CHECK-RECOVERY-NEXT: func disappearingMethod()
98125
// CHECK-RECOVERY-NEXT: func nullabilityChangeMethod() -> Any?
99126
// CHECK-RECOVERY-NEXT: func typeChangeMethod() -> Any
100127
// CHECK-RECOVERY-NEXT: func disappearingMethodWithOverload()
101128
// CHECK-RECOVERY-NEXT: /* placeholder for disappearingProperty */
129+
// CHECK-RECOVERY-NEXT: var disappearingPropertySetter: Int{{$}}
102130
// CHECK-RECOVERY-NEXT: init()
103131
// CHECK-RECOVERY-NEXT: {{^}$}}
104132

@@ -107,6 +135,16 @@ public class A_Sub2: A_Sub {
107135
// CHECK-RECOVERY-NEXT: init()
108136
// CHECK-RECOVERY-NEXT: {{^}$}}
109137

138+
// CHECK-RECOVERY-LABEL: class A_Sub3Final : Base {
139+
// CHECK-RECOVERY-NEXT: func disappearingMethod()
140+
// CHECK-RECOVERY-NEXT: func nullabilityChangeMethod() -> Any?
141+
// CHECK-RECOVERY-NEXT: func typeChangeMethod() -> Any
142+
// CHECK-RECOVERY-NEXT: func disappearingMethodWithOverload()
143+
// CHECK-RECOVERY-NEXT: /* placeholder for disappearingProperty */
144+
// CHECK-RECOVERY-NEXT: var disappearingPropertySetter: Int{{$}}
145+
// CHECK-RECOVERY-NEXT: init()
146+
// CHECK-RECOVERY-NEXT: {{^}$}}
147+
110148
extension Base {
111149
@nonobjc func disappearingMethodWithOverload() -> SwiftOnlyClass? { return nil }
112150
}

0 commit comments

Comments
 (0)