mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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.
This commit is contained in:
@@ -1496,6 +1496,9 @@ tryCastUnwrappingExistentialSource(
|
||||
const Metadata *&destFailureType, const Metadata *&srcFailureType,
|
||||
bool takeOnSuccess, bool mayDeferChecks)
|
||||
{
|
||||
assert(srcType != destType);
|
||||
assert(srcType->getKind() == MetadataKind::Existential);
|
||||
|
||||
auto srcExistentialType = cast<ExistentialTypeMetadata>(srcType);
|
||||
|
||||
// Unpack the existential content
|
||||
@@ -1535,6 +1538,29 @@ tryCastUnwrappingExistentialSource(
|
||||
mayDeferChecks);
|
||||
}
|
||||
|
||||
static DynamicCastResult
|
||||
tryCastUnwrappingExistentialMetatypeSource(
|
||||
OpaqueValue *destLocation, const Metadata *destType,
|
||||
OpaqueValue *srcValue, const Metadata *srcType,
|
||||
const Metadata *&destFailureType, const Metadata *&srcFailureType,
|
||||
bool takeOnSuccess, bool mayDeferChecks)
|
||||
{
|
||||
assert(srcType != destType);
|
||||
assert(srcType->getKind() == MetadataKind::ExistentialMetatype);
|
||||
|
||||
auto srcExistentialContainer = reinterpret_cast<ExistentialMetatypeContainer *>(srcValue);
|
||||
auto srcInnerValue = reinterpret_cast<OpaqueValue *>(&srcExistentialContainer->Value);
|
||||
assert((const void *)srcInnerValue == (const void *)srcValue);
|
||||
auto srcInnerValueAsType = srcExistentialContainer->Value;
|
||||
const Metadata *srcInnerType = swift_getMetatypeMetadata(srcInnerValueAsType);
|
||||
srcFailureType = srcInnerType;
|
||||
return tryCast(destLocation, destType,
|
||||
srcInnerValue, srcInnerType,
|
||||
destFailureType, srcFailureType,
|
||||
takeOnSuccess & (srcInnerValue == srcValue),
|
||||
mayDeferChecks);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/**************************** Opaque Destination ******************************/
|
||||
/******************************************************************************/
|
||||
@@ -1591,8 +1617,7 @@ tryCastToMetatype(
|
||||
const MetatypeMetadata *destMetatypeType = cast<MetatypeMetadata>(destType);
|
||||
MetadataKind srcKind = srcType->getKind();
|
||||
switch (srcKind) {
|
||||
case MetadataKind::Metatype:
|
||||
case MetadataKind::ExistentialMetatype: {
|
||||
case MetadataKind::Metatype: {
|
||||
const Metadata *srcMetatype = *(const Metadata * const *) srcValue;
|
||||
if (auto result = swift_dynamicCastMetatype(
|
||||
srcMetatype, destMetatypeType->InstanceType)) {
|
||||
@@ -1702,8 +1727,7 @@ tryCastToExistentialMetatype(
|
||||
= cast<ExistentialMetatypeMetadata>(destType);
|
||||
MetadataKind srcKind = srcType->getKind();
|
||||
switch (srcKind) {
|
||||
case MetadataKind::Metatype: // Metatype => ExistentialMetatype
|
||||
case MetadataKind::ExistentialMetatype: { // ExistentialMetatype => ExistentialMetatype
|
||||
case MetadataKind::Metatype: { // Metatype => ExistentialMetatype
|
||||
const Metadata *srcMetatype = *(const Metadata * const *) srcValue;
|
||||
return _dynamicCastMetatypeToExistentialMetatype(
|
||||
destLocation,
|
||||
@@ -1957,6 +1981,16 @@ tryCast(
|
||||
break;
|
||||
}
|
||||
|
||||
case MetadataKind::ExistentialMetatype: {
|
||||
auto subcastResult = tryCastUnwrappingExistentialMetatypeSource(
|
||||
destLocation, destType, srcValue, srcType,
|
||||
destFailureType, srcFailureType, takeOnSuccess, mayDeferChecks);
|
||||
if (isSuccess(subcastResult)) {
|
||||
return subcastResult;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -764,4 +764,35 @@ CastsTests.test("Optional nil -> AnyHashable") {
|
||||
expectNotNil(a as? AnyHashable)
|
||||
}
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
// See below for notes about missing Linux functionality
|
||||
// that prevents us from running this test there.
|
||||
CastsTests.test("AnyObject.Type -> AnyObject") {
|
||||
class C {}
|
||||
let a = C.self
|
||||
let b = a as? AnyObject.Type
|
||||
expectNotNil(b)
|
||||
// Note: On macOS, the following cast generates a call to
|
||||
// `swift_dynamicCastMetatypeToObjectConditional` That function is currently
|
||||
// unimplemented on Linux, so this cast always fails on Linux.
|
||||
let c = b as? AnyObject
|
||||
expectNotNil(c)
|
||||
// Note: The following cast currently succeeds on Linux only by stuffing the
|
||||
// source into a `__SwiftValue` container, which breaks the checks below.
|
||||
let d = runtimeCast(b, to: AnyObject.self)
|
||||
expectNotNil(d)
|
||||
let e = c as? C.Type
|
||||
expectNotNil(e)
|
||||
let f = runtimeCast(d, to: C.Type.self)
|
||||
expectNotNil(f)
|
||||
// Verify that the round-trip casts yield exactly the same pointer. In
|
||||
// particular, none of the casts above should fall back on stuffing the source
|
||||
// into a `__SwiftValue` container.
|
||||
expectTrue(c! === a)
|
||||
expectTrue(d! === a)
|
||||
expectTrue(e! === a)
|
||||
expectTrue(f! === a)
|
||||
}
|
||||
#endif
|
||||
|
||||
runAllTests()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OCClassConstants : NSObject
|
||||
@property (class, readonly, copy) NSArray<Class> *classes;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
12
test/Casting/Inputs/ObjCClassConstants/ObjCClassConstants.m
Normal file
12
test/Casting/Inputs/ObjCClassConstants/ObjCClassConstants.m
Normal file
@@ -0,0 +1,12 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ObjCClassConstants.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation OCClassConstants
|
||||
|
||||
+ (NSArray<Class> *)classes { return @[OCClassConstants.class]; }
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
3
test/Casting/Inputs/ObjCClassConstants/module.map
Normal file
3
test/Casting/Inputs/ObjCClassConstants/module.map
Normal file
@@ -0,0 +1,3 @@
|
||||
module ObjCClassConstants {
|
||||
header "ObjCClassConstants.h"
|
||||
}
|
||||
80
test/Casting/ObjCClassConstants.swift
Normal file
80
test/Casting/ObjCClassConstants.swift
Normal file
@@ -0,0 +1,80 @@
|
||||
// ObjCClassConstants.swift - Tests for class constant casts w/ Obj-C
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Contains tests for non-trapping type conversions reported by users.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
// RUN: %empty-directory(%t)
|
||||
//
|
||||
// RUN: %target-clang -fmodules -c %S/Inputs/ObjCClassConstants/ObjCClassConstants.m -o %t/ObjCClassConstants.objc.o
|
||||
//
|
||||
// RUN: %target-build-swift -swift-version 5 -g -Onone -module-name a -I %S/Inputs/ObjCClassConstants -c %s -o %t/ObjCClassConstants.swift.Onone.o
|
||||
// RUN: %target-swiftc_driver %t/ObjCClassConstants.objc.o %t/ObjCClassConstants.swift.Onone.o -o %t/a.swift5.Onone.out
|
||||
// RUN: %target-codesign %t/a.swift5.Onone.out
|
||||
// RUN: %target-run %t/a.swift5.Onone.out
|
||||
//
|
||||
// RUN: %target-build-swift -swift-version 5 -g -O -module-name a -I %S/Inputs/ObjCClassConstants -c %s -o %t/ObjCClassConstants.swift.O.o
|
||||
// RUN: %target-swiftc_driver %t/ObjCClassConstants.objc.o %t/ObjCClassConstants.swift.O.o -o %t/a.swift5.O.out
|
||||
// RUN: %target-codesign %t/a.swift5.O.out
|
||||
// RUN: %target-run %t/a.swift5.O.out
|
||||
//
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: objc_interop
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
|
||||
import Foundation
|
||||
import ObjCClassConstants
|
||||
import Swift
|
||||
|
||||
import StdlibUnittest
|
||||
|
||||
let tests = TestSuite("ObjCClassConstants")
|
||||
|
||||
tests.test("ObjC and Swift type lookups should agree") {
|
||||
// Look up class object from Obj-C (in an array)
|
||||
// Extract first element, then cast to AnyObject
|
||||
let a = OCClassConstants.classes
|
||||
let b = a[0]
|
||||
let c = b as? AnyObject
|
||||
expectNotNil(c)
|
||||
let d = c!
|
||||
|
||||
// Look up class object from Swift, cast to AnyObject
|
||||
let e = OCClassConstants.self
|
||||
let f = e as? AnyObject
|
||||
expectNotNil(f)
|
||||
let g = f!
|
||||
|
||||
// Should be exact same pointer
|
||||
expectTrue(d === g)
|
||||
}
|
||||
|
||||
tests.test("ObjC and Swift type lookups should agree (with array cast to AnyObject)") {
|
||||
// Look up class object from Obj-C (in an array)
|
||||
// Cast array to AnyObject, then extract first element
|
||||
let a = OCClassConstants.classes
|
||||
let b = a as? [AnyObject]
|
||||
expectNotNil(b)
|
||||
let c = b!
|
||||
let d = c[0]
|
||||
|
||||
// Look up class object from Swift, cast to AnyObject
|
||||
let e = OCClassConstants.self
|
||||
let f = e as? AnyObject
|
||||
expectNotNil(f)
|
||||
let g = f!
|
||||
|
||||
// Should be exact same pointer
|
||||
expectTrue(d === g)
|
||||
}
|
||||
|
||||
runAllTests()
|
||||
Reference in New Issue
Block a user