mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[concurrency] Ban associated objects from being set on instances of actor classes that do not inherit from NSObject.
Associated objects are actively dangerous there because they’re non-isolated actor state, and it’s “new” code wher no backward compatibility concerns that make it more difficult to ban this on other forms of classes. rdar://69769048
This commit is contained in:
@@ -53,7 +53,12 @@ enum class ObjCClassFlags : uint32_t {
|
||||
/// This class provides a non-trivial .cxx_destruct method, but
|
||||
/// its .cxx_construct is trivial. For backwards compatibility,
|
||||
/// when setting this flag, HasCXXStructors must be set as well.
|
||||
HasCXXDestructorOnly = 0x00100
|
||||
HasCXXDestructorOnly = 0x00100,
|
||||
|
||||
/// This class does not allow associated objects on instances.
|
||||
///
|
||||
/// Will cause the objc runtime to trap in objc_setAssociatedObject.
|
||||
ForbidsAssociatedObjects = 0x00400,
|
||||
};
|
||||
inline ObjCClassFlags &operator|=(ObjCClassFlags &lhs, ObjCClassFlags rhs) {
|
||||
lhs = ObjCClassFlags(uint32_t(lhs) | uint32_t(rhs));
|
||||
|
||||
@@ -106,5 +106,10 @@ SEMANTICS_ATTR(KEYPATH_KVC_KEY_PATH_STRING, "keypath.kvcKeyPathString")
|
||||
/// consider inlining where to put these.
|
||||
SEMANTICS_ATTR(FORCE_EMIT_OPT_REMARK_PREFIX, "optremark")
|
||||
|
||||
/// An attribute that when attached to a class causes instances of the class to
|
||||
/// be forbidden from having associated objects set upon them. This is only used
|
||||
/// for testing purposes.
|
||||
SEMANTICS_ATTR(OBJC_FORBID_ASSOCIATED_OBJECTS, "objc.forbidAssociatedObjects")
|
||||
|
||||
#undef SEMANTICS_ATTR
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "swift/AST/Module.h"
|
||||
#include "swift/AST/Pattern.h"
|
||||
#include "swift/AST/PrettyStackTrace.h"
|
||||
#include "swift/AST/SemanticAttrs.h"
|
||||
#include "swift/AST/TypeMemberVisitor.h"
|
||||
#include "swift/AST/Types.h"
|
||||
#include "swift/ClangImporter/ClangModule.h"
|
||||
@@ -1443,6 +1444,32 @@ namespace {
|
||||
}
|
||||
|
||||
private:
|
||||
/// If we should set the forbids associated objects on instances metadata
|
||||
/// flag.
|
||||
///
|
||||
/// We currently do this on:
|
||||
///
|
||||
/// * Actor classes.
|
||||
/// * classes marked with @_semantics("objc.forbidAssociatedObjects")
|
||||
/// (for testing purposes)
|
||||
///
|
||||
/// TODO: Expand this as appropriate over time.
|
||||
bool doesClassForbidAssociatedObjectsOnInstances() const {
|
||||
auto *clsDecl = getClass();
|
||||
|
||||
// We ban this on actors without objc ancestry.
|
||||
if (clsDecl->isActor() && !clsDecl->checkAncestry(AncestryFlags::ObjC))
|
||||
return true;
|
||||
|
||||
// Otherwise, we only do it if our special semantics attribute is on the
|
||||
// relevant class. This is for testing purposes.
|
||||
if (clsDecl->hasSemanticsAttr(semantics::OBJC_FORBID_ASSOCIATED_OBJECTS))
|
||||
return true;
|
||||
|
||||
// TODO: Add new cases here as appropriate over time.
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjCClassFlags buildFlags(ForMetaClass_t forMeta,
|
||||
HasUpdateCallback_t hasUpdater) {
|
||||
ObjCClassFlags flags = ObjCClassFlags::CompiledByARC;
|
||||
@@ -1463,6 +1490,11 @@ namespace {
|
||||
if (hasUpdater)
|
||||
flags |= ObjCClassFlags::HasMetadataUpdateCallback;
|
||||
|
||||
// If we know that our class does not support having associated objects
|
||||
// placed upon instances, set the forbid associated object flag.
|
||||
if (doesClassForbidAssociatedObjectsOnInstances())
|
||||
flags |= ObjCClassFlags::ForbidsAssociatedObjects;
|
||||
|
||||
// FIXME: set ObjCClassFlags::Hidden when appropriate
|
||||
return flags;
|
||||
}
|
||||
|
||||
25
test/IRGen/actor_class_forbid_objc_assoc_objects.swift
Normal file
25
test/IRGen/actor_class_forbid_objc_assoc_objects.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
// RUN: %target-swift-frontend -enable-experimental-concurrency -emit-ir %s | %FileCheck %s
|
||||
|
||||
// REQUIRES: concurrency
|
||||
// REQUIRES: objc_interop
|
||||
|
||||
import _Concurrency
|
||||
|
||||
// CHECK: @_METACLASS_DATA__TtC37actor_class_forbid_objc_assoc_objects5Actor = internal constant { {{.*}} } { i32 [[METAFLAGS:1153]],
|
||||
// CHECK: @_DATA__TtC37actor_class_forbid_objc_assoc_objects5Actor = internal constant { {{.*}} } { i32 [[OBJECTFLAGS:1152|1216]],
|
||||
final actor class Actor {
|
||||
}
|
||||
|
||||
// CHECK: @_METACLASS_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor2 = internal constant { {{.*}} } { i32 [[METAFLAGS]],
|
||||
// CHECK: @_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor2 = internal constant { {{.*}} } { i32 [[OBJECTFLAGS]],
|
||||
actor class Actor2 {
|
||||
}
|
||||
|
||||
// CHECK: @_METACLASS_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor3 = internal constant { {{.*}} } { i32 [[METAFLAGS]],
|
||||
// CHECK: @_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor3 = internal constant { {{.*}} } { i32 [[OBJECTFLAGS]],
|
||||
class Actor3 : Actor2 {}
|
||||
|
||||
actor class GenericActor<T> {
|
||||
var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
79
test/IRGen/class_forbid_objc_assoc_objects.swift
Normal file
79
test/IRGen/class_forbid_objc_assoc_objects.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
// RUN: %target-swift-frontend -emit-ir %s | %FileCheck %s
|
||||
|
||||
// REQUIRES: objc_interop
|
||||
|
||||
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects24AllowedToHaveAssocObject = internal constant { {{.*}} } { i32 129,
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects24AllowedToHaveAssocObject = internal constant { {{.*}} } { i32 128,
|
||||
final class AllowedToHaveAssocObject {
|
||||
}
|
||||
|
||||
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects24UnableToHaveAssocObjects = internal constant { {{.*}} } { i32 1153,
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects24UnableToHaveAssocObjects = internal constant { {{.*}} } { i32 1152,
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class UnableToHaveAssocObjects {
|
||||
}
|
||||
|
||||
// Class Metadata For Generic Metadata
|
||||
//
|
||||
// CHECK: [[CLASS_METADATA:@[0-9][0-9]*]] = internal constant <{ {{.*}} }> <{ {{.*}} { i32 1152,
|
||||
//
|
||||
// Generic Metadata Pattern
|
||||
//
|
||||
// CHECK: @"$s31class_forbid_objc_assoc_objects31UnableToHaveAssocObjectsGenericCMP" = internal constant {{.*}}[[CLASS_METADATA]]
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class UnableToHaveAssocObjectsGeneric<T> {
|
||||
var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
// This should be normal.
|
||||
//
|
||||
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundAbleToHaveAssocObjectsParentClass = internal constant { {{.*}} } { i32 129,
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundAbleToHaveAssocObjectsParentClass = internal constant { {{.*}} } { i32 128,
|
||||
class UnsoundAbleToHaveAssocObjectsParentClass {
|
||||
}
|
||||
|
||||
// This should have assoc object constraints
|
||||
//
|
||||
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects39UnsoundUnableToHaveAssocObjectsSubClass = internal constant { {{.*}} } { i32 1153,
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects39UnsoundUnableToHaveAssocObjectsSubClass = internal constant { {{.*}} } { i32 1152,
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class UnsoundUnableToHaveAssocObjectsSubClass : UnsoundAbleToHaveAssocObjectsParentClass {
|
||||
}
|
||||
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects41UnsoundAbleToHaveAssocObjectsParentClass2 = internal constant { {{.*}} } { i32 1152,
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
class UnsoundAbleToHaveAssocObjectsParentClass2 {
|
||||
}
|
||||
|
||||
// This has normal metadata. We must at runtime add the flags of the subclass to
|
||||
// the child.
|
||||
//
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundUnableToHaveAssocObjectsSubClass2 = internal constant { {{.*}} } { i32 128,
|
||||
final class UnsoundUnableToHaveAssocObjectsSubClass2 : UnsoundAbleToHaveAssocObjectsParentClass2 {
|
||||
}
|
||||
|
||||
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundUnableToHaveAssocObjectsSubClass3 = internal constant { {{.*}} } { i32 128,
|
||||
class UnsoundUnableToHaveAssocObjectsSubClass3 : UnsoundAbleToHaveAssocObjectsParentClass2 {
|
||||
}
|
||||
|
||||
class GenericAbleToHaveAssocObjectsParentClass<T> {
|
||||
public var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class GenericUnableToHaveAssocObjectsSubClass<T> : GenericAbleToHaveAssocObjectsParentClass<T> {
|
||||
}
|
||||
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
class GenericAbleToHaveAssocObjectsParentClass2<T> {
|
||||
public var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
final class GenericUnableToHaveAssocObjectsSubClass2<T> : GenericAbleToHaveAssocObjectsParentClass2<T> {
|
||||
}
|
||||
|
||||
class GenericUnableToHaveAssocObjectsSubClass3<T> : GenericAbleToHaveAssocObjectsParentClass2<T> {
|
||||
}
|
||||
190
test/Interpreter/actor_class_forbid_objc_assoc_objects.swift
Normal file
190
test/Interpreter/actor_class_forbid_objc_assoc_objects.swift
Normal file
@@ -0,0 +1,190 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-swiftc_driver -Xfrontend -enable-experimental-concurrency %s -o %t/out
|
||||
// RUN: %target-run %t/out
|
||||
|
||||
// REQUIRES: concurrency
|
||||
// REQUIRES: objc_interop
|
||||
// REQUIRES: executable_test
|
||||
|
||||
import ObjectiveC
|
||||
import _Concurrency
|
||||
import StdlibUnittest
|
||||
|
||||
defer { runAllTests() }
|
||||
|
||||
var Tests = TestSuite("Actor.AssocObject")
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
final actor class Actor {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("final class crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
actor class Actor2 {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("non-final class crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor2()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class Actor3 : Actor2 {}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("non-final subclass crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor3()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
final class Actor3Final : Actor2 {}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("final subclass crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor3Final()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class Actor4<T> : Actor2 {
|
||||
var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("generic subclass crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor4(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
actor class Actor5<T> {
|
||||
var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("base generic class crash when set assoc object")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor5(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
Tests.test("base generic class metatype crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor5<Int>.self
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class Actor6<T> : Actor5<T> {
|
||||
override init(state: T) { super.init(state: state) }
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("sub-generic class base generic class crash when set assoc object")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor6(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
final class Actor6Final<T> : Actor5<T> {
|
||||
override init(state: T) { super.init(state: state) }
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("final sub-generic class base generic class crash when set assoc object")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor6Final(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
Tests.test("final sub-generic class base generic class crash when set assoc object2")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.code {
|
||||
let x = Actor6Final(state: 5)
|
||||
print(type(of: x))
|
||||
}
|
||||
|
||||
Tests.test("final sub-generic class metatype, base generic class crash when set assoc object")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = Actor6Final<Int>.self
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
actor class ActorNSObjectSubKlass : NSObject {}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("no crash when inherit from nsobject")
|
||||
.code {
|
||||
let x = ActorNSObjectSubKlass()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
actor class ActorNSObjectSubKlassGeneric<T> : NSObject {
|
||||
var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("no crash when generic inherit from nsobject")
|
||||
.code {
|
||||
let x = ActorNSObjectSubKlassGeneric(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
55
test/Interpreter/actor_subclass_metatypes.swift
Normal file
55
test/Interpreter/actor_subclass_metatypes.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-swiftc_driver -Xfrontend -enable-experimental-concurrency %s -o %t/out
|
||||
// RUN: %target-run %t/out
|
||||
|
||||
// REQUIRES: concurrency
|
||||
// REQUIRES: objc_interop
|
||||
// REQUIRES: executable_test
|
||||
|
||||
import ObjectiveC
|
||||
import _Concurrency
|
||||
import StdlibUnittest
|
||||
|
||||
defer { runAllTests() }
|
||||
|
||||
var Tests = TestSuite("Actor.SubClass.Metatype")
|
||||
|
||||
actor class Actor5<T> {
|
||||
var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
Tests.test("base generic class")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.code {
|
||||
let x = Actor5(state: 5)
|
||||
print(type(of: x))
|
||||
}
|
||||
|
||||
class Actor6<T> : Actor5<T> {
|
||||
override init(state: T) { super.init(state: state) }
|
||||
}
|
||||
|
||||
Tests.test("non-final sub-generic class parent generic class crash")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.code {
|
||||
let x = Actor6(state: 5)
|
||||
print(type(of: x))
|
||||
}
|
||||
|
||||
final class Actor6Final<T> : Actor5<T> {
|
||||
override init(state: T) { super.init(state: state) }
|
||||
}
|
||||
|
||||
Tests.test("final sub-generic class parent generic class crash")
|
||||
.xfail(
|
||||
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
|
||||
"actor generic class isa initialization: rdar://70589739"))
|
||||
.code {
|
||||
let x = Actor6Final(state: 5)
|
||||
print(type(of: x))
|
||||
}
|
||||
239
test/Interpreter/class_forbid_objc_assoc_objects.swift
Normal file
239
test/Interpreter/class_forbid_objc_assoc_objects.swift
Normal file
@@ -0,0 +1,239 @@
|
||||
// RUN: %target-run-simple-swift
|
||||
// RUN: %target-run-simple-swift(-O)
|
||||
|
||||
// REQUIRES: objc_interop
|
||||
// REQUIRES: executable_test
|
||||
|
||||
import ObjectiveC
|
||||
import StdlibUnittest
|
||||
|
||||
defer { runAllTests() }
|
||||
|
||||
var Tests = TestSuite("AssocObject")
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
final class AllowedToHaveAssocObject {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("no crash when set assoc object, assign") {
|
||||
let x = AllowedToHaveAssocObject()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
|
||||
Tests.test("no crash when set assoc object, copy") {
|
||||
let x = AllowedToHaveAssocObject()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_COPY)
|
||||
}
|
||||
|
||||
Tests.test("no crash when set assoc object, copy_nonatomic") {
|
||||
let x = AllowedToHaveAssocObject()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
}
|
||||
|
||||
Tests.test("no crash when set assoc object, retain") {
|
||||
let x = AllowedToHaveAssocObject()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
Tests.test("no crash when set assoc object, retain_nonatomic") {
|
||||
let x = AllowedToHaveAssocObject()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class UnableToHaveAssocObjects {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("crash when set assoc object, assign")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnableToHaveAssocObjects()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
|
||||
Tests.test("crash when set assoc object, copy")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnableToHaveAssocObjects()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_COPY)
|
||||
}
|
||||
|
||||
Tests.test("crash when set assoc object, copy_nonatomic")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnableToHaveAssocObjects()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
}
|
||||
|
||||
Tests.test("crash when set assoc object, retain")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnableToHaveAssocObjects()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
Tests.test("crash when set assoc object, retain_nonatomic")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnableToHaveAssocObjects()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class UnableToHaveAssocObjectsGeneric<T> {
|
||||
var state: T
|
||||
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("crash when set assoc object (generic)")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnableToHaveAssocObjectsGeneric(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// In this case, we mark the child. This is unsound since we will get different
|
||||
// answers since the type checker isn't enforcing this.
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class UnsoundAbleToHaveAssocObjectsParentClass {
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class UnsoundUnableToHaveAssocObjectsSubClass : UnsoundAbleToHaveAssocObjectsParentClass {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("no crash when set assoc object set only on child subclass, but assoc to parent")
|
||||
.code {
|
||||
let x = UnsoundAbleToHaveAssocObjectsParentClass()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
Tests.test("crash when set assoc object set only on child subclass")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnsoundUnableToHaveAssocObjectsSubClass()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// In this case, we mark the parent. It seems like the bit is propagated... I am
|
||||
// not sure.
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
class UnsoundAbleToHaveAssocObjectsParentClass2 {
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
final class UnsoundUnableToHaveAssocObjectsSubClass2 : UnsoundAbleToHaveAssocObjectsParentClass2 {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("crash when set assoc object set only on parent class")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnsoundUnableToHaveAssocObjectsSubClass2()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class UnsoundUnableToHaveAssocObjectsSubClass3 : UnsoundAbleToHaveAssocObjectsParentClass2 {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("crash when set assoc object set only on parent class, child not final")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = UnsoundUnableToHaveAssocObjectsSubClass3()
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// More Generic Tests
|
||||
|
||||
// In this case, we mark the child. This is unsound since we will get different
|
||||
// answers since the type checker isn't enforcing this.
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class GenericAbleToHaveAssocObjectsParentClass<T> {
|
||||
public var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
final class GenericUnableToHaveAssocObjectsSubClass<T> : GenericAbleToHaveAssocObjectsParentClass<T> {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("no crash when set assoc object set only on child subclass, but assoc to parent")
|
||||
.code {
|
||||
let x = GenericAbleToHaveAssocObjectsParentClass(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
Tests.test("crash when set assoc object set only on child subclass")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = GenericUnableToHaveAssocObjectsSubClass(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// In this case, we mark the parent. It seems like the bit is propagated... I am
|
||||
// not sure.
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
@_semantics("objc.forbidAssociatedObjects")
|
||||
class GenericAbleToHaveAssocObjectsParentClass2<T> {
|
||||
public var state: T
|
||||
init(state: T) { self.state = state }
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
final class GenericUnableToHaveAssocObjectsSubClass2<T> : GenericAbleToHaveAssocObjectsParentClass2<T> {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("crash when set assoc object set only on parent class")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = GenericUnableToHaveAssocObjectsSubClass2(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
|
||||
class GenericUnableToHaveAssocObjectsSubClass3<T> : GenericAbleToHaveAssocObjectsParentClass2<T> {
|
||||
}
|
||||
|
||||
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
|
||||
Tests.test("crash when set assoc object set only on parent class, child not final")
|
||||
.crashOutputMatches("objc_setAssociatedObject called on instance")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
let x = GenericUnableToHaveAssocObjectsSubClass3(state: 5)
|
||||
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user