Skip to content

Commit baa99c6

Browse files
committed
Serialization: Recovery for protocol conformances with changed witness or requirement signatures.
Deserializing a witness record in a conformance may fail if either of the requirement or witness changed name or type, most likely due to SDK modernization changes across Swift versions. When this happens, leave an opaque placeholder in the conformance to indicate that the witness exists but we don't get to see it. For expedience, right now this just witnesses the requirement to itself, so that code in the type checker or elsewhere that tries to ad-hoc devirtualize references to the requirement just gets the requirement back. Arguably, we shouldn't include the witness at all in imported conformances, since they should be an implementation detail, but that's a bigger, riskier change. This patch as is should be enough to address rdar://problem/31185053.
1 parent 6fc36e5 commit baa99c6

File tree

14 files changed

+344
-16
lines changed

14 files changed

+344
-16
lines changed

include/swift/AST/Witness.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ class Witness {
108108
/// not generic (excepting \c Self) and the conforming type is non-generic.
109109
Witness(ValueDecl *witness) : storage(witness) { assert(witness != nullptr); }
110110

111+
/// Create an opaque witness for the given requirement.
112+
///
113+
/// This indicates that a witness exists, but is not visible to the current
114+
/// module.
115+
static Witness forOpaque(ValueDecl *requirement) {
116+
// TODO: It's probably a good idea to have a separate 'opaque' bit.
117+
// Making req == witness is kind of a hack.
118+
return Witness(requirement);
119+
}
120+
111121
/// Create a witness that requires substitutions.
112122
///
113123
/// \param decl The declaration for the witness.

include/swift/Basic/Version.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ class Version {
144144

145145
bool operator>=(const Version &lhs, const Version &rhs);
146146
bool operator==(const Version &lhs, const Version &rhs);
147+
inline bool operator!=(const Version &lhs, const Version &rhs) {
148+
return !(lhs == rhs);
149+
}
147150

148151
raw_ostream &operator<<(raw_ostream &os, const Version &version);
149152

include/swift/Serialization/ModuleFile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class ModuleFile : public LazyMemberLoader {
7373
StringRef TargetTriple;
7474

7575
/// The Swift compatibility version in use when this module was built.
76-
StringRef CompatibilityVersion;
76+
version::Version CompatibilityVersion;
7777

7878
/// The data blob containing all of the module's identifiers.
7979
StringRef IdentifierData;

include/swift/Serialization/Validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct ValidationInfo {
7171
StringRef name = {};
7272
StringRef targetTriple = {};
7373
StringRef shortVersion = {};
74-
StringRef compatibilityVersion = {};
74+
version::Version compatibilityVersion = {};
7575
size_t bytes = 0;
7676
Status status = Status::Malformed;
7777
};

lib/Basic/Version.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ Optional<Version> Version::parseVersionString(StringRef VersionString,
226226
return isValidVersion ? Optional<Version>(TheVersion) : None;
227227
}
228228

229+
Version::Version(StringRef VersionString,
230+
SourceLoc Loc,
231+
DiagnosticEngine *Diags)
232+
: Version(*parseVersionString(VersionString, Loc, Diags))
233+
{}
229234

230235
Version Version::getCurrentCompilerVersion() {
231236
#ifdef SWIFT_COMPILER_VERSION

lib/Serialization/Deserialization.cpp

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,20 @@ void ModuleFile::fatal(llvm::Error error) {
165165
getContext().Diags.diagnose(SourceLoc(), diag::serialization_fatal, Name);
166166

167167
if (!CompatibilityVersion.empty()) {
168-
SmallString<16> buffer;
169-
llvm::raw_svector_ostream out(buffer);
170-
out << getContext().LangOpts.EffectiveLanguageVersion;
171-
if (out.str() != CompatibilityVersion) {
168+
if (getContext().LangOpts.EffectiveLanguageVersion
169+
!= CompatibilityVersion) {
170+
SmallString<16> effectiveVersionBuffer, compatVersionBuffer;
171+
{
172+
llvm::raw_svector_ostream out(effectiveVersionBuffer);
173+
out << getContext().LangOpts.EffectiveLanguageVersion;
174+
}
175+
{
176+
llvm::raw_svector_ostream out(compatVersionBuffer);
177+
out << CompatibilityVersion;
178+
}
172179
getContext().Diags.diagnose(
173180
SourceLoc(), diag::serialization_compatibility_version_mismatch,
174-
out.str(), Name, CompatibilityVersion);
181+
effectiveVersionBuffer, Name, compatVersionBuffer);
175182
}
176183
}
177184
}
@@ -4464,20 +4471,69 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
44644471

44654472
ArrayRef<uint64_t>::iterator rawIDIter = rawIDs.begin();
44664473

4474+
// An imported requirement may have changed type between Swift versions.
4475+
// In this situation we need to do a post-pass to fill in missing
4476+
// requirements with opaque witnesses.
4477+
bool needToFillInOpaqueValueWitnesses = false;
44674478
while (valueCount--) {
4468-
auto req = cast<ValueDecl>(getDecl(*rawIDIter++));
4469-
auto witness = cast_or_null<ValueDecl>(getDecl(*rawIDIter++));
4470-
assert(witness ||
4479+
ValueDecl *req;
4480+
4481+
auto trySetWitness = [&](Witness w) {
4482+
if (req)
4483+
conformance->setWitness(req, w);
4484+
};
4485+
4486+
auto deserializedReq = getDeclChecked(*rawIDIter++);
4487+
if (deserializedReq) {
4488+
req = cast<ValueDecl>(*deserializedReq);
4489+
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
4490+
consumeError(deserializedReq.takeError());
4491+
req = nullptr;
4492+
needToFillInOpaqueValueWitnesses = true;
4493+
} else {
4494+
fatal(deserializedReq.takeError());
4495+
}
4496+
4497+
bool isOpaque = false;
4498+
ValueDecl *witness;
4499+
auto deserializedWitness = getDeclChecked(*rawIDIter++);
4500+
if (deserializedWitness) {
4501+
witness = cast<ValueDecl>(*deserializedWitness);
4502+
// Across language compatibility versions, the witnessing decl may have
4503+
// changed its signature as seen by the current compatibility version.
4504+
// In that case, we want the conformance to still be available, but
4505+
// we can't make use of the relationship to the underlying decl.
4506+
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
4507+
consumeError(deserializedWitness.takeError());
4508+
isOpaque = true;
4509+
witness = nullptr;
4510+
} else {
4511+
fatal(deserializedWitness.takeError());
4512+
}
4513+
4514+
assert(!req || isOpaque || witness ||
44714515
req->getAttrs().hasAttribute<OptionalAttr>() ||
44724516
req->getAttrs().isUnavailable(getContext()));
4473-
if (!witness) {
4474-
conformance->setWitness(req, Witness());
4517+
if (!witness && !isOpaque) {
4518+
trySetWitness(Witness());
44754519
continue;
44764520
}
44774521

44784522
// Generic signature and environment.
44794523
GenericSignature *syntheticSig = nullptr;
44804524
GenericEnvironment *syntheticEnv = nullptr;
4525+
4526+
auto trySetOpaqueWitness = [&]{
4527+
if (!req)
4528+
return;
4529+
4530+
// We shouldn't yet need to worry about generic requirements, since
4531+
// an imported ObjC method should never be generic.
4532+
assert(syntheticSig == nullptr && syntheticEnv == nullptr &&
4533+
"opaque witness shouldn't be generic yet. when this is "
4534+
"possible, it should use forwarding substitutions");
4535+
conformance->setWitness(req, Witness::forOpaque(req));
4536+
};
44814537

44824538
// Requirement -> synthetic map.
44834539
SmallVector<Substitution, 4> reqToSyntheticSubs;
@@ -4518,16 +4574,22 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
45184574
}
45194575
}
45204576

4577+
// Handle opaque witnesses that couldn't be deserialized.
4578+
if (isOpaque) {
4579+
trySetOpaqueWitness();
4580+
continue;
4581+
}
4582+
45214583
// Handle simple witnesses.
45224584
if (witnessSubstitutions.empty() && !syntheticSig && !syntheticEnv &&
45234585
reqToSyntheticSubs.empty()) {
4524-
conformance->setWitness(req, Witness(witness));
4586+
trySetWitness(Witness(witness));
45254587
continue;
45264588
}
45274589

45284590
// Set the witness.
4529-
conformance->setWitness(req, Witness(witness, witnessSubstitutions,
4530-
syntheticEnv, reqToSyntheticSubs));
4591+
trySetWitness(Witness(witness, witnessSubstitutions,
4592+
syntheticEnv, reqToSyntheticSubs));
45314593
}
45324594
assert(rawIDIter <= rawIDs.end() && "read too much");
45334595

@@ -4558,6 +4620,20 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
45584620
conformance->setTypeWitness(typeWitness.first, typeWitness.second.first,
45594621
typeWitness.second.second);
45604622
}
4623+
4624+
// Fill in opaque value witnesses if we need to.
4625+
if (needToFillInOpaqueValueWitnesses) {
4626+
for (auto member : proto->getMembers()) {
4627+
// We only care about non-associated-type requirements.
4628+
auto valueMember = dyn_cast<ValueDecl>(member);
4629+
if (!valueMember || !valueMember->isProtocolRequirement()
4630+
|| isa<AssociatedTypeDecl>(valueMember))
4631+
continue;
4632+
4633+
if (!conformance->hasWitness(valueMember))
4634+
conformance->setWitness(valueMember, Witness::forOpaque(valueMember));
4635+
}
4636+
}
45614637
}
45624638

45634639
GenericEnvironment *ModuleFile::loadGenericEnvironment(const DeclContext *decl,

lib/Serialization/ModuleFile.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ validateControlBlock(llvm::BitstreamCursor &cursor,
200200
default:
201201
// Add new cases here, in descending order.
202202
case 4:
203-
result.compatibilityVersion = blobData.substr(scratch[2]+1, scratch[3]);
203+
result.compatibilityVersion =
204+
version::Version(blobData.substr(scratch[2]+1, scratch[3]),
205+
SourceLoc(), nullptr);
204206
LLVM_FALLTHROUGH;
205207
case 3:
206208
result.shortVersion = blobData.slice(0, scratch[2]);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Name: SomeObjCModule
2+
Classes:
3+
- Name: NSRuncibleSpoon
4+
SwiftBridge: RuncibleSpoon
5+
SwiftVersions:
6+
- Version: 3
7+
Classes:
8+
- Name: NSRuncibleSpoon
9+
SwiftBridge: ""
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Swift 3 sees the ObjC class NSRuncibleSpoon as the class, and uses methods
2+
// with type signatures involving NSRuncibleSpoon to conform to protocols
3+
// across the language boundary. Swift 4 sees the type as bridged to
4+
// a RuncibleSpoon value type, but still needs to be able to use conformances
5+
// declared by Swift 3.
6+
7+
@import Foundation;
8+
9+
@interface NSRuncibleSpoon: NSObject
10+
@end
11+
12+
@interface SomeObjCClass: NSObject
13+
- (instancetype _Nonnull)initWithSomeSwiftInitRequirement:(NSRuncibleSpoon* _Nonnull)s;
14+
- (void)someSwiftMethodRequirement:(NSRuncibleSpoon* _Nonnull)s;
15+
@property NSRuncibleSpoon * _Nonnull someSwiftPropertyRequirement;
16+
@end
17+
18+
@protocol SomeObjCProtocol
19+
- (instancetype _Nonnull)initWithSomeObjCInitRequirement:(NSRuncibleSpoon * _Nonnull)string;
20+
- (void)someObjCMethodRequirement:(NSRuncibleSpoon * _Nonnull)string;
21+
@property NSRuncibleSpoon * _Nonnull someObjCPropertyRequirement;
22+
@end
23+
24+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// NB: This file is not named SomeObjCModule.swift to avoid getting picked up
2+
// by -enable-source-import
3+
4+
@_exported import SomeObjCModule
5+
6+
public struct RuncibleSpoon: _ObjectiveCBridgeable {
7+
public init() {}
8+
9+
public func _bridgeToObjectiveC() -> NSRuncibleSpoon {
10+
fatalError()
11+
}
12+
public static func _forceBridgeFromObjectiveC(_: NSRuncibleSpoon, result: inout RuncibleSpoon?) {
13+
fatalError()
14+
}
15+
public static func _conditionallyBridgeFromObjectiveC(_: NSRuncibleSpoon, result: inout RuncibleSpoon?) -> Bool {
16+
fatalError()
17+
}
18+
public static func _unconditionallyBridgeFromObjectiveC(_: NSRuncibleSpoon?) -> RuncibleSpoon {
19+
fatalError()
20+
}
21+
}
22+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module SomeObjCModule {
2+
header "SomeObjCModule.h"
3+
export *
4+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
// Swift 3 sees the ObjC class NSRuncibleSpoon as the class, and uses methods
3+
// with type signatures involving NSRuncibleSpoon to conform to protocols
4+
// across the language boundary. Swift 4 sees the type as bridged to
5+
// a RuncibleSpoon value type, but still needs to be able to use conformances
6+
// declared by Swift 3.
7+
8+
// Swift 3, importing Swift 3 and Swift 4 code
9+
10+
import SomeObjCModule
11+
import SomeSwift3Module
12+
import SomeSwift4Module
13+
14+
func testMatchAndMix(bridged: RuncibleSpoon, unbridged: NSRuncibleSpoon) {
15+
let objcInstanceViaClass
16+
= SomeObjCClass(someSwiftInitRequirement: unbridged)
17+
18+
let objcClassAsS3Protocol: SomeSwift3Protocol.Type = SomeObjCClass.self
19+
let objcInstanceViaS3Protocol
20+
= objcClassAsS3Protocol.init(someSwiftInitRequirement: unbridged)
21+
22+
let objcClassAsS4Protocol: SomeSwift4Protocol.Type = SomeObjCClass.self
23+
let objcInstanceViaS4Protocol
24+
= objcClassAsS4Protocol.init(someSwiftInitRequirement: bridged)
25+
26+
var bridgedSink: RuncibleSpoon
27+
var unbridgedSink: NSRuncibleSpoon
28+
29+
let swiftPropertyViaClass = objcInstanceViaClass.someSwiftPropertyRequirement
30+
unbridgedSink = swiftPropertyViaClass
31+
let swiftPropertyViaS3Protocol = objcInstanceViaS3Protocol.someSwiftPropertyRequirement
32+
unbridgedSink = swiftPropertyViaS3Protocol
33+
let swiftPropertyViaS4Protocol = objcInstanceViaS4Protocol.someSwiftPropertyRequirement
34+
bridgedSink = swiftPropertyViaS4Protocol
35+
36+
objcInstanceViaClass.someSwiftMethodRequirement(unbridged)
37+
objcInstanceViaS3Protocol.someSwiftMethodRequirement(unbridged)
38+
objcInstanceViaS4Protocol.someSwiftMethodRequirement(bridged)
39+
40+
let swift3InstanceViaClass
41+
= SomeSwift3Class(someObjCInitRequirement: unbridged)
42+
let swift3ClassAsProtocol: SomeObjCProtocol.Type = SomeSwift3Class.self
43+
let swift3InstanceViaProtocol
44+
= swift3ClassAsProtocol.init(someObjCInitRequirement: unbridged)
45+
46+
let objcPropertyViaClassS3 = swift3InstanceViaClass.someObjCPropertyRequirement
47+
unbridgedSink = objcPropertyViaClassS3
48+
let objcPropertyViaProtocolS3 = swift3InstanceViaProtocol.someObjCPropertyRequirement
49+
unbridgedSink = objcPropertyViaProtocolS3
50+
51+
swift3InstanceViaClass.someObjCMethodRequirement(unbridged)
52+
swift3InstanceViaProtocol.someObjCMethodRequirement(unbridged)
53+
54+
let swift4InstanceViaClass
55+
= SomeSwift4Class(someObjCInitRequirement: bridged)
56+
let swift4ClassAsProtocol: SomeObjCProtocol.Type = SomeSwift4Class.self
57+
let swift4InstanceViaProtocol
58+
= swift4ClassAsProtocol.init(someObjCInitRequirement: unbridged)
59+
60+
let objcPropertyViaClassS4 = swift4InstanceViaClass.someObjCPropertyRequirement
61+
bridgedSink = objcPropertyViaClassS4
62+
let objcPropertyViaProtocolS4 = swift4InstanceViaProtocol.someObjCPropertyRequirement
63+
unbridgedSink = objcPropertyViaProtocolS4
64+
65+
swift4InstanceViaClass.someObjCMethodRequirement(bridged)
66+
swift4InstanceViaProtocol.someObjCMethodRequirement(unbridged)
67+
68+
_ = bridgedSink
69+
_ = unbridgedSink
70+
}
71+

0 commit comments

Comments
 (0)