Skip to content

Commit ba6c08f

Browse files
authored
Merge pull request #34136 from gottesmm/pr-7da9c7145fbd96869d5e73a40a600d6c0e23fa91
[concurrency] Ban associated objects from being set on instances of actor classes.
2 parents 517f4b7 + 91209d3 commit ba6c08f

8 files changed

+631
-1
lines changed

include/swift/ABI/Class.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ enum class ObjCClassFlags : uint32_t {
5353
/// This class provides a non-trivial .cxx_destruct method, but
5454
/// its .cxx_construct is trivial. For backwards compatibility,
5555
/// when setting this flag, HasCXXStructors must be set as well.
56-
HasCXXDestructorOnly = 0x00100
56+
HasCXXDestructorOnly = 0x00100,
57+
58+
/// This class does not allow associated objects on instances.
59+
///
60+
/// Will cause the objc runtime to trap in objc_setAssociatedObject.
61+
ForbidsAssociatedObjects = 0x00400,
5762
};
5863
inline ObjCClassFlags &operator|=(ObjCClassFlags &lhs, ObjCClassFlags rhs) {
5964
lhs = ObjCClassFlags(uint32_t(lhs) | uint32_t(rhs));

include/swift/AST/SemanticAttrs.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,10 @@ SEMANTICS_ATTR(KEYPATH_KVC_KEY_PATH_STRING, "keypath.kvcKeyPathString")
106106
/// consider inlining where to put these.
107107
SEMANTICS_ATTR(FORCE_EMIT_OPT_REMARK_PREFIX, "optremark")
108108

109+
/// An attribute that when attached to a class causes instances of the class to
110+
/// be forbidden from having associated objects set upon them. This is only used
111+
/// for testing purposes.
112+
SEMANTICS_ATTR(OBJC_FORBID_ASSOCIATED_OBJECTS, "objc.forbidAssociatedObjects")
113+
109114
#undef SEMANTICS_ATTR
110115

lib/IRGen/GenClass.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/Module.h"
2626
#include "swift/AST/Pattern.h"
2727
#include "swift/AST/PrettyStackTrace.h"
28+
#include "swift/AST/SemanticAttrs.h"
2829
#include "swift/AST/TypeMemberVisitor.h"
2930
#include "swift/AST/Types.h"
3031
#include "swift/ClangImporter/ClangModule.h"
@@ -1443,6 +1444,32 @@ namespace {
14431444
}
14441445

14451446
private:
1447+
/// If we should set the forbids associated objects on instances metadata
1448+
/// flag.
1449+
///
1450+
/// We currently do this on:
1451+
///
1452+
/// * Actor classes.
1453+
/// * classes marked with @_semantics("objc.forbidAssociatedObjects")
1454+
/// (for testing purposes)
1455+
///
1456+
/// TODO: Expand this as appropriate over time.
1457+
bool doesClassForbidAssociatedObjectsOnInstances() const {
1458+
auto *clsDecl = getClass();
1459+
1460+
// We ban this on actors without objc ancestry.
1461+
if (clsDecl->isActor() && !clsDecl->checkAncestry(AncestryFlags::ObjC))
1462+
return true;
1463+
1464+
// Otherwise, we only do it if our special semantics attribute is on the
1465+
// relevant class. This is for testing purposes.
1466+
if (clsDecl->hasSemanticsAttr(semantics::OBJC_FORBID_ASSOCIATED_OBJECTS))
1467+
return true;
1468+
1469+
// TODO: Add new cases here as appropriate over time.
1470+
return false;
1471+
}
1472+
14461473
ObjCClassFlags buildFlags(ForMetaClass_t forMeta,
14471474
HasUpdateCallback_t hasUpdater) {
14481475
ObjCClassFlags flags = ObjCClassFlags::CompiledByARC;
@@ -1463,6 +1490,11 @@ namespace {
14631490
if (hasUpdater)
14641491
flags |= ObjCClassFlags::HasMetadataUpdateCallback;
14651492

1493+
// If we know that our class does not support having associated objects
1494+
// placed upon instances, set the forbid associated object flag.
1495+
if (doesClassForbidAssociatedObjectsOnInstances())
1496+
flags |= ObjCClassFlags::ForbidsAssociatedObjects;
1497+
14661498
// FIXME: set ObjCClassFlags::Hidden when appropriate
14671499
return flags;
14681500
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-swift-frontend -enable-experimental-concurrency -emit-ir %s | %FileCheck %s
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: objc_interop
5+
6+
import _Concurrency
7+
8+
// CHECK: @_METACLASS_DATA__TtC37actor_class_forbid_objc_assoc_objects5Actor = internal constant { {{.*}} } { i32 [[METAFLAGS:1153]],
9+
// CHECK: @_DATA__TtC37actor_class_forbid_objc_assoc_objects5Actor = internal constant { {{.*}} } { i32 [[OBJECTFLAGS:1152|1216]],
10+
final actor class Actor {
11+
}
12+
13+
// CHECK: @_METACLASS_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor2 = internal constant { {{.*}} } { i32 [[METAFLAGS]],
14+
// CHECK: @_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor2 = internal constant { {{.*}} } { i32 [[OBJECTFLAGS]],
15+
actor class Actor2 {
16+
}
17+
18+
// CHECK: @_METACLASS_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor3 = internal constant { {{.*}} } { i32 [[METAFLAGS]],
19+
// CHECK: @_DATA__TtC37actor_class_forbid_objc_assoc_objects6Actor3 = internal constant { {{.*}} } { i32 [[OBJECTFLAGS]],
20+
class Actor3 : Actor2 {}
21+
22+
actor class GenericActor<T> {
23+
var state: T
24+
init(state: T) { self.state = state }
25+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// RUN: %target-swift-frontend -emit-ir %s | %FileCheck %s
2+
3+
// REQUIRES: objc_interop
4+
5+
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects24AllowedToHaveAssocObject = internal constant { {{.*}} } { i32 129,
6+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects24AllowedToHaveAssocObject = internal constant { {{.*}} } { i32 128,
7+
final class AllowedToHaveAssocObject {
8+
}
9+
10+
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects24UnableToHaveAssocObjects = internal constant { {{.*}} } { i32 1153,
11+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects24UnableToHaveAssocObjects = internal constant { {{.*}} } { i32 1152,
12+
@_semantics("objc.forbidAssociatedObjects")
13+
final class UnableToHaveAssocObjects {
14+
}
15+
16+
// Class Metadata For Generic Metadata
17+
//
18+
// CHECK: [[CLASS_METADATA:@[0-9][0-9]*]] = internal constant <{ {{.*}} }> <{ {{.*}} { i32 1152,
19+
//
20+
// Generic Metadata Pattern
21+
//
22+
// CHECK: @"$s31class_forbid_objc_assoc_objects31UnableToHaveAssocObjectsGenericCMP" = internal constant {{.*}}[[CLASS_METADATA]]
23+
@_semantics("objc.forbidAssociatedObjects")
24+
final class UnableToHaveAssocObjectsGeneric<T> {
25+
var state: T
26+
init(state: T) { self.state = state }
27+
}
28+
29+
// This should be normal.
30+
//
31+
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundAbleToHaveAssocObjectsParentClass = internal constant { {{.*}} } { i32 129,
32+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundAbleToHaveAssocObjectsParentClass = internal constant { {{.*}} } { i32 128,
33+
class UnsoundAbleToHaveAssocObjectsParentClass {
34+
}
35+
36+
// This should have assoc object constraints
37+
//
38+
// CHECK: @_METACLASS_DATA__TtC31class_forbid_objc_assoc_objects39UnsoundUnableToHaveAssocObjectsSubClass = internal constant { {{.*}} } { i32 1153,
39+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects39UnsoundUnableToHaveAssocObjectsSubClass = internal constant { {{.*}} } { i32 1152,
40+
@_semantics("objc.forbidAssociatedObjects")
41+
final class UnsoundUnableToHaveAssocObjectsSubClass : UnsoundAbleToHaveAssocObjectsParentClass {
42+
}
43+
44+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects41UnsoundAbleToHaveAssocObjectsParentClass2 = internal constant { {{.*}} } { i32 1152,
45+
@_semantics("objc.forbidAssociatedObjects")
46+
class UnsoundAbleToHaveAssocObjectsParentClass2 {
47+
}
48+
49+
// This has normal metadata. We must at runtime add the flags of the subclass to
50+
// the child.
51+
//
52+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundUnableToHaveAssocObjectsSubClass2 = internal constant { {{.*}} } { i32 128,
53+
final class UnsoundUnableToHaveAssocObjectsSubClass2 : UnsoundAbleToHaveAssocObjectsParentClass2 {
54+
}
55+
56+
// CHECK: @_DATA__TtC31class_forbid_objc_assoc_objects40UnsoundUnableToHaveAssocObjectsSubClass3 = internal constant { {{.*}} } { i32 128,
57+
class UnsoundUnableToHaveAssocObjectsSubClass3 : UnsoundAbleToHaveAssocObjectsParentClass2 {
58+
}
59+
60+
class GenericAbleToHaveAssocObjectsParentClass<T> {
61+
public var state: T
62+
init(state: T) { self.state = state }
63+
}
64+
65+
@_semantics("objc.forbidAssociatedObjects")
66+
final class GenericUnableToHaveAssocObjectsSubClass<T> : GenericAbleToHaveAssocObjectsParentClass<T> {
67+
}
68+
69+
@_semantics("objc.forbidAssociatedObjects")
70+
class GenericAbleToHaveAssocObjectsParentClass2<T> {
71+
public var state: T
72+
init(state: T) { self.state = state }
73+
}
74+
75+
final class GenericUnableToHaveAssocObjectsSubClass2<T> : GenericAbleToHaveAssocObjectsParentClass2<T> {
76+
}
77+
78+
class GenericUnableToHaveAssocObjectsSubClass3<T> : GenericAbleToHaveAssocObjectsParentClass2<T> {
79+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swiftc_driver -Xfrontend -enable-experimental-concurrency %s -o %t/out
3+
// RUN: %target-run %t/out
4+
5+
// REQUIRES: concurrency
6+
// REQUIRES: objc_interop
7+
// REQUIRES: executable_test
8+
9+
import ObjectiveC
10+
import _Concurrency
11+
import StdlibUnittest
12+
13+
defer { runAllTests() }
14+
15+
var Tests = TestSuite("Actor.AssocObject")
16+
17+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
18+
final actor class Actor {
19+
}
20+
21+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
22+
Tests.test("final class crash when set assoc object")
23+
.crashOutputMatches("objc_setAssociatedObject called on instance")
24+
.code {
25+
expectCrashLater()
26+
let x = Actor()
27+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
28+
}
29+
}
30+
31+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
32+
actor class Actor2 {
33+
}
34+
35+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
36+
Tests.test("non-final class crash when set assoc object")
37+
.crashOutputMatches("objc_setAssociatedObject called on instance")
38+
.code {
39+
expectCrashLater()
40+
let x = Actor2()
41+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
42+
}
43+
}
44+
45+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
46+
class Actor3 : Actor2 {}
47+
48+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
49+
Tests.test("non-final subclass crash when set assoc object")
50+
.crashOutputMatches("objc_setAssociatedObject called on instance")
51+
.code {
52+
expectCrashLater()
53+
let x = Actor3()
54+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
55+
}
56+
}
57+
58+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
59+
final class Actor3Final : Actor2 {}
60+
61+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
62+
Tests.test("final subclass crash when set assoc object")
63+
.crashOutputMatches("objc_setAssociatedObject called on instance")
64+
.code {
65+
expectCrashLater()
66+
let x = Actor3Final()
67+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
68+
}
69+
}
70+
71+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
72+
class Actor4<T> : Actor2 {
73+
var state: T
74+
init(state: T) { self.state = state }
75+
}
76+
77+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
78+
Tests.test("generic subclass crash when set assoc object")
79+
.crashOutputMatches("objc_setAssociatedObject called on instance")
80+
.code {
81+
expectCrashLater()
82+
let x = Actor4(state: 5)
83+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
84+
}
85+
}
86+
87+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
88+
actor class Actor5<T> {
89+
var state: T
90+
init(state: T) { self.state = state }
91+
}
92+
93+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
94+
Tests.test("base generic class crash when set assoc object")
95+
.xfail(
96+
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
97+
"actor generic class isa initialization: rdar://70589739"))
98+
.crashOutputMatches("objc_setAssociatedObject called on instance")
99+
.code {
100+
expectCrashLater()
101+
let x = Actor5(state: 5)
102+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
103+
}
104+
105+
Tests.test("base generic class metatype crash when set assoc object")
106+
.crashOutputMatches("objc_setAssociatedObject called on instance")
107+
.code {
108+
expectCrashLater()
109+
let x = Actor5<Int>.self
110+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
111+
}
112+
}
113+
114+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
115+
class Actor6<T> : Actor5<T> {
116+
override init(state: T) { super.init(state: state) }
117+
}
118+
119+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
120+
Tests.test("sub-generic class base generic class crash when set assoc object")
121+
.xfail(
122+
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
123+
"actor generic class isa initialization: rdar://70589739"))
124+
.crashOutputMatches("objc_setAssociatedObject called on instance")
125+
.code {
126+
expectCrashLater()
127+
let x = Actor6(state: 5)
128+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
129+
}
130+
}
131+
132+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
133+
final class Actor6Final<T> : Actor5<T> {
134+
override init(state: T) { super.init(state: state) }
135+
}
136+
137+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
138+
Tests.test("final sub-generic class base generic class crash when set assoc object")
139+
.xfail(
140+
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
141+
"actor generic class isa initialization: rdar://70589739"))
142+
.crashOutputMatches("objc_setAssociatedObject called on instance")
143+
.code {
144+
expectCrashLater()
145+
let x = Actor6Final(state: 5)
146+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
147+
}
148+
149+
Tests.test("final sub-generic class base generic class crash when set assoc object2")
150+
.xfail(
151+
.custom({ true }, reason: "We appear to be stomping on isa pointers during " +
152+
"actor generic class isa initialization: rdar://70589739"))
153+
.code {
154+
let x = Actor6Final(state: 5)
155+
print(type(of: x))
156+
}
157+
158+
Tests.test("final sub-generic class metatype, base generic class crash when set assoc object")
159+
.crashOutputMatches("objc_setAssociatedObject called on instance")
160+
.code {
161+
expectCrashLater()
162+
let x = Actor6Final<Int>.self
163+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
164+
}
165+
}
166+
167+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
168+
actor class ActorNSObjectSubKlass : NSObject {}
169+
170+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
171+
Tests.test("no crash when inherit from nsobject")
172+
.code {
173+
let x = ActorNSObjectSubKlass()
174+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
175+
}
176+
}
177+
178+
@available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *)
179+
actor class ActorNSObjectSubKlassGeneric<T> : NSObject {
180+
var state: T
181+
init(state: T) { self.state = state }
182+
}
183+
184+
if #available(macOS 10.4.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) {
185+
Tests.test("no crash when generic inherit from nsobject")
186+
.code {
187+
let x = ActorNSObjectSubKlassGeneric(state: 5)
188+
objc_setAssociatedObject(x, "myKey", "myValue", .OBJC_ASSOCIATION_RETAIN)
189+
}
190+
}

0 commit comments

Comments
 (0)