Skip to content

Commit 066f8d7

Browse files
committed
[Concurrency] Implement a narrow carve out in the isolation
override checking for `NSObject.init()` Overriding `NSObject.init()` within a `@MainActor`-isolated type is difficult-to-impossible, especially if you need to call an initializer from an intermediate superclass that is also `@MainActor`-isolated. This won't admit a runtime data-race safety hole, because dynamic isolation checks will be inserted in the @objc thunks under `DynamicActorIsolation`, and direct calls will enforce `@MainActor` as usual. (cherry picked from commit 0d5e598)
1 parent aa0786c commit 066f8d7

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5285,6 +5285,16 @@ ConcreteDeclRef swift::getDeclRefInContext(ValueDecl *value) {
52855285
return ConcreteDeclRef(value);
52865286
}
52875287

5288+
static bool isNSObjectInit(ValueDecl *overridden) {
5289+
auto *classDecl = dyn_cast_or_null<ClassDecl>(
5290+
overridden->getDeclContext()->getSelfNominalTypeDecl());
5291+
if (!classDecl || !classDecl->isNSObject()) {
5292+
return false;
5293+
}
5294+
5295+
return isa<ConstructorDecl>(overridden);
5296+
}
5297+
52885298
/// Generally speaking, the isolation of the decl that overrides
52895299
/// must match the overridden decl. But there are a number of exceptions,
52905300
/// e.g., the decl that overrides can be nonisolated.
@@ -5317,9 +5327,21 @@ static OverrideIsolationResult validOverrideIsolation(
53175327
}
53185328

53195329
// If the overridden declaration is from Objective-C with no actor
5320-
// annotation, allow it.
5321-
if (ctx.LangOpts.StrictConcurrencyLevel != StrictConcurrency::Complete &&
5322-
overridden->hasClangNode() && !overriddenIsolation) {
5330+
// annotation, don't allow overriding isolation in complete concurrency
5331+
// checking. Calls from outside the actor, via the nonisolated superclass
5332+
// method that is dynamically dispatched, will crash at runtime due to
5333+
// the dynamic isolation check in the @objc thunk.
5334+
//
5335+
// There's a narrow carve out for `NSObject.init()` because overriding
5336+
// this init of `@MainActor`-isolated type is difficult-to-impossible,
5337+
// especially if you need to call an initializer from an intermediate
5338+
// superclass that is also `@MainActor`-isolated. This won't admit a
5339+
// runtime data-race safety hole, because dynamic isolation checks will
5340+
// be inserted in the @objc thunks under `DynamicActorIsolation`, and
5341+
// direct calls will enforce `@MainActor` as usual.
5342+
if (isNSObjectInit(overridden) ||
5343+
(ctx.LangOpts.StrictConcurrencyLevel != StrictConcurrency::Complete &&
5344+
overridden->hasClangNode() && !overriddenIsolation)) {
53235345
return OverrideIsolationResult::Allowed;
53245346
}
53255347

test/ClangImporter/objc_isolation_complete.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,27 @@ class IsolatedSub: NXSender {
3232
}
3333
}
3434

35+
class NotSendable {}
36+
37+
@MainActor
38+
class NSObjectInitOverride: NSObject {
39+
var ns: NotSendable
40+
41+
override init() {
42+
self.ns = NotSendable()
43+
super.init()
44+
}
45+
}
46+
47+
3548
@objc
3649
@MainActor
3750
class Test : NSObject {
38-
static var shared: Test? // expected-note {{mutation of this static property is only permitted within the actor}}
51+
static var shared: Test?
3952

4053
override init() {
4154
super.init()
4255

4356
Self.shared = self
44-
// expected-warning@-1 {{main actor-isolated static property 'shared' can not be mutated from a nonisolated context; this is an error in the Swift 6 language mode}}
4557
}
4658
}

0 commit comments

Comments
 (0)