Skip to content

Commit d92f1d5

Browse files
authored
Dynamic Casting: Properly unwrap existential metatype sources (#34469)
* Dynamic Casting: Properly unwrap existential metatype sources Existential metatypes are really just existentials that hold metatypes. As such, they should be handled in the general casting logic in much the same way as regular existentials: They should generally be ignored by most casting logic, and unwrapped as necessary at the top level. In particular, the previous code would fail to correctly handle the following cast from an existential metatype (`AnyObject.Type`) to an existential (`AnyObject`): ``` class C {} let a = C.self as AnyObject.Type let b = a as! AnyObject ``` With the old code, `b` above would hold a reference to a `__SwiftValue` box containing the type reference. The correct result would simply store the type reference directly in `b`. These two are only really distinguishable in that the correct form permits `a === b` to return `true`. Fixes rdar://70582753 Note: This is not yet fully supported on Linux. Basically, metatypes on Linux are not currently fully compatible with reference-counted class pointers, which prevents us from fully supporting metatype operations on Linux that we support on macOS.
1 parent e684d43 commit d92f1d5

File tree

6 files changed

+173
-4
lines changed

6 files changed

+173
-4
lines changed

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,6 +1496,9 @@ tryCastUnwrappingExistentialSource(
14961496
const Metadata *&destFailureType, const Metadata *&srcFailureType,
14971497
bool takeOnSuccess, bool mayDeferChecks)
14981498
{
1499+
assert(srcType != destType);
1500+
assert(srcType->getKind() == MetadataKind::Existential);
1501+
14991502
auto srcExistentialType = cast<ExistentialTypeMetadata>(srcType);
15001503

15011504
// Unpack the existential content
@@ -1535,6 +1538,29 @@ tryCastUnwrappingExistentialSource(
15351538
mayDeferChecks);
15361539
}
15371540

1541+
static DynamicCastResult
1542+
tryCastUnwrappingExistentialMetatypeSource(
1543+
OpaqueValue *destLocation, const Metadata *destType,
1544+
OpaqueValue *srcValue, const Metadata *srcType,
1545+
const Metadata *&destFailureType, const Metadata *&srcFailureType,
1546+
bool takeOnSuccess, bool mayDeferChecks)
1547+
{
1548+
assert(srcType != destType);
1549+
assert(srcType->getKind() == MetadataKind::ExistentialMetatype);
1550+
1551+
auto srcExistentialContainer = reinterpret_cast<ExistentialMetatypeContainer *>(srcValue);
1552+
auto srcInnerValue = reinterpret_cast<OpaqueValue *>(&srcExistentialContainer->Value);
1553+
assert((const void *)srcInnerValue == (const void *)srcValue);
1554+
auto srcInnerValueAsType = srcExistentialContainer->Value;
1555+
const Metadata *srcInnerType = swift_getMetatypeMetadata(srcInnerValueAsType);
1556+
srcFailureType = srcInnerType;
1557+
return tryCast(destLocation, destType,
1558+
srcInnerValue, srcInnerType,
1559+
destFailureType, srcFailureType,
1560+
takeOnSuccess & (srcInnerValue == srcValue),
1561+
mayDeferChecks);
1562+
}
1563+
15381564
/******************************************************************************/
15391565
/**************************** Opaque Destination ******************************/
15401566
/******************************************************************************/
@@ -1591,8 +1617,7 @@ tryCastToMetatype(
15911617
const MetatypeMetadata *destMetatypeType = cast<MetatypeMetadata>(destType);
15921618
MetadataKind srcKind = srcType->getKind();
15931619
switch (srcKind) {
1594-
case MetadataKind::Metatype:
1595-
case MetadataKind::ExistentialMetatype: {
1620+
case MetadataKind::Metatype: {
15961621
const Metadata *srcMetatype = *(const Metadata * const *) srcValue;
15971622
if (auto result = swift_dynamicCastMetatype(
15981623
srcMetatype, destMetatypeType->InstanceType)) {
@@ -1702,8 +1727,7 @@ tryCastToExistentialMetatype(
17021727
= cast<ExistentialMetatypeMetadata>(destType);
17031728
MetadataKind srcKind = srcType->getKind();
17041729
switch (srcKind) {
1705-
case MetadataKind::Metatype: // Metatype => ExistentialMetatype
1706-
case MetadataKind::ExistentialMetatype: { // ExistentialMetatype => ExistentialMetatype
1730+
case MetadataKind::Metatype: { // Metatype => ExistentialMetatype
17071731
const Metadata *srcMetatype = *(const Metadata * const *) srcValue;
17081732
return _dynamicCastMetatypeToExistentialMetatype(
17091733
destLocation,
@@ -1957,6 +1981,16 @@ tryCast(
19571981
break;
19581982
}
19591983

1984+
case MetadataKind::ExistentialMetatype: {
1985+
auto subcastResult = tryCastUnwrappingExistentialMetatypeSource(
1986+
destLocation, destType, srcValue, srcType,
1987+
destFailureType, srcFailureType, takeOnSuccess, mayDeferChecks);
1988+
if (isSuccess(subcastResult)) {
1989+
return subcastResult;
1990+
}
1991+
break;
1992+
}
1993+
19601994
default:
19611995
break;
19621996
}

test/Casting/Casts.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,4 +764,35 @@ CastsTests.test("Optional nil -> AnyHashable") {
764764
expectNotNil(a as? AnyHashable)
765765
}
766766

767+
#if _runtime(_ObjC)
768+
// See below for notes about missing Linux functionality
769+
// that prevents us from running this test there.
770+
CastsTests.test("AnyObject.Type -> AnyObject") {
771+
class C {}
772+
let a = C.self
773+
let b = a as? AnyObject.Type
774+
expectNotNil(b)
775+
// Note: On macOS, the following cast generates a call to
776+
// `swift_dynamicCastMetatypeToObjectConditional` That function is currently
777+
// unimplemented on Linux, so this cast always fails on Linux.
778+
let c = b as? AnyObject
779+
expectNotNil(c)
780+
// Note: The following cast currently succeeds on Linux only by stuffing the
781+
// source into a `__SwiftValue` container, which breaks the checks below.
782+
let d = runtimeCast(b, to: AnyObject.self)
783+
expectNotNil(d)
784+
let e = c as? C.Type
785+
expectNotNil(e)
786+
let f = runtimeCast(d, to: C.Type.self)
787+
expectNotNil(f)
788+
// Verify that the round-trip casts yield exactly the same pointer. In
789+
// particular, none of the casts above should fall back on stuffing the source
790+
// into a `__SwiftValue` container.
791+
expectTrue(c! === a)
792+
expectTrue(d! === a)
793+
expectTrue(e! === a)
794+
expectTrue(f! === a)
795+
}
796+
#endif
797+
767798
runAllTests()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#import <Foundation/Foundation.h>
2+
3+
NS_ASSUME_NONNULL_BEGIN
4+
5+
@interface OCClassConstants : NSObject
6+
@property (class, readonly, copy) NSArray<Class> *classes;
7+
@end
8+
9+
NS_ASSUME_NONNULL_END
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#import <Foundation/Foundation.h>
2+
#import "ObjCClassConstants.h"
3+
4+
NS_ASSUME_NONNULL_BEGIN
5+
6+
@implementation OCClassConstants
7+
8+
+ (NSArray<Class> *)classes { return @[OCClassConstants.class]; }
9+
10+
@end
11+
12+
NS_ASSUME_NONNULL_END
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module ObjCClassConstants {
2+
header "ObjCClassConstants.h"
3+
}

test/Casting/ObjCClassConstants.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// ObjCClassConstants.swift - Tests for class constant casts w/ Obj-C
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
// -----------------------------------------------------------------------------
12+
///
13+
/// Contains tests for non-trapping type conversions reported by users.
14+
///
15+
// -----------------------------------------------------------------------------
16+
// RUN: %empty-directory(%t)
17+
//
18+
// RUN: %target-clang -fmodules -c %S/Inputs/ObjCClassConstants/ObjCClassConstants.m -o %t/ObjCClassConstants.objc.o
19+
//
20+
// RUN: %target-build-swift -swift-version 5 -g -Onone -module-name a -I %S/Inputs/ObjCClassConstants -c %s -o %t/ObjCClassConstants.swift.Onone.o
21+
// RUN: %target-swiftc_driver %t/ObjCClassConstants.objc.o %t/ObjCClassConstants.swift.Onone.o -o %t/a.swift5.Onone.out
22+
// RUN: %target-codesign %t/a.swift5.Onone.out
23+
// RUN: %target-run %t/a.swift5.Onone.out
24+
//
25+
// RUN: %target-build-swift -swift-version 5 -g -O -module-name a -I %S/Inputs/ObjCClassConstants -c %s -o %t/ObjCClassConstants.swift.O.o
26+
// RUN: %target-swiftc_driver %t/ObjCClassConstants.objc.o %t/ObjCClassConstants.swift.O.o -o %t/a.swift5.O.out
27+
// RUN: %target-codesign %t/a.swift5.O.out
28+
// RUN: %target-run %t/a.swift5.O.out
29+
//
30+
// REQUIRES: executable_test
31+
// REQUIRES: objc_interop
32+
// UNSUPPORTED: use_os_stdlib
33+
34+
import Foundation
35+
import ObjCClassConstants
36+
import Swift
37+
38+
import StdlibUnittest
39+
40+
let tests = TestSuite("ObjCClassConstants")
41+
42+
tests.test("ObjC and Swift type lookups should agree") {
43+
// Look up class object from Obj-C (in an array)
44+
// Extract first element, then cast to AnyObject
45+
let a = OCClassConstants.classes
46+
let b = a[0]
47+
let c = b as? AnyObject
48+
expectNotNil(c)
49+
let d = c!
50+
51+
// Look up class object from Swift, cast to AnyObject
52+
let e = OCClassConstants.self
53+
let f = e as? AnyObject
54+
expectNotNil(f)
55+
let g = f!
56+
57+
// Should be exact same pointer
58+
expectTrue(d === g)
59+
}
60+
61+
tests.test("ObjC and Swift type lookups should agree (with array cast to AnyObject)") {
62+
// Look up class object from Obj-C (in an array)
63+
// Cast array to AnyObject, then extract first element
64+
let a = OCClassConstants.classes
65+
let b = a as? [AnyObject]
66+
expectNotNil(b)
67+
let c = b!
68+
let d = c[0]
69+
70+
// Look up class object from Swift, cast to AnyObject
71+
let e = OCClassConstants.self
72+
let f = e as? AnyObject
73+
expectNotNil(f)
74+
let g = f!
75+
76+
// Should be exact same pointer
77+
expectTrue(d === g)
78+
}
79+
80+
runAllTests()

0 commit comments

Comments
 (0)