Skip to content

Commit 000664b

Browse files
authored
Merge pull request swiftlang#37912 from tbkka/tbkka/sr14635-overlyAggressiveSwiftValueBoxing-Part2
SR-14635: __SwiftValue should be transparent to casting
2 parents 5786221 + 455b6d6 commit 000664b

File tree

5 files changed

+92
-14
lines changed

5 files changed

+92
-14
lines changed

include/swift/Runtime/Bincompat.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ bool useLegacyOptionalNilInjectionInCasting();
3838
/// Obj-C interop
3939
bool useLegacyObjCBoxingInCasting();
4040

41+
/// Whether to use legacy semantics when unboxing __SwiftValue
42+
bool useLegacySwiftValueUnboxingInCasting();
43+
4144
} // namespace bincompat
4245

4346
} // namespace runtime

stdlib/public/runtime/Bincompat.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,27 @@ bool useLegacyOptionalNilInjectionInCasting() {
189189
// by that protocol.
190190
bool useLegacyObjCBoxingInCasting() {
191191
#if BINARY_COMPATIBILITY_APPLE
192-
return true; // For now, continue using the legacy behavior on Apple OSes
192+
return false; // For now, always use the new behavior on Apple OSes
193+
#else
194+
return false; // Always use the new behavior on non-Apple OSes
195+
#endif
196+
}
197+
198+
// Should casting be strict about protocol conformance when
199+
// unboxing values that were boxed for Obj-C use?
200+
201+
// Similar to `useLegacyObjCBoxingInCasting()`, but
202+
// this applies to the case where you have already boxed
203+
// some Swift non-reference-type into a `__SwiftValue`
204+
// and are now casting to a protocol.
205+
206+
// For example, this cast
207+
// `x as! AnyObject as? NSCopying`
208+
// always succeeded with the legacy semantics.
209+
210+
bool useLegacySwiftValueUnboxingInCasting() {
211+
#if BINARY_COMPATIBILITY_APPLE
212+
return false; // For now, always use the new behavior on Apple OSes
193213
#else
194214
return false; // Always use the new behavior on non-Apple OSes
195215
#endif

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,23 @@ tryCastUnwrappingObjCSwiftValueSource(
425425
destFailureType, srcFailureType,
426426
/*takeOnSuccess=*/ false, mayDeferChecks);
427427
}
428+
#else
429+
static DynamicCastResult
430+
tryCastUnwrappingSwiftValueSource(
431+
OpaqueValue *destLocation, const Metadata *destType,
432+
OpaqueValue *srcValue, const Metadata *srcType,
433+
const Metadata *&destFailureType, const Metadata *&srcFailureType,
434+
bool takeOnSuccess, bool mayDeferChecks)
435+
{
436+
assert(srcType->getKind() == MetadataKind::Class);
437+
438+
// unboxFromSwiftValueWithType is really just a recursive casting operation...
439+
if (swift_unboxFromSwiftValueWithType(srcValue, destLocation, destType)) {
440+
return DynamicCastResult::SuccessViaCopy;
441+
} else {
442+
return DynamicCastResult::Failure;
443+
}
444+
}
428445
#endif
429446

430447
/******************************************************************************/
@@ -1474,6 +1491,18 @@ tryCastToClassExistential(
14741491
}
14751492

14761493
case MetadataKind::ObjCClassWrapper:
1494+
#if SWIFT_OBJC_INTEROP
1495+
id srcObject;
1496+
memcpy(&srcObject, srcValue, sizeof(id));
1497+
if (!runtime::bincompat::useLegacySwiftValueUnboxingInCasting()) {
1498+
if (getAsSwiftValue(srcObject) != nullptr) {
1499+
// Do not directly cast a `__SwiftValue` box
1500+
// Return failure so our caller will unwrap and try again
1501+
return DynamicCastResult::Failure;
1502+
}
1503+
}
1504+
#endif
1505+
SWIFT_FALLTHROUGH;
14771506
case MetadataKind::Class:
14781507
case MetadataKind::ForeignClass: {
14791508
auto srcObject = getNonNullSrcObject(srcValue, srcType, destType);
@@ -2285,8 +2314,11 @@ tryCast(
22852314
case MetadataKind::Class: {
22862315
#if !SWIFT_OBJC_INTEROP
22872316
// Try unwrapping native __SwiftValue implementation
2288-
if (swift_unboxFromSwiftValueWithType(srcValue, destLocation, destType)) {
2289-
return DynamicCastResult::SuccessViaCopy;
2317+
auto subcastResult = tryCastUnwrappingSwiftValueSource(
2318+
destLocation, destType, srcValue, srcType,
2319+
destFailureType, srcFailureType, takeOnSuccess, mayDeferChecks);
2320+
if (isSuccess(subcastResult)) {
2321+
return subcastResult;
22902322
}
22912323
#endif
22922324
break;

stdlib/public/runtime/SwiftObject.mm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#endif
2626
#include "llvm/ADT/StringRef.h"
2727
#include "swift/Basic/Lazy.h"
28+
#include "swift/Runtime/Bincompat.h"
2829
#include "swift/Runtime/Casting.h"
2930
#include "swift/Runtime/CustomRRABI.h"
3031
#include "swift/Runtime/Debug.h"
@@ -41,6 +42,7 @@
4142
#include "ErrorObject.h"
4243
#include "Private.h"
4344
#include "SwiftObject.h"
45+
#include "SwiftValue.h"
4446
#include "WeakReference.h"
4547
#if SWIFT_OBJC_INTEROP
4648
#include <dlfcn.h>
@@ -1269,6 +1271,12 @@ id swift_dynamicCastObjCProtocolUnconditional(id object,
12691271
id swift_dynamicCastObjCProtocolConditional(id object,
12701272
size_t numProtocols,
12711273
Protocol * const *protocols) {
1274+
if (!runtime::bincompat::useLegacySwiftValueUnboxingInCasting()) {
1275+
if (getAsSwiftValue(object) != nil) {
1276+
// SwiftValue wrapper never holds a class object
1277+
return nil;
1278+
}
1279+
}
12721280
for (size_t i = 0; i < numProtocols; ++i) {
12731281
if (![object conformsToProtocol:protocols[i]]) {
12741282
return nil;

test/Casting/Casts.swift

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -981,37 +981,41 @@ CastsTests.test("Recursive AnyHashable") {
981981
// https://github.com/apple/swift/issues/56987
982982
#if _runtime(_ObjC)
983983
CastsTests.test("Do not overuse __SwiftValue")
984-
.skip(.osxAny("Not yet fully enabled for Apple OSes"))
985-
.skip(.iOSAny("Not yet fully enabled for Apple OSes"))
986-
.skip(.iOSSimulatorAny("Not yet fully enabled for Apple OSes"))
987-
.skip(.tvOSAny("Not yet fully enabled for Apple OSes"))
988-
.skip(.tvOSSimulatorAny("Not yet fully enabled for Apple OSes"))
989-
.skip(.watchOSAny("Not yet fully enabled for Apple OSes"))
990-
.skip(.watchOSSimulatorAny("Not yet fully enabled for Apple OSes"))
984+
.skip(.custom({
985+
if #available(SwiftStdlib 5.9, *) { return false } else { return true }
986+
}, reason: "Requires stdlib from Swift 5.9 or later"))
991987
.code {
992988
struct Bar {}
993989
// This used to succeed because of overeager __SwiftValue
994990
// boxing (and __SwiftValue does satisfy NSCopying)
995991
expectFalse(Bar() is NSCopying)
992+
expectNil(runtimeCast(Bar(), to: NSCopying.self))
996993
expectFalse(Bar() as Any is NSCopying)
994+
expectNil(runtimeCast(Bar() as Any, to: NSCopying.self))
997995

998-
// This seems unavoidable?
999-
// `Bar() as! AnyObject` gets boxed as a __SwiftValue,
1000-
// and __SwiftValue does conform to NSCopying
1001-
expectTrue(Bar() as! AnyObject is NSCopying)
996+
// `Bar() as! AnyObject` gets boxed as a __SwiftValue.
997+
// __SwiftValue does conform to NSCopying, but that should
998+
// not be visible here.
999+
let anyBar = Bar() as! AnyObject
1000+
expectNil(runtimeCast(anyBar, to: NSCopying.self))
1001+
expectFalse(anyBar is NSCopying)
10021002

10031003
class Foo {}
10041004
// Foo does not conform to NSCopying
10051005
// (This used to succeed due to over-eager __SwiftValue boxing)
10061006
expectFalse(Foo() is NSCopying)
1007+
expectNil(runtimeCast(Foo(), to: NSCopying.self))
10071008
expectFalse(Foo() as Any is NSCopying)
1009+
expectNil(runtimeCast(Foo() as Any, to: NSCopying.self))
10081010

10091011
// A type that really does conform should cast to NSCopying
10101012
class Foo2: NSCopying {
10111013
func copy(with: NSZone?) -> Any { return self }
10121014
}
10131015
expectTrue(Foo2() is NSCopying)
1016+
expectNotNil(runtimeCast(Foo2(), to: NSCopying.self))
10141017
expectTrue(Foo2() is AnyObject)
1018+
expectNotNil(runtimeCast(Foo2(), to: AnyObject.self))
10151019
}
10161020
#endif
10171021

@@ -1030,10 +1034,21 @@ CastsTests.test("Do not overuse __SwiftValue (non-ObjC)") {
10301034
// This should succeed because this is what __SwiftValue boxing is for
10311035
expectTrue(Bar() is AnyObject)
10321036
expectTrue(Bar() as Any is AnyObject)
1037+
let a = Bar() as Any as! AnyObject
1038+
expectTrue(a is Bar)
10331039

10341040
class Foo {}
10351041
// Any class type can be cast to AnyObject
10361042
expectTrue(Foo() is AnyObject)
1043+
let b = Foo() as! AnyObject
1044+
expectTrue(b is Foo)
1045+
1046+
// As above, but force use of runtime casting
1047+
expectNotNil(runtimeCast(Bar(), to: AnyObject.self))
1048+
expectNotNil(runtimeCast(Bar() as Any, to: AnyObject.self))
1049+
expectNotNil(runtimeCast(a, to: Bar.self))
1050+
expectNotNil(runtimeCast(Foo(), to: AnyObject.self))
1051+
expectNotNil(runtimeCast(b, to: Foo.self))
10371052
}
10381053

10391054
runAllTests()

0 commit comments

Comments
 (0)