Skip to content

Commit 4311a03

Browse files
[AST] Treat actors inheriting from NSObject as SwiftNativeNSObjects.
Previously, there was a divergence in behavior between marking an actor as: - objc: This would lead to usage of Swift reference counting. - subclass of NSObject but not objc: This would lead to usage of ObjC reference counting. By itself, this might seem OK, after all, the two reference counting schemes are compatible (you can pass things from one language to another). However, there is a difference here because actors have a multi-step deinitialization process due to the way the reference accounting works with tasks. When the last reference is released, the destroy() method is called which only puts the actor into a Zombie_Latching state and deallocation happens later on. In contrast, when the ObjC runtime releases the last reference, it immediately deallocates the actor, without calling the destroy() method or having any awareness about actors. This mismatch leads to a use-after-free in the Swift runtime; normally, the actor would be in the Zombie_Latching state and when the final running task finishes, it would end up deallocating the actor. This expectation is not met when the memory is freed by the ObjC runtime, hence the UAF. Normally, I would find it a bit odd to see a commit come with acknowledgements but in this case I think maybe it's okay, given that this took me 2+ weeks to investigate and fix. Thanks to: - Dani C. in helping reproduce the issue with a large and small test case. - John for answering questions about the actor and job lifecycle. - Mike Ash for providing generous help with debugging. Fixes rdar://80863853.
1 parent d6c9bd3 commit 4311a03

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

lib/AST/Decl.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8491,8 +8491,13 @@ bool ClassDecl::isRootDefaultActor(ModuleDecl *M,
84918491

84928492
bool ClassDecl::isNativeNSObjectSubclass() const {
84938493
// @objc actors implicitly inherit from NSObject.
8494-
if (isActor() && getAttrs().hasAttribute<ObjCAttr>())
8495-
return true;
8494+
if (isActor()) {
8495+
if (getAttrs().hasAttribute<ObjCAttr>()) {
8496+
return true;
8497+
}
8498+
ClassDecl *superclass = getSuperclassDecl();
8499+
return superclass && superclass->isNSObject();
8500+
}
84968501

84978502
// For now, non-actor classes cannot use the native NSObject subclass.
84988503
// Eventually we should roll this out to more classes that directly

test/SILGen/objc_actor.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %target-swift-frontend -emit-silgen %s -swift-version 5 -disable-availability-checking | %FileCheck %s
2+
// REQUIRES: concurrency
3+
// REQUIRES: objc_interop
4+
5+
// rdar://80863853 - For an actor inheriting from NSObject and using '@objc'
6+
// should have the same effect: the effective superclass is SwiftNativeNSObject
7+
// (see 945011d39f8b271b8906bd509aac3aa954f4fc57) not NSObject.
8+
// Check that we don't treat any case as an ObjC class.
9+
10+
import Foundation
11+
12+
public actor MyClass1: NSObject {
13+
public var x: Int
14+
public init(_ x: Int) { self.x = x }
15+
}
16+
17+
// CHECK: alloc_ref $MyClass1
18+
// CHECK-NOT: alloc_ref [objc] $MyClass1
19+
20+
@objc public actor MyClass2 {
21+
public var x: Int
22+
public init(_ x: Int) { self.x = x }
23+
}
24+
25+
// CHECK: alloc_ref $MyClass2
26+
// CHECK-NOT: alloc_ref [objc] $MyClass2
27+
28+
@objc public actor MyClass3: NSObject {
29+
public var x: Int
30+
public init(_ x: Int) { self.x = x }
31+
}
32+
33+
// CHECK: alloc_ref $MyClass3
34+
// CHECK-NOT: alloc_ref [objc] $MyClass3

0 commit comments

Comments
 (0)