Skip to content

Commit aec59f2

Browse files
Merge pull request #67974 from sophiapoirier/global-static-data-race-safety
enforce under strict concurrency that globals and statics be either…
2 parents 594469a + 936ab20 commit aec59f2

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
@@ -5248,6 +5248,15 @@ NOTE(actor_mutable_state,none,
52485248
WARNING(shared_mutable_state_access,none,
52495249
"reference to %kind0 is not concurrency-safe because it involves "
52505250
"shared mutable state", (const ValueDecl *))
5251+
WARNING(shared_mutable_state_decl,none,
5252+
"%kind0 is not concurrency-safe because it is non-isolated global "
5253+
"shared mutable state", (const ValueDecl *))
5254+
NOTE(shared_mutable_state_decl_note,none,
5255+
"isolate %0 to a global actor, or convert it to a 'let' constant and "
5256+
"conform it to 'Sendable'", (const ValueDecl *))
5257+
WARNING(shared_immutable_state_decl,none,
5258+
"%kind0 is not concurrency-safe because it is not either conforming to "
5259+
"'Sendable' or isolated to a global actor", (const ValueDecl *))
52515260
ERROR(actor_isolated_witness,none,
52525261
"%select{|distributed }0%1 %kind2 cannot be used to satisfy %3 protocol "
52535262
"requirement",

include/swift/Basic/Features.def

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

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

lib/AST/ASTPrinter.cpp

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

3492+
static bool usesFeatureGlobalConcurrency(Decl *decl) { return false; }
3493+
34923494
static bool usesFeaturePlaygroundExtendedCallbacks(Decl *decl) {
34933495
return false;
34943496
}

lib/Frontend/CompilerInvocation.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -954,14 +954,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
954954
if (Opts.isSwiftVersionAtLeast(6)) {
955955
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
956956
} else if (const Arg *A = Args.getLastArg(OPT_strict_concurrency)) {
957-
auto value =
958-
llvm::StringSwitch<llvm::Optional<StrictConcurrency>>(A->getValue())
959-
.Case("minimal", StrictConcurrency::Minimal)
960-
.Case("targeted", StrictConcurrency::Targeted)
961-
.Case("complete", StrictConcurrency::Complete)
962-
.Default(llvm::None);
963-
964-
if (value)
957+
if (auto value = parseStrictConcurrency(A->getValue()))
965958
Opts.StrictConcurrencyLevel = *value;
966959
else
967960
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
@@ -3184,10 +3184,7 @@ namespace {
31843184
isolatedActor, llvm::None, llvm::None, getClosureActorIsolation);
31853185
switch (result) {
31863186
case ActorReferenceResult::SameConcurrencyDomain:
3187-
if (diagnoseReferenceToUnsafeGlobal(decl, loc))
3188-
return true;
3189-
3190-
return false;
3187+
return diagnoseReferenceToUnsafeGlobal(decl, loc);
31913188

31923189
case ActorReferenceResult::ExitsActorToNonisolated:
31933190
if (diagnoseReferenceToUnsafeGlobal(decl, loc))
@@ -4112,6 +4109,33 @@ ActorIsolation ActorIsolationRequest::evaluate(
41124109
return ActorIsolation::forActorInstanceParameter(actor, *paramIdx);
41134110
}
41144111

4112+
// Diagnose global state that is not either immutable plus Sendable or
4113+
// isolated to a global actor.
4114+
auto checkGlobalIsolation = [var = dyn_cast<VarDecl>(value)](
4115+
ActorIsolation isolation) {
4116+
if (var && var->getLoc() &&
4117+
var->getASTContext().LangOpts.hasFeature(Feature::GlobalConcurrency) &&
4118+
!isolation.isGlobalActor()) {
4119+
const bool isGlobalState =
4120+
var->isStatic() || var->getDeclContext()->isModuleScopeContext() ||
4121+
(var->getDeclContext()->isTypeContext() && !var->isInstanceMember());
4122+
auto *classDecl = var->getDeclContext()->getSelfClassDecl();
4123+
const bool isActorType = classDecl && classDecl->isAnyActor();
4124+
if (isGlobalState && !isActorType) {
4125+
if (var->isLet()) {
4126+
if (!isSendableType(var->getModuleContext(),
4127+
var->getInterfaceType())) {
4128+
var->diagnose(diag::shared_immutable_state_decl, var);
4129+
}
4130+
} else {
4131+
var->diagnose(diag::shared_mutable_state_decl, var);
4132+
var->diagnose(diag::shared_mutable_state_decl_note, var);
4133+
}
4134+
}
4135+
}
4136+
return isolation;
4137+
};
4138+
41154139
auto isolationFromAttr = getIsolationFromAttributes(value);
41164140
if (FuncDecl *fd = dyn_cast<FuncDecl>(value)) {
41174141
// Main.main() and Main.$main are implicitly MainActor-protected.
@@ -4138,7 +4162,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
41384162
checkClassGlobalActorIsolation(classDecl, *isolationFromAttr);
41394163
}
41404164

4141-
return *isolationFromAttr;
4165+
return checkGlobalIsolation(*isolationFromAttr);
41424166
}
41434167

41444168
// Determine the default isolation for this declaration, which may still be
@@ -4169,78 +4193,82 @@ ActorIsolation ActorIsolationRequest::evaluate(
41694193
}
41704194

41714195
// Function used when returning an inferred isolation.
4172-
auto inferredIsolation = [&](
4173-
ActorIsolation inferred, bool onlyGlobal = false) {
4174-
// check if the inferred isolation is valid in the context of
4175-
// its overridden isolation.
4176-
if (overriddenValue) {
4177-
// if the inferred isolation is not valid, then carry-over the overridden
4178-
// declaration's isolation as this decl's inferred isolation.
4179-
switch (validOverrideIsolation(
4180-
value, inferred, overriddenValue, *overriddenIso)) {
4181-
case OverrideIsolationResult::Allowed:
4182-
case OverrideIsolationResult::Sendable:
4183-
break;
4184-
4185-
case OverrideIsolationResult::Disallowed:
4186-
inferred = *overriddenIso;
4187-
break;
4188-
}
4189-
}
4196+
auto inferredIsolation = [&](ActorIsolation inferred,
4197+
bool onlyGlobal = false) {
4198+
// Invoke the body within checkGlobalIsolation to check the result.
4199+
return checkGlobalIsolation([&] {
4200+
// check if the inferred isolation is valid in the context of
4201+
// its overridden isolation.
4202+
if (overriddenValue) {
4203+
// if the inferred isolation is not valid, then carry-over the
4204+
// overridden declaration's isolation as this decl's inferred isolation.
4205+
switch (validOverrideIsolation(value, inferred, overriddenValue,
4206+
*overriddenIso)) {
4207+
case OverrideIsolationResult::Allowed:
4208+
case OverrideIsolationResult::Sendable:
4209+
break;
41904210

4191-
// Add an implicit attribute to capture the actor isolation that was
4192-
// inferred, so that (e.g.) it will be printed and serialized.
4193-
ASTContext &ctx = value->getASTContext();
4194-
switch (inferred) {
4195-
case ActorIsolation::Independent:
4196-
// Stored properties cannot be non-isolated, so don't infer it.
4197-
if (auto var = dyn_cast<VarDecl>(value)) {
4198-
if (!var->isStatic() && var->hasStorage())
4199-
return ActorIsolation::forUnspecified()
4200-
.withPreconcurrency(inferred.preconcurrency());
4211+
case OverrideIsolationResult::Disallowed:
4212+
inferred = *overriddenIso;
4213+
break;
4214+
}
42014215
}
42024216

4217+
// Add an implicit attribute to capture the actor isolation that was
4218+
// inferred, so that (e.g.) it will be printed and serialized.
4219+
ASTContext &ctx = value->getASTContext();
4220+
switch (inferred) {
4221+
case ActorIsolation::Independent:
4222+
// Stored properties cannot be non-isolated, so don't infer it.
4223+
if (auto var = dyn_cast<VarDecl>(value)) {
4224+
if (!var->isStatic() && var->hasStorage())
4225+
return ActorIsolation::forUnspecified().withPreconcurrency(
4226+
inferred.preconcurrency());
4227+
}
42034228

4204-
if (onlyGlobal)
4205-
return ActorIsolation::forUnspecified()
4206-
.withPreconcurrency(inferred.preconcurrency());
4229+
if (onlyGlobal) {
4230+
return ActorIsolation::forUnspecified().withPreconcurrency(
4231+
inferred.preconcurrency());
4232+
}
42074233

4208-
value->getAttrs().add(new (ctx) NonisolatedAttr(/*IsImplicit=*/true));
4209-
break;
4234+
value->getAttrs().add(new (ctx) NonisolatedAttr(/*IsImplicit=*/true));
4235+
break;
42104236

4211-
case ActorIsolation::GlobalActorUnsafe:
4212-
case ActorIsolation::GlobalActor: {
4213-
// Stored properties of a struct don't need global-actor isolation.
4214-
if (ctx.isSwiftVersionAtLeast(6))
4215-
if (auto *var = dyn_cast<VarDecl>(value))
4216-
if (!var->isStatic() && var->isOrdinaryStoredProperty())
4217-
if (auto *varDC = var->getDeclContext())
4218-
if (auto *nominal = varDC->getSelfNominalTypeDecl())
4219-
if (isa<StructDecl>(nominal) &&
4220-
!isWrappedValueOfPropWrapper(var))
4221-
return ActorIsolation::forUnspecified()
4222-
.withPreconcurrency(inferred.preconcurrency());
4223-
4224-
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
4225-
auto attr = CustomAttr::create(
4226-
ctx, SourceLoc(), typeExpr, /*implicit=*/true);
4227-
if (inferred == ActorIsolation::GlobalActorUnsafe)
4228-
attr->setArgIsUnsafe(true);
4229-
value->getAttrs().add(attr);
4230-
break;
4231-
}
4237+
case ActorIsolation::GlobalActorUnsafe:
4238+
case ActorIsolation::GlobalActor: {
4239+
// Stored properties of a struct don't need global-actor isolation.
4240+
if (ctx.isSwiftVersionAtLeast(6))
4241+
if (auto *var = dyn_cast<VarDecl>(value))
4242+
if (!var->isStatic() && var->isOrdinaryStoredProperty())
4243+
if (auto *varDC = var->getDeclContext())
4244+
if (auto *nominal = varDC->getSelfNominalTypeDecl())
4245+
if (isa<StructDecl>(nominal) &&
4246+
!isWrappedValueOfPropWrapper(var))
4247+
return ActorIsolation::forUnspecified().withPreconcurrency(
4248+
inferred.preconcurrency());
4249+
4250+
auto typeExpr =
4251+
TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
4252+
auto attr =
4253+
CustomAttr::create(ctx, SourceLoc(), typeExpr, /*implicit=*/true);
4254+
if (inferred == ActorIsolation::GlobalActorUnsafe)
4255+
attr->setArgIsUnsafe(true);
4256+
value->getAttrs().add(attr);
4257+
break;
4258+
}
42324259

4233-
case ActorIsolation::ActorInstance:
4234-
case ActorIsolation::Unspecified:
4235-
if (onlyGlobal)
4236-
return ActorIsolation::forUnspecified()
4237-
.withPreconcurrency(inferred.preconcurrency());
4260+
case ActorIsolation::ActorInstance:
4261+
case ActorIsolation::Unspecified:
4262+
if (onlyGlobal)
4263+
return ActorIsolation::forUnspecified().withPreconcurrency(
4264+
inferred.preconcurrency());
42384265

4239-
// Nothing to do.
4240-
break;
4241-
}
4266+
// Nothing to do.
4267+
break;
4268+
}
42424269

4243-
return inferred;
4270+
return inferred;
4271+
}());
42444272
};
42454273

42464274
// If this is a local function, inherit the actor isolation from its
@@ -4371,7 +4399,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
43714399
}
43724400

43734401
// Default isolation for this member.
4374-
return defaultIsolation;
4402+
return checkGlobalIsolation(defaultIsolation);
43754403
}
43764404

43774405
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)