Skip to content

Commit 0f5b7b0

Browse files
committed
Dynamic casts to AnyObject should succeed for an arbitrary source
type because we can always make a SwiftValue. rdar://26268575
1 parent 6e978a6 commit 0f5b7b0

File tree

4 files changed

+83
-6
lines changed

4 files changed

+83
-6
lines changed

stdlib/public/runtime/Casting.cpp

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,26 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
10491049
MetadataKind kind =
10501050
srcDynamicType ? srcDynamicType->getKind() : MetadataKind::Class;
10511051

1052+
// A fallback to use if we don't have a more specialized approach
1053+
// for a non-class type.
1054+
auto fallbackForNonClass = [&] {
1055+
#if SWIFT_OBJC_INTEROP
1056+
// If the destination type is a set of protocols that SwiftValue
1057+
// implements, we're fine.
1058+
if (findSwiftValueConformances(targetType->Protocols,
1059+
destExistential->getWitnessTables())) {
1060+
bool consumeValue = dynamicFlags & DynamicCastFlags::TakeOnSuccess;
1061+
destExistential->Value =
1062+
bridgeAnythingToSwiftValueObject(srcDynamicValue, srcDynamicType,
1063+
consumeValue);
1064+
maybeDeallocateSource(true);
1065+
return true;
1066+
}
1067+
#endif
1068+
1069+
return _fail(src, srcType, targetType, flags);
1070+
};
1071+
10521072
// If the source type is a value type, it cannot possibly conform
10531073
// to a class-bounded protocol.
10541074
switch (kind) {
@@ -1068,14 +1088,15 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
10681088
}
10691089
#endif
10701090
// Otherwise, metatypes aren't class objects.
1071-
return _fail(src, srcType, targetType, flags);
1091+
return fallbackForNonClass();
10721092
}
10731093

10741094
case MetadataKind::Class:
10751095
case MetadataKind::ObjCClassWrapper:
10761096
case MetadataKind::ForeignClass:
10771097
case MetadataKind::Existential:
1078-
// Handle these cases below.
1098+
// Handle the class cases below. Note that opaque existentials
1099+
// shouldn't get here because we should have drilled into them above.
10791100
break;
10801101

10811102
case MetadataKind::Struct:
@@ -1105,16 +1126,15 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
11051126
return success;
11061127
}
11071128
#endif
1108-
break;
1129+
SWIFT_FALLTHROUGH;
11091130

11101131
case MetadataKind::Function:
11111132
case MetadataKind::HeapLocalVariable:
11121133
case MetadataKind::HeapGenericLocalVariable:
11131134
case MetadataKind::ErrorObject:
11141135
case MetadataKind::Opaque:
11151136
case MetadataKind::Tuple:
1116-
// Will never succeed.
1117-
return _fail(src, srcType, targetType, flags);
1137+
return fallbackForNonClass();
11181138
}
11191139

11201140
// Check for protocol conformances and fill in the witness tables. If

stdlib/public/runtime/SwiftValue.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#if SWIFT_OBJC_INTEROP
2525
#include <objc/runtime.h>
26+
#include "swift/Runtime/Metadata.h"
2627

2728
// _SwiftValue is an Objective-C class, but we shouldn't interface with it
2829
// directly as such. Keep the type opaque.
@@ -51,6 +52,12 @@ getValueFromSwiftValue(_SwiftValue *v);
5152
/// or nil if it is not.
5253
_SwiftValue *getAsSwiftValue(id object);
5354

55+
/// Find conformances for SwiftValue to the given list of protocols.
56+
///
57+
/// Returns true if SwiftValue does conform to all the protocols.
58+
bool findSwiftValueConformances(const ProtocolDescriptorList &protocols,
59+
const WitnessTable **tablesBuffer);
60+
5461
} // namespace swift
5562
#endif
5663

stdlib/public/runtime/SwiftValue.mm

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,49 @@ static size_t getSwiftValuePayloadAlignMask(const Metadata *type) {
212212
return nil;
213213
}
214214

215+
bool
216+
swift::findSwiftValueConformances(const ProtocolDescriptorList &protocols,
217+
const WitnessTable **tablesBuffer) {
218+
Class cls = nullptr;
219+
220+
// Note that currently we never modify tablesBuffer because
221+
// _SwiftValue doesn't conform to any protocols that need witness tables.
222+
223+
for (size_t i = 0, e = protocols.NumProtocols; i != e; ++i) {
224+
auto protocol = protocols[i];
225+
226+
// _SwiftValue does conform to AnyObject.
227+
switch (protocol->Flags.getSpecialProtocol()) {
228+
case SpecialProtocol::AnyObject:
229+
continue;
230+
231+
case SpecialProtocol::Error:
232+
return false;
233+
234+
case SpecialProtocol::None:
235+
break;
236+
}
237+
238+
// Otherwise, it only conforms to ObjC protocols. We specifically
239+
// don't want to say that _SwiftValue conforms to the Swift protocols
240+
// that NSObject conforms to because that would create a situation
241+
// where arguably an arbitrary type would conform to those protocols
242+
// by way of coercion through _SwiftValue. Eventually we want to
243+
// change _SwiftValue to not be an NSObject subclass at all.
244+
245+
if (protocol->Flags.getDispatchStrategy() != ProtocolDispatchStrategy::ObjC)
246+
return false;
247+
248+
if (!cls) cls = _getSwiftValueClass();
249+
250+
// Check whether the class conforms to the protocol.
251+
if (![cls conformsToProtocol: (Protocol*) protocol])
252+
return false;
253+
}
254+
255+
return true;
256+
}
257+
215258
@implementation _SwiftValue
216259

217260
+ (instancetype)allocWithZone:(NSZone *)zone {

test/Interpreter/generic_casts.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,15 @@ print(allToAll(type(of: C()), AnyObject.self)) // CHECK-NEXT: true
158158
// Bridging
159159
print(allToAll(0, AnyObject.self)) // CHECK-NEXT: true
160160

161+
// This will get bridged using _SwiftValue.
161162
struct NotBridged { var x: Int }
162-
print(allToAll(NotBridged(x: 0), AnyObject.self)) // CHECK-NEXT: false
163+
print(allToAll(NotBridged(x: 0), AnyObject.self)) // CHECK-NEXT: true
164+
print(allToAll(NotBridged(x: 0), NSCopying.self)) // CHECK-NEXT: true
165+
166+
// These casts fail (intentionally) even though _SwiftValue does
167+
// technically conform to these protocols through NSObject.
168+
print(allToAll(NotBridged(x: 0), CustomStringConvertible.self)) // CHECK-NEXT: false
169+
print(allToAll(NotBridged(x: 0), (AnyObject & CustomStringConvertible).self)) // CHECK-NEXT: false
163170

164171
//
165172
// rdar://problem/19482567

0 commit comments

Comments
 (0)