Skip to content

Commit 936ab20

Browse files
committed
enforce under strict concurrency that globals and statics be either isolated to a global actor or Sendable plus immutable
rdar://81629027 Global and static variable data-race safety
1 parent 7b3916e commit 936ab20

File tree

6 files changed

+151
-79
lines changed

6 files changed

+151
-79
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5243,6 +5243,15 @@ NOTE(actor_mutable_state,none,
52435243
WARNING(shared_mutable_state_access,none,
52445244
"reference to %kind0 is not concurrency-safe because it involves "
52455245
"shared mutable state", (const ValueDecl *))
5246+
WARNING(shared_mutable_state_decl,none,
5247+
"%kind0 is not concurrency-safe because it is non-isolated global "
5248+
"shared mutable state", (const ValueDecl *))
5249+
NOTE(shared_mutable_state_decl_note,none,
5250+
"isolate %0 to a global actor, or convert it to a 'let' constant and "
5251+
"conform it to 'Sendable'", (const ValueDecl *))
5252+
WARNING(shared_immutable_state_decl,none,
5253+
"%kind0 is not concurrency-safe because it is not either conforming to "
5254+
"'Sendable' or isolated to a global actor", (const ValueDecl *))
52465255
ERROR(actor_isolated_witness,none,
52475256
"%select{|distributed }0%1 %kind2 cannot be used to satisfy %3 protocol "
52485257
"requirement",

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ EXPERIMENTAL_FEATURE(StrictConcurrency, true)
220220
/// Defer Sendable checking to SIL diagnostic phase.
221221
EXPERIMENTAL_FEATURE(SendNonSendable, false)
222222

223+
/// Within strict concurrency, narrow global variable isolation requirements.
224+
EXPERIMENTAL_FEATURE(GlobalConcurrency, false)
225+
223226
/// Enable extended callbacks (with additional parameters) to be used when the
224227
/// "playground transform" is enabled.
225228
EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true)

lib/AST/ASTPrinter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3504,6 +3504,8 @@ static bool usesFeatureSendNonSendable(Decl *decl) {
35043504
return false;
35053505
}
35063506

3507+
static bool usesFeatureGlobalConcurrency(Decl *decl) { return false; }
3508+
35073509
static bool usesFeaturePlaygroundExtendedCallbacks(Decl *decl) {
35083510
return false;
35093511
}

lib/Frontend/CompilerInvocation.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -953,14 +953,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
953953
if (Opts.isSwiftVersionAtLeast(6)) {
954954
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
955955
} else if (const Arg *A = Args.getLastArg(OPT_strict_concurrency)) {
956-
auto value =
957-
llvm::StringSwitch<llvm::Optional<StrictConcurrency>>(A->getValue())
958-
.Case("minimal", StrictConcurrency::Minimal)
959-
.Case("targeted", StrictConcurrency::Targeted)
960-
.Case("complete", StrictConcurrency::Complete)
961-
.Default(llvm::None);
962-
963-
if (value)
956+
if (auto value = parseStrictConcurrency(A->getValue()))
964957
Opts.StrictConcurrencyLevel = *value;
965958
else
966959
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 97 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -3145,10 +3145,7 @@ namespace {
31453145
isolatedActor, llvm::None, llvm::None, getClosureActorIsolation);
31463146
switch (result) {
31473147
case ActorReferenceResult::SameConcurrencyDomain:
3148-
if (diagnoseReferenceToUnsafeGlobal(decl, loc))
3149-
return true;
3150-
3151-
return false;
3148+
return diagnoseReferenceToUnsafeGlobal(decl, loc);
31523149

31533150
case ActorReferenceResult::ExitsActorToNonisolated:
31543151
if (diagnoseReferenceToUnsafeGlobal(decl, loc))
@@ -4073,6 +4070,33 @@ ActorIsolation ActorIsolationRequest::evaluate(
40734070
return ActorIsolation::forActorInstanceParameter(actor, *paramIdx);
40744071
}
40754072

4073+
// Diagnose global state that is not either immutable plus Sendable or
4074+
// isolated to a global actor.
4075+
auto checkGlobalIsolation = [var = dyn_cast<VarDecl>(value)](
4076+
ActorIsolation isolation) {
4077+
if (var && var->getLoc() &&
4078+
var->getASTContext().LangOpts.hasFeature(Feature::GlobalConcurrency) &&
4079+
!isolation.isGlobalActor()) {
4080+
const bool isGlobalState =
4081+
var->isStatic() || var->getDeclContext()->isModuleScopeContext() ||
4082+
(var->getDeclContext()->isTypeContext() && !var->isInstanceMember());
4083+
auto *classDecl = var->getDeclContext()->getSelfClassDecl();
4084+
const bool isActorType = classDecl && classDecl->isAnyActor();
4085+
if (isGlobalState && !isActorType) {
4086+
if (var->isLet()) {
4087+
if (!isSendableType(var->getModuleContext(),
4088+
var->getInterfaceType())) {
4089+
var->diagnose(diag::shared_immutable_state_decl, var);
4090+
}
4091+
} else {
4092+
var->diagnose(diag::shared_mutable_state_decl, var);
4093+
var->diagnose(diag::shared_mutable_state_decl_note, var);
4094+
}
4095+
}
4096+
}
4097+
return isolation;
4098+
};
4099+
40764100
auto isolationFromAttr = getIsolationFromAttributes(value);
40774101
if (FuncDecl *fd = dyn_cast<FuncDecl>(value)) {
40784102
// Main.main() and Main.$main are implicitly MainActor-protected.
@@ -4099,7 +4123,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
40994123
checkClassGlobalActorIsolation(classDecl, *isolationFromAttr);
41004124
}
41014125

4102-
return *isolationFromAttr;
4126+
return checkGlobalIsolation(*isolationFromAttr);
41034127
}
41044128

41054129
// Determine the default isolation for this declaration, which may still be
@@ -4130,78 +4154,82 @@ ActorIsolation ActorIsolationRequest::evaluate(
41304154
}
41314155

41324156
// Function used when returning an inferred isolation.
4133-
auto inferredIsolation = [&](
4134-
ActorIsolation inferred, bool onlyGlobal = false) {
4135-
// check if the inferred isolation is valid in the context of
4136-
// its overridden isolation.
4137-
if (overriddenValue) {
4138-
// if the inferred isolation is not valid, then carry-over the overridden
4139-
// declaration's isolation as this decl's inferred isolation.
4140-
switch (validOverrideIsolation(
4141-
value, inferred, overriddenValue, *overriddenIso)) {
4142-
case OverrideIsolationResult::Allowed:
4143-
case OverrideIsolationResult::Sendable:
4144-
break;
4145-
4146-
case OverrideIsolationResult::Disallowed:
4147-
inferred = *overriddenIso;
4148-
break;
4149-
}
4150-
}
4157+
auto inferredIsolation = [&](ActorIsolation inferred,
4158+
bool onlyGlobal = false) {
4159+
// Invoke the body within checkGlobalIsolation to check the result.
4160+
return checkGlobalIsolation([&] {
4161+
// check if the inferred isolation is valid in the context of
4162+
// its overridden isolation.
4163+
if (overriddenValue) {
4164+
// if the inferred isolation is not valid, then carry-over the
4165+
// overridden declaration's isolation as this decl's inferred isolation.
4166+
switch (validOverrideIsolation(value, inferred, overriddenValue,
4167+
*overriddenIso)) {
4168+
case OverrideIsolationResult::Allowed:
4169+
case OverrideIsolationResult::Sendable:
4170+
break;
41514171

4152-
// Add an implicit attribute to capture the actor isolation that was
4153-
// inferred, so that (e.g.) it will be printed and serialized.
4154-
ASTContext &ctx = value->getASTContext();
4155-
switch (inferred) {
4156-
case ActorIsolation::Independent:
4157-
// Stored properties cannot be non-isolated, so don't infer it.
4158-
if (auto var = dyn_cast<VarDecl>(value)) {
4159-
if (!var->isStatic() && var->hasStorage())
4160-
return ActorIsolation::forUnspecified()
4161-
.withPreconcurrency(inferred.preconcurrency());
4172+
case OverrideIsolationResult::Disallowed:
4173+
inferred = *overriddenIso;
4174+
break;
4175+
}
41624176
}
41634177

4178+
// Add an implicit attribute to capture the actor isolation that was
4179+
// inferred, so that (e.g.) it will be printed and serialized.
4180+
ASTContext &ctx = value->getASTContext();
4181+
switch (inferred) {
4182+
case ActorIsolation::Independent:
4183+
// Stored properties cannot be non-isolated, so don't infer it.
4184+
if (auto var = dyn_cast<VarDecl>(value)) {
4185+
if (!var->isStatic() && var->hasStorage())
4186+
return ActorIsolation::forUnspecified().withPreconcurrency(
4187+
inferred.preconcurrency());
4188+
}
41644189

4165-
if (onlyGlobal)
4166-
return ActorIsolation::forUnspecified()
4167-
.withPreconcurrency(inferred.preconcurrency());
4190+
if (onlyGlobal) {
4191+
return ActorIsolation::forUnspecified().withPreconcurrency(
4192+
inferred.preconcurrency());
4193+
}
41684194

4169-
value->getAttrs().add(new (ctx) NonisolatedAttr(/*IsImplicit=*/true));
4170-
break;
4195+
value->getAttrs().add(new (ctx) NonisolatedAttr(/*IsImplicit=*/true));
4196+
break;
41714197

4172-
case ActorIsolation::GlobalActorUnsafe:
4173-
case ActorIsolation::GlobalActor: {
4174-
// Stored properties of a struct don't need global-actor isolation.
4175-
if (ctx.isSwiftVersionAtLeast(6))
4176-
if (auto *var = dyn_cast<VarDecl>(value))
4177-
if (!var->isStatic() && var->isOrdinaryStoredProperty())
4178-
if (auto *varDC = var->getDeclContext())
4179-
if (auto *nominal = varDC->getSelfNominalTypeDecl())
4180-
if (isa<StructDecl>(nominal) &&
4181-
!isWrappedValueOfPropWrapper(var))
4182-
return ActorIsolation::forUnspecified()
4183-
.withPreconcurrency(inferred.preconcurrency());
4184-
4185-
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
4186-
auto attr = CustomAttr::create(
4187-
ctx, SourceLoc(), typeExpr, /*implicit=*/true);
4188-
if (inferred == ActorIsolation::GlobalActorUnsafe)
4189-
attr->setArgIsUnsafe(true);
4190-
value->getAttrs().add(attr);
4191-
break;
4192-
}
4198+
case ActorIsolation::GlobalActorUnsafe:
4199+
case ActorIsolation::GlobalActor: {
4200+
// Stored properties of a struct don't need global-actor isolation.
4201+
if (ctx.isSwiftVersionAtLeast(6))
4202+
if (auto *var = dyn_cast<VarDecl>(value))
4203+
if (!var->isStatic() && var->isOrdinaryStoredProperty())
4204+
if (auto *varDC = var->getDeclContext())
4205+
if (auto *nominal = varDC->getSelfNominalTypeDecl())
4206+
if (isa<StructDecl>(nominal) &&
4207+
!isWrappedValueOfPropWrapper(var))
4208+
return ActorIsolation::forUnspecified().withPreconcurrency(
4209+
inferred.preconcurrency());
4210+
4211+
auto typeExpr =
4212+
TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
4213+
auto attr =
4214+
CustomAttr::create(ctx, SourceLoc(), typeExpr, /*implicit=*/true);
4215+
if (inferred == ActorIsolation::GlobalActorUnsafe)
4216+
attr->setArgIsUnsafe(true);
4217+
value->getAttrs().add(attr);
4218+
break;
4219+
}
41934220

4194-
case ActorIsolation::ActorInstance:
4195-
case ActorIsolation::Unspecified:
4196-
if (onlyGlobal)
4197-
return ActorIsolation::forUnspecified()
4198-
.withPreconcurrency(inferred.preconcurrency());
4221+
case ActorIsolation::ActorInstance:
4222+
case ActorIsolation::Unspecified:
4223+
if (onlyGlobal)
4224+
return ActorIsolation::forUnspecified().withPreconcurrency(
4225+
inferred.preconcurrency());
41994226

4200-
// Nothing to do.
4201-
break;
4202-
}
4227+
// Nothing to do.
4228+
break;
4229+
}
42034230

4204-
return inferred;
4231+
return inferred;
4232+
}());
42054233
};
42064234

42074235
// If this is a local function, inherit the actor isolation from its
@@ -4332,7 +4360,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
43324360
}
43334361

43344362
// Default isolation for this member.
4335-
return defaultIsolation;
4363+
return checkGlobalIsolation(defaultIsolation);
43364364
}
43374365

43384366
bool HasIsolatedSelfRequest::evaluate(

test/Concurrency/experimental_feature_strictconcurrency.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-typecheck-verify-swift -enable-experimental-feature StrictConcurrency
2-
// RUN: %target-typecheck-verify-swift -enable-experimental-feature StrictConcurrency=complete
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency -enable-experimental-feature GlobalConcurrency
2+
// RUN: %target-typecheck-verify-swift -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency=complete -enable-experimental-feature GlobalConcurrency
33
// REQUIRES: concurrency
44

55
class C1 { } // expected-note{{class 'C1' does not conform to the 'Sendable' protocol}}
@@ -20,3 +20,40 @@ struct Test2: TestProtocol { // expected-warning{{conformance of 'C2' to 'Sendab
2020
// expected-note@-1{{in associated type 'Self.Value' (inferred as 'C2')}}
2121
typealias Value = C2
2222
}
23+
24+
@globalActor
25+
actor TestGlobalActor {
26+
static var shared = TestGlobalActor()
27+
}
28+
29+
@TestGlobalActor
30+
var mutableIsolatedGlobal = 1
31+
32+
var mutableNonisolatedGlobal = 1 // expected-warning{{var 'mutableNonisolatedGlobal' is not concurrency-safe because it is non-isolated global shared mutable state}}
33+
// expected-note@-1{{isolate 'mutableNonisolatedGlobal' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
34+
35+
let immutableGlobal = 1
36+
37+
final class TestSendable: Sendable {
38+
init() {}
39+
}
40+
41+
final class TestNonsendable {
42+
init() {}
43+
}
44+
45+
struct A {
46+
static let immutableExplicitSendable = TestSendable()
47+
static let immutableNonsendableGlobal = TestNonsendable() // expected-warning{{static property 'immutableNonsendableGlobal' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor}}
48+
static let immutableInferredSendable = 0
49+
static var mutable = 0 // expected-warning{{static property 'mutable' is not concurrency-safe because it is non-isolated global shared mutable state}}
50+
// expected-note@-1{{isolate 'mutable' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
51+
// expected-note@-2{{static property declared here}}
52+
}
53+
54+
@TestGlobalActor
55+
func f() {
56+
print(A.immutableExplicitSendable)
57+
print(A.immutableInferredSendable)
58+
print(A.mutable) // expected-warning{{reference to static property 'mutable' is not concurrency-safe because it involves shared mutable state}}
59+
}

0 commit comments

Comments
 (0)