mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
NCGenerics: add availability checking
Not all runtimes can correctly operate with types that use noncopyable generics. When the generic argument of a type is noncopyable, old runtimes can't recognize that to correctly check conformances that may be conditional on those arguments being Copyable, etc. resolves rdar://126239335
This commit is contained in:
@@ -6737,6 +6737,16 @@ ERROR(availability_isolated_any_only_version_newer, none,
|
|||||||
"%0 %1 or newer",
|
"%0 %1 or newer",
|
||||||
(StringRef, llvm::VersionTuple))
|
(StringRef, llvm::VersionTuple))
|
||||||
|
|
||||||
|
ERROR(availability_copyable_generics_casting_only_version_newer, none,
|
||||||
|
"runtime support for casting types with noncopyable generic arguments "
|
||||||
|
"is only available in %0 %1 or newer",
|
||||||
|
(StringRef, llvm::VersionTuple))
|
||||||
|
|
||||||
|
ERROR(availability_escapable_generics_casting_only_version_newer, none,
|
||||||
|
"runtime support for casting types with nonescapable generic arguments "
|
||||||
|
"is only available in %0 %1 or newer",
|
||||||
|
(StringRef, llvm::VersionTuple))
|
||||||
|
|
||||||
ERROR(availability_typed_throws_only_version_newer, none,
|
ERROR(availability_typed_throws_only_version_newer, none,
|
||||||
"runtime support for typed throws function types is only available in "
|
"runtime support for typed throws function types is only available in "
|
||||||
"%0 %1 or newer",
|
"%0 %1 or newer",
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ FEATURE(SwiftExceptionPersonality, (6, 0))
|
|||||||
// Metadata support for @isolated(any) function types
|
// Metadata support for @isolated(any) function types
|
||||||
FEATURE(IsolatedAny, (6, 0))
|
FEATURE(IsolatedAny, (6, 0))
|
||||||
|
|
||||||
|
FEATURE(NoncopyableGenerics, (6, 0))
|
||||||
|
|
||||||
FEATURE(TaskExecutor, FUTURE)
|
FEATURE(TaskExecutor, FUTURE)
|
||||||
FEATURE(Differentiation, FUTURE)
|
FEATURE(Differentiation, FUTURE)
|
||||||
FEATURE(InitRawStructMetadata, FUTURE)
|
FEATURE(InitRawStructMetadata, FUTURE)
|
||||||
|
|||||||
@@ -3032,6 +3032,83 @@ static bool diagnoseTypedThrowsAvailability(
|
|||||||
ReferenceDC);
|
ReferenceDC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make sure the generic arguments conform to all known invertible protocols.
|
||||||
|
/// Runtimes prior to NoncopyableGenerics do not check if any of the
|
||||||
|
/// generic arguments conform to Copyable/Escapable during dynamic casts.
|
||||||
|
/// But a dynamic cast *needs* to check if the generic arguments conform,
|
||||||
|
/// to determine if the cast should be permitted at all. For example:
|
||||||
|
///
|
||||||
|
/// struct X<T> {}
|
||||||
|
/// extension X: P where T: Y {}
|
||||||
|
///
|
||||||
|
/// func f<Y: ~Copyable>(...) {
|
||||||
|
/// let x: X<Y> = ...
|
||||||
|
/// _ = x as? any P // <- cast should fail
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// The dynamic cast here must fail because Y does not conform to Copyable,
|
||||||
|
/// thus X<Y> doesn't conform to P!
|
||||||
|
///
|
||||||
|
/// \param boundTy The generic type with its generic arguments.
|
||||||
|
/// \returns the invertible protocol for which a conformance is missing in
|
||||||
|
/// one of the generic arguments, or none if all are present for
|
||||||
|
/// every generic argument.
|
||||||
|
static std::optional<InvertibleProtocolKind> checkGenericArgsForInvertibleReqs(
|
||||||
|
BoundGenericType *boundTy) {
|
||||||
|
for (auto arg : boundTy->getGenericArgs()) {
|
||||||
|
for (auto ip : InvertibleProtocolSet::allKnown()) {
|
||||||
|
switch (ip) {
|
||||||
|
case InvertibleProtocolKind::Copyable:
|
||||||
|
if (arg->isNoncopyable())
|
||||||
|
return ip;
|
||||||
|
break;
|
||||||
|
case InvertibleProtocolKind::Escapable:
|
||||||
|
if (!arg->isEscapable())
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Older runtimes won't check for required invertible protocol conformances
|
||||||
|
/// at runtime during a cast.
|
||||||
|
///
|
||||||
|
/// \param srcType the source or initial type of the cast
|
||||||
|
/// \param refLoc source location of the cast
|
||||||
|
/// \param refDC decl context in which the cast occurs
|
||||||
|
/// \return true if diagnosed
|
||||||
|
static bool checkInverseGenericsCastingAvailability(Type srcType,
|
||||||
|
SourceRange refLoc,
|
||||||
|
const DeclContext *refDC) {
|
||||||
|
if (!srcType) return false;
|
||||||
|
|
||||||
|
auto type = srcType->getCanonicalType();
|
||||||
|
|
||||||
|
if (auto boundTy = dyn_cast<BoundGenericType>(type)) {
|
||||||
|
if (auto missing = checkGenericArgsForInvertibleReqs(boundTy)) {
|
||||||
|
std::optional<Diag<StringRef, llvm::VersionTuple>> diag;
|
||||||
|
switch (*missing) {
|
||||||
|
case InvertibleProtocolKind::Copyable:
|
||||||
|
diag =
|
||||||
|
diag::availability_copyable_generics_casting_only_version_newer;
|
||||||
|
break;
|
||||||
|
case InvertibleProtocolKind::Escapable:
|
||||||
|
diag =
|
||||||
|
diag::availability_escapable_generics_casting_only_version_newer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce the availability restriction.
|
||||||
|
return TypeChecker::checkAvailability(
|
||||||
|
refLoc,
|
||||||
|
refDC->getASTContext().getNoncopyableGenericsAvailability(),
|
||||||
|
*diag,
|
||||||
|
refDC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool checkTypeMetadataAvailabilityInternal(CanType type,
|
static bool checkTypeMetadataAvailabilityInternal(CanType type,
|
||||||
SourceRange refLoc,
|
SourceRange refLoc,
|
||||||
const DeclContext *refDC) {
|
const DeclContext *refDC) {
|
||||||
@@ -3075,7 +3152,13 @@ static bool checkTypeMetadataAvailabilityForConverted(Type refType,
|
|||||||
// there.
|
// there.
|
||||||
if (type.isAnyExistentialType()) return false;
|
if (type.isAnyExistentialType()) return false;
|
||||||
|
|
||||||
return checkTypeMetadataAvailabilityInternal(type, refLoc, refDC);
|
if (checkTypeMetadataAvailabilityInternal(type, refLoc, refDC))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (checkInverseGenericsCastingAvailability(type, refLoc, refDC))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -3476,6 +3559,9 @@ public:
|
|||||||
if (auto EE = dyn_cast<ErasureExpr>(E)) {
|
if (auto EE = dyn_cast<ErasureExpr>(E)) {
|
||||||
checkTypeMetadataAvailability(EE->getSubExpr()->getType(),
|
checkTypeMetadataAvailability(EE->getSubExpr()->getType(),
|
||||||
EE->getLoc(), Where.getDeclContext());
|
EE->getLoc(), Where.getDeclContext());
|
||||||
|
checkInverseGenericsCastingAvailability(EE->getSubExpr()->getType(),
|
||||||
|
EE->getLoc(),
|
||||||
|
Where.getDeclContext());
|
||||||
|
|
||||||
for (ProtocolConformanceRef C : EE->getConformances()) {
|
for (ProtocolConformanceRef C : EE->getConformances()) {
|
||||||
diagnoseConformanceAvailability(E->getLoc(), C, Where, Type(), Type(),
|
diagnoseConformanceAvailability(E->getLoc(), C, Where, Type(), Type(),
|
||||||
|
|||||||
129
test/Generics/inverse_casting_availability.swift
Normal file
129
test/Generics/inverse_casting_availability.swift
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift \
|
||||||
|
// RUN: -debug-diagnostic-names -target arm64-apple-macos14.4 \
|
||||||
|
// RUN: -enable-experimental-feature NonescapableTypes
|
||||||
|
|
||||||
|
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchOS || OS=xros
|
||||||
|
|
||||||
|
protocol P {}
|
||||||
|
struct NCG<T: ~Copyable> {}
|
||||||
|
extension NCG: P where T: Copyable {} // expected-note 2{{requirement_implied_by_conditional_conformance}}
|
||||||
|
|
||||||
|
struct NEG<T: ~Escapable> {}
|
||||||
|
extension NEG: P {} // expected-note {{requirement_implied_by_conditional_conformance}}
|
||||||
|
|
||||||
|
struct All {}
|
||||||
|
struct NoCopy: ~Copyable {}
|
||||||
|
struct NoEscape: ~Escapable {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// MARK: dynamic casts are gated by availability. Older runtimes don't check
|
||||||
|
/// for conformance to Copyable so they'll give bogus results.
|
||||||
|
|
||||||
|
// expected-note@+1 8{{availability_add_attribute}}
|
||||||
|
func dyn_cast_errors<T: ~Copyable, V: ~Escapable>(
|
||||||
|
_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
|
||||||
|
_ genericEsc: NEG<V>, _ concreteEsc: NEG<NoEscape>) {
|
||||||
|
_ = concrete as? any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_ = generic as? any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
_ = concrete is any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_ = generic is any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
_ = concrete as! any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_ = generic as! any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
_ = genericEsc as? any P // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_ = concreteEsc is any P // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(SwiftStdlib 6.0, *)
|
||||||
|
func FIXED_dyn_cast_errors<T: ~Copyable, V: ~Escapable>(
|
||||||
|
_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
|
||||||
|
_ genericEsc: NEG<V>, _ concreteEsc: NEG<NoEscape>) {
|
||||||
|
_ = concrete as? any P
|
||||||
|
_ = generic as? any P
|
||||||
|
|
||||||
|
_ = concrete is any P
|
||||||
|
_ = generic is any P
|
||||||
|
|
||||||
|
_ = concrete as! any P
|
||||||
|
_ = generic as! any P
|
||||||
|
|
||||||
|
_ = genericEsc as? any P
|
||||||
|
_ = concreteEsc is any P
|
||||||
|
}
|
||||||
|
|
||||||
|
func noAvailabilityNeeded<T>(_ generic: NCG<T>, _ concrete: NCG<All>) {
|
||||||
|
_ = concrete as? any P // expected-warning {{conditional_downcast_coercion}}
|
||||||
|
_ = generic as? any P // expected-warning {{conditional_downcast_coercion}}
|
||||||
|
|
||||||
|
_ = concrete is any P // expected-warning {{isa_is_always_true}}
|
||||||
|
_ = generic is any P // expected-warning {{isa_is_always_true}}
|
||||||
|
|
||||||
|
_ = concrete as! any P // expected-warning {{forced_downcast_coercion}}
|
||||||
|
_ = generic as! any P // expected-warning {{forced_downcast_coercion}}
|
||||||
|
|
||||||
|
_ = concrete as any P
|
||||||
|
_ = generic as any P
|
||||||
|
|
||||||
|
_ = concrete as Any
|
||||||
|
_ = generic as Any
|
||||||
|
}
|
||||||
|
|
||||||
|
func expected_checked_cast_errors<T: ~Copyable>(_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
|
||||||
|
_ concreteEsc: NEG<NoEscape>) {
|
||||||
|
_ = concrete as any P // expected-error {{type_does_not_conform_decl_owner}}
|
||||||
|
_ = generic as any P // expected-error {{type_does_not_conform_decl_owner}}
|
||||||
|
_ = concreteEsc as any P // expected-error {{type_does_not_conform_decl_owner}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MARK: existential erasure requires availability, because later dynamic casts
|
||||||
|
/// of that erased type will not correctly check for Copyable generic args.
|
||||||
|
|
||||||
|
func eraseImplicit(_ a: Any) {}
|
||||||
|
|
||||||
|
// expected-note@+1 9{{availability_add_attribute}}
|
||||||
|
func erasure_cast_disallowed<T: ~Copyable>(_ generic: NCG<T>, _ concrete: NCG<NoCopy>, _ concreteEsc: NEG<NoEscape>) -> Any {
|
||||||
|
_ = concrete as Any // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_ = concreteEsc as Any // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_ = generic as Any // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
let _: Any = concrete // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
let _: Any = generic // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
let _: Any = { concreteEsc }() // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
eraseImplicit(concrete) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
eraseImplicit(generic) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
|
||||||
|
return concrete // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Box<Wrapped: ~Copyable>: ~Copyable { // expected-note {{availability_add_attribute}}
|
||||||
|
private let _pointer: UnsafeMutablePointer<Wrapped>
|
||||||
|
|
||||||
|
init(_ element: consuming Wrapped) { // expected-note {{availability_add_attribute}}
|
||||||
|
_pointer = .allocate(capacity: 1)
|
||||||
|
print("allocating",_pointer) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||||
|
_pointer.initialize(to: element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MARK: misc. operations that are permitted
|
||||||
|
|
||||||
|
public protocol Debatable: ~Copyable {}
|
||||||
|
|
||||||
|
public func asExistential(_ t: consuming any Debatable & ~Copyable) {}
|
||||||
|
|
||||||
|
public func hello<T: Debatable & ~Copyable>(_ t: consuming T) {
|
||||||
|
asExistential(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UnsafeMutableRawPointer {
|
||||||
|
public func blahInitializeMemory<T: ~Copyable>(
|
||||||
|
as type: T.Type, from source: UnsafeMutablePointer<T>, count: Int
|
||||||
|
) {
|
||||||
|
_ = UnsafeMutableRawPointer(source + count)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all) | %FileCheck %s
|
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all -Xfrontend -disable-availability-checking) | %FileCheck %s
|
||||||
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all) | %FileCheck %s
|
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all -Xfrontend -disable-availability-checking) | %FileCheck %s
|
||||||
|
|
||||||
// REQUIRES: executable_test
|
// REQUIRES: executable_test
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil -verify %s
|
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil -verify %s -disable-availability-checking
|
||||||
|
|
||||||
extension List {
|
extension List {
|
||||||
var peek: Element {
|
var peek: Element {
|
||||||
@@ -13,30 +13,8 @@ extension List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MyPointer<Wrapped: ~Copyable>: Copyable {
|
|
||||||
var v: UnsafeMutablePointer<Int>
|
|
||||||
|
|
||||||
static func allocate(capacity: Int) -> Self {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initialize(to: consuming Wrapped) {
|
|
||||||
}
|
|
||||||
func deinitialize(count: Int) {
|
|
||||||
}
|
|
||||||
func deallocate() {
|
|
||||||
}
|
|
||||||
func move() -> Wrapped {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
var pointee: Wrapped {
|
|
||||||
_read { fatalError() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Box<Wrapped: ~Copyable>: ~Copyable {
|
struct Box<Wrapped: ~Copyable>: ~Copyable {
|
||||||
private let _pointer: MyPointer<Wrapped>
|
private let _pointer: UnsafeMutablePointer<Wrapped>
|
||||||
|
|
||||||
init(_ element: consuming Wrapped) {
|
init(_ element: consuming Wrapped) {
|
||||||
_pointer = .allocate(capacity: 1)
|
_pointer = .allocate(capacity: 1)
|
||||||
|
|||||||
@@ -1,27 +1,7 @@
|
|||||||
// RUN: %target-swift-frontend -emit-sil -verify %s
|
// RUN: %target-swift-frontend -emit-sil -verify %s -disable-availability-checking
|
||||||
|
|
||||||
// TODO: Remove this and just use the real `UnsafeMutablePointer` when
|
|
||||||
// noncopyable type support has been upstreamed.
|
|
||||||
struct MyPointer<Wrapped: ~Copyable>: Copyable {
|
|
||||||
var v: UnsafeMutablePointer<Int>
|
|
||||||
|
|
||||||
static func allocate(capacity: Int) -> Self {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initialize(to: consuming Wrapped) {
|
|
||||||
}
|
|
||||||
func deinitialize(count: Int) {
|
|
||||||
}
|
|
||||||
func deallocate() {
|
|
||||||
}
|
|
||||||
func move() -> Wrapped {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Box<Wrapped: ~Copyable>: ~Copyable {
|
struct Box<Wrapped: ~Copyable>: ~Copyable {
|
||||||
private let _pointer: MyPointer<Wrapped>
|
private let _pointer: UnsafeMutablePointer<Wrapped>
|
||||||
|
|
||||||
init(_ element: consuming Wrapped) {
|
init(_ element: consuming Wrapped) {
|
||||||
_pointer = .allocate(capacity: 1)
|
_pointer = .allocate(capacity: 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user