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:
tbkka
2020-10-29 14:46:10 -07:00
committed by GitHub
parent e684d430cc
commit d92f1d58f8
6 changed files with 173 additions and 4 deletions

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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

View 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

View File

@@ -0,0 +1,3 @@
module ObjCClassConstants {
header "ObjCClassConstants.h"
}

View 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()