Skip to content

Commit d587460

Browse files
committed
Sema: Ignore global actor attr on accessors in Swift 6
Accesses to storage declarations are checked relative to the storage's isolation, not the accessor's, whereas accessors are checked relative to their own isolation. This inconsistency exposes a data race safety hole because global actor attributes are allowed on accessors in Swift 5 and even in Swift 6 on getters. This fixes the bug by inferring accessor isolation from the storage in Swift 6 and onward. Expected source compatibility impact is negligible. First, an accessor with a global actor attribute is not a common pattern. Second, this change will only affect clients that depend on resilient dynamic libraries that declare such an accessor as inlinable.
1 parent 7bc4da3 commit d587460

File tree

5 files changed

+200
-20
lines changed

5 files changed

+200
-20
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6016,6 +6016,9 @@ ERROR(multiple_global_actors,none,
60166016
(Identifier, Identifier))
60176017
ERROR(global_actor_disallowed,none,
60186018
"%kindonly0 cannot have a global actor", (const Decl *))
6019+
NOTE(global_actor_attr_is_ignored,none,
6020+
"global actor attribute is ignored", ())
6021+
60196022
ERROR(global_actor_on_actor_class,none,
60206023
"actor %0 cannot have a global actor", (Identifier))
60216024
ERROR(global_actor_on_local_variable,none,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ swift::checkGlobalActorAttributes(SourceLoc loc, DeclContext *dc,
348348
return std::make_pair(globalActorAttr, globalActorNominal);
349349
}
350350

351+
static bool shouldIgnoreGlobalActorOnAccessor(const ASTContext &ctx) {
352+
return ctx.isSwiftVersionAtLeast(6);
353+
}
354+
351355
std::optional<std::pair<CustomAttr *, NominalTypeDecl *>>
352356
GlobalActorAttributeRequest::evaluate(
353357
Evaluator &evaluator,
@@ -452,12 +456,18 @@ GlobalActorAttributeRequest::evaluate(
452456
auto &ctx = accessor->getASTContext();
453457
const auto attrRange = globalActorAttr->getRangeWithAt();
454458
const unsigned langModeForError = accessor->isGetter() ? 7 : 6;
459+
const bool isError = ctx.isSwiftVersionAtLeast(langModeForError);
455460

456461
// Complain about a global actor attribute on an accessor.
457462
accessor->diagnose(diag::global_actor_disallowed, decl)
458463
.highlight(attrRange)
459464
.warnUntilSwiftVersion(langModeForError);
460465

466+
if (!isError && shouldIgnoreGlobalActorOnAccessor(ctx)) {
467+
ctx.Diags.diagnose(attrRange.Start, diag::global_actor_attr_is_ignored)
468+
.highlight(attrRange);
469+
}
470+
461471
auto *storage = accessor->getStorage();
462472

463473
// Suggest to move the attribute to the storage if it is legal and the
@@ -476,7 +486,7 @@ GlobalActorAttributeRequest::evaluate(
476486
}
477487

478488
// Fail if we emitted an error.
479-
if (ctx.isSwiftVersionAtLeast(langModeForError))
489+
if (isError)
480490
return std::nullopt;
481491

482492
} else if (isa<ExtensionDecl>(decl) || isa<AbstractFunctionDecl>(decl)) {
@@ -5458,9 +5468,11 @@ getActorIsolationForMainFuncDecl(FuncDecl *fnDecl) {
54585468
/// Check rules related to global actor attributes on a class declaration.
54595469
///
54605470
/// \returns true if an error occurred.
5461-
static bool checkClassGlobalActorIsolation(
5462-
ClassDecl *classDecl, ActorIsolation isolation) {
5463-
assert(isolation.isGlobalActor());
5471+
static bool checkClassIsolation(ClassDecl *classDecl,
5472+
ActorIsolation isolation) {
5473+
if (!isolation.isGlobalActor()) {
5474+
return false;
5475+
}
54645476

54655477
// A class can only be annotated with a global actor if it has no
54665478
// superclass, the superclass is annotated with the same global actor, or
@@ -5900,8 +5912,7 @@ static InferredActorIsolation computeActorIsolation(Evaluator &evaluator,
59005912
if (isolationFromAttr && isolationFromAttr->isGlobalActor()) {
59015913
if (!areTypesEqual(isolationFromAttr->getGlobalActor(),
59025914
mainIsolation->getGlobalActor())) {
5903-
fd->getASTContext().Diags.diagnose(
5904-
fd->getLoc(), diag::main_function_must_be_mainActor);
5915+
fd->diagnose(diag::main_function_must_be_mainActor);
59055916
}
59065917
}
59075918
return {
@@ -5911,17 +5922,29 @@ static InferredActorIsolation computeActorIsolation(Evaluator &evaluator,
59115922
}
59125923
}
59135924

5914-
// If this declaration has one of the actor isolation attributes, report
5915-
// that.
5925+
// If this declaration has one of the actor isolation attributes, consider
5926+
// reporting that.
59165927
if (isolationFromAttr) {
5917-
// Classes with global actors have additional rules regarding inheritance.
5918-
if (isolationFromAttr->isGlobalActor()) {
5919-
if (auto classDecl = dyn_cast<ClassDecl>(value))
5920-
checkClassGlobalActorIsolation(classDecl, *isolationFromAttr);
5928+
if (isa<AccessorDecl>(value) && shouldIgnoreGlobalActorOnAccessor(ctx)) {
5929+
// Global actor attributes on accessors are deprecated, whereas other
5930+
// isolation attributes are unconditionally unapplicable to accessors.
5931+
//
5932+
// In Swift 6 mode and onward, disregard the global actor attribute and
5933+
// proceed to infer the isolation from the storage declaration.
5934+
} else {
5935+
// Classes with global actors have additional rules regarding inheritance.
5936+
if (auto classDecl = dyn_cast<ClassDecl>(value)) {
5937+
checkClassIsolation(classDecl, *isolationFromAttr);
5938+
}
5939+
5940+
return {*isolationFromAttr,
5941+
IsolationSource(/*source*/ nullptr, IsolationSource::Explicit)};
59215942
}
5943+
}
59225944

5923-
return {*isolationFromAttr,
5924-
IsolationSource(/*source*/ nullptr, IsolationSource::Explicit)};
5945+
// For an accessor, use the actor isolation of its storage declaration.
5946+
if (auto *accessor = dyn_cast<AccessorDecl>(value)) {
5947+
return getInferredActorIsolation(accessor->getStorage());
59255948
}
59265949

59275950
InferredActorIsolation defaultIsolation;
@@ -6037,12 +6060,6 @@ static InferredActorIsolation computeActorIsolation(Evaluator &evaluator,
60376060
}
60386061
}
60396062

6040-
// If this is an accessor, use the actor isolation of its storage
6041-
// declaration.
6042-
if (auto accessor = dyn_cast<AccessorDecl>(value)) {
6043-
return getInferredActorIsolation(accessor->getStorage());
6044-
}
6045-
60466063
if (auto var = dyn_cast<VarDecl>(value)) {
60476064
auto &ctx = var->getASTContext();
60486065
if (!ctx.LangOpts.isConcurrencyModelTaskToThread() &&
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
actor SomeActor {}
2+
3+
@globalActor
4+
struct GA1 {
5+
static let shared = SomeActor()
6+
}
7+
@globalActor
8+
struct GA2 {
9+
static let shared = SomeActor()
10+
}
11+
12+
// expected-swift6-note@+1 * {{calls to global function 'ga2()' from outside of its actor context are implicitly asynchronous}}
13+
@GA2 func ga2() {}
14+
15+
// Global actor on global var accessors.
16+
17+
@GA1 var globalObserved: Int = 0 {
18+
// expected-swift6-error@+2 {{cannot have a global actor}}
19+
// expected-swift5-warning@+1 {{cannot have a global actor}}
20+
@GA2 willSet {
21+
ga2()
22+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
23+
}
24+
// expected-swift6-error@+2 {{cannot have a global actor}}
25+
// expected-swift5-warning@+1 {{cannot have a global actor}}
26+
@GA2 didSet {
27+
ga2()
28+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
29+
}
30+
}
31+
32+
@GA1 var globalComputed: Int {
33+
// expected-swift6-note@+3 {{global actor attribute is ignored}}
34+
// expected-swift6-warning@+2 {{cannot have a global actor}}
35+
// expected-swift5-warning@+1 {{cannot have a global actor}}
36+
@GA2 get {
37+
ga2()
38+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
39+
40+
return 0
41+
}
42+
// expected-swift6-error@+2 {{cannot have a global actor}}
43+
// expected-swift5-warning@+1 {{cannot have a global actor}}
44+
@GA2 set {
45+
ga2()
46+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
47+
}
48+
// expected-swift6-error@+2 {{cannot have a global actor}}
49+
// expected-swift5-warning@+1 {{cannot have a global actor}}
50+
@GA2 _modify {
51+
ga2()
52+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
53+
}
54+
}
55+
56+
@GA1 var globalComputedRead: Int {
57+
// expected-swift6-error@+2 {{cannot have a global actor}}
58+
// expected-swift5-warning@+1 {{cannot have a global actor}}
59+
@GA2 _read {
60+
ga2()
61+
// expected-swift6-error@+-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
62+
}
63+
}
64+
65+
@GA1 func testGlobalStorageDeclActorIsolation() {
66+
let _ = globalObserved
67+
globalObserved = 0
68+
69+
let _ = globalComputed
70+
globalComputed = 0
71+
72+
let _ = globalComputedRead
73+
}
74+
75+
// Global actor on property/subscript accessors.
76+
77+
@GA1 struct TypeContext {
78+
nonisolated init() {}
79+
80+
var observed: Int {
81+
// expected-swift6-error@+3 {{cannot have a global actor}}
82+
// expected-swift5-warning@+2 {{cannot have a global actor}}
83+
// expected-note@+1 {{move global actor}}
84+
@GA2 willSet {
85+
ga2()
86+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
87+
}
88+
// expected-swift6-error@+3 {{cannot have a global actor}}
89+
// expected-swift5-warning@+2 {{cannot have a global actor}}
90+
// expected-note@+1 {{move global actor}}
91+
@GA2 didSet {
92+
ga2()
93+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
94+
}
95+
}
96+
97+
var computed: Int {
98+
// expected-swift6-note@+4 {{global actor attribute is ignored}}
99+
// expected-swift6-warning@+3 {{cannot have a global actor}}
100+
// expected-swift5-warning@+2 {{cannot have a global actor}}
101+
// expected-note@+1 {{move global actor}}
102+
@GA2 get {
103+
ga2()
104+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
105+
106+
return 0
107+
}
108+
// expected-swift6-error@+3 {{cannot have a global actor}}
109+
// expected-swift5-warning@+2 {{cannot have a global actor}}
110+
// expected-note@+1 {{move global actor}}
111+
@GA2 set {
112+
ga2()
113+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
114+
}
115+
// expected-swift6-error@+3 {{cannot have a global actor}}
116+
// expected-swift5-warning@+2 {{cannot have a global actor}}
117+
// expected-note@+1 {{move global actor}}
118+
@GA2 _modify {
119+
ga2()
120+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
121+
}
122+
// expected-swift6-error@+3 {{cannot have a global actor}}
123+
// expected-swift5-warning@+2 {{cannot have a global actor}}
124+
// expected-note@+1 {{move global actor}}
125+
@GA2 init {
126+
ga2()
127+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
128+
}
129+
}
130+
131+
var computedRead: Int {
132+
// expected-swift6-error@+3 {{cannot have a global actor}}
133+
// expected-swift5-warning@+2 {{cannot have a global actor}}
134+
// expected-note@+1 {{move global actor}}
135+
@GA2 _read {
136+
ga2()
137+
// expected-swift6-error@-1 {{call to global actor 'GA2'-isolated global function 'ga2()' in a synchronous global actor 'GA1'-isolated context}}
138+
}
139+
}
140+
}
141+
142+
@GA1 func testMemberStorageDeclActorIsolation() {
143+
var c = TypeContext()
144+
145+
let _ = c.observed
146+
c.observed = 0
147+
148+
let _ = c.computed
149+
c.computed = 0
150+
151+
let _ = c.computedRead
152+
}
153+
154+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// RUN: %target-swift-frontend -typecheck -verify -swift-version 5 -parse-as-library -verify-additional-prefix swift5- %S/Inputs/global_actor_accessors.swift
2+
3+
// REQUIRES: concurrency
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// RUN: %target-swift-frontend -typecheck -verify -swift-version 6 -parse-as-library -verify-additional-prefix swift6- -verify-additional-prefix swift6+- %S/Inputs/global_actor_accessors.swift
2+
3+
// REQUIRES: concurrency

0 commit comments

Comments
 (0)