Skip to content

Commit be035a6

Browse files
authored
Merge pull request #78652 from jamieQ/static-actor-safety-hole
[Sema]: Fix data race safety hole with mutable statics within actors
2 parents 653925b + f1a1998 commit be035a6

File tree

2 files changed

+90
-50
lines changed

2 files changed

+90
-50
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6247,58 +6247,66 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
62476247

62486248
void swift::checkGlobalIsolation(VarDecl *var) {
62496249
const auto isolation = getActorIsolation(var);
6250-
if (var->getLoc() &&
6251-
var->getASTContext().LangOpts.hasFeature(Feature::GlobalConcurrency) &&
6252-
!isolation.isGlobalActor() &&
6253-
(isolation != ActorIsolation::NonisolatedUnsafe)) {
6254-
auto *classDecl = var->getDeclContext()->getSelfClassDecl();
6255-
const bool isActorType = classDecl && classDecl->isAnyActor();
6256-
if (var->isGlobalStorage() && !isActorType) {
6257-
auto *diagVar = var;
6258-
if (auto *originalVar = var->getOriginalWrappedProperty()) {
6259-
diagVar = originalVar;
6260-
}
62616250

6262-
bool diagnosed = false;
6263-
if (var->isLet()) {
6264-
auto type = var->getInterfaceType();
6265-
diagnosed = diagnoseIfAnyNonSendableTypes(
6266-
type, SendableCheckContext(var->getDeclContext()),
6267-
/*inDerivedConformance=*/Type(), /*typeLoc=*/SourceLoc(),
6268-
/*diagnoseLoc=*/var->getLoc(), diag::shared_immutable_state_decl,
6269-
diagVar);
6270-
} else {
6271-
diagVar->diagnose(diag::shared_mutable_state_decl, diagVar)
6272-
.warnUntilSwiftVersion(6);
6273-
diagnosed = true;
6274-
}
6275-
6276-
// If we diagnosed this global, tack on notes to suggest potential courses
6277-
// of action.
6278-
if (diagnosed) {
6279-
if (!var->isLet()) {
6280-
auto diag =
6281-
diagVar->diagnose(diag::shared_state_make_immutable, diagVar);
6282-
SourceLoc fixItLoc = getFixItLocForVarToLet(diagVar);
6283-
if (fixItLoc.isValid()) {
6284-
diag.fixItReplace(fixItLoc, "let");
6285-
}
6286-
}
6251+
// Skip this if the relevant features aren't supported.
6252+
if (!var->getLoc() ||
6253+
!var->getASTContext().LangOpts.hasFeature(Feature::GlobalConcurrency))
6254+
return;
62876255

6288-
auto mainActor = var->getASTContext().getMainActorType();
6289-
if (mainActor) {
6290-
diagVar
6291-
->diagnose(diag::add_globalactor_to_decl, mainActor->getString(),
6292-
diagVar, mainActor)
6293-
.fixItInsert(diagVar->getAttributeInsertionLoc(false),
6294-
diag::insert_globalactor_attr, mainActor);
6295-
}
6296-
diagVar->diagnose(diag::shared_state_nonisolated_unsafe, diagVar)
6297-
.fixItInsert(diagVar->getAttributeInsertionLoc(true),
6298-
"nonisolated(unsafe) ");
6299-
}
6300-
}
6256+
// Skip if the decl is global actor-isolated or is unsafely opted-out.
6257+
if (isolation.isGlobalActor() || isolation.isNonisolatedUnsafe())
6258+
return;
6259+
6260+
// We're only concerned with global storage.
6261+
if (!var->isGlobalStorage())
6262+
return;
6263+
6264+
// At this point, we've found global state that may need to be diagnosed.
6265+
auto *diagVar = var;
6266+
6267+
// Look through property wrappers.
6268+
if (auto *originalVar = var->getOriginalWrappedProperty())
6269+
diagVar = originalVar;
6270+
6271+
bool diagnosed = false;
6272+
if (var->isLet()) {
6273+
// `let` variables are okay if they are of Sendable type.
6274+
auto type = var->getInterfaceType();
6275+
diagnosed = diagnoseIfAnyNonSendableTypes(
6276+
type, SendableCheckContext(var->getDeclContext()),
6277+
/*inDerivedConformance=*/Type(), /*typeLoc=*/SourceLoc(),
6278+
/*diagnoseLoc=*/var->getLoc(), diag::shared_immutable_state_decl,
6279+
diagVar);
6280+
} else {
6281+
diagVar->diagnose(diag::shared_mutable_state_decl, diagVar)
6282+
.warnUntilSwiftVersion(6);
6283+
diagnosed = true;
63016284
}
6285+
6286+
// If we didn't find anything to report, we're done.
6287+
if (!diagnosed)
6288+
return;
6289+
6290+
// If we diagnosed this global, tack on notes to suggest potential courses
6291+
// of action.
6292+
if (!var->isLet()) {
6293+
auto diag = diagVar->diagnose(diag::shared_state_make_immutable, diagVar);
6294+
SourceLoc fixItLoc = getFixItLocForVarToLet(diagVar);
6295+
if (fixItLoc.isValid())
6296+
diag.fixItReplace(fixItLoc, "let");
6297+
}
6298+
6299+
auto mainActor = var->getASTContext().getMainActorType();
6300+
if (mainActor) {
6301+
diagVar
6302+
->diagnose(diag::add_globalactor_to_decl, mainActor->getString(),
6303+
diagVar, mainActor)
6304+
.fixItInsert(diagVar->getAttributeInsertionLoc(false),
6305+
diag::insert_globalactor_attr, mainActor);
6306+
}
6307+
diagVar->diagnose(diag::shared_state_nonisolated_unsafe, diagVar)
6308+
.fixItInsert(diagVar->getAttributeInsertionLoc(true),
6309+
"nonisolated(unsafe) ");
63026310
}
63036311

63046312
bool swift::contextRequiresStrictConcurrencyChecking(

test/Concurrency/global_variables.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
@globalActor
1010
actor TestGlobalActor {
11-
static var shared = TestGlobalActor()
11+
static let shared = TestGlobalActor()
1212
}
1313

1414
@TestGlobalActor
@@ -72,6 +72,38 @@ struct TestStatics {
7272
public actor TestPublicActor {
7373
nonisolated(unsafe) let immutableNonisolatedUnsafeSendable = TestSendable()
7474
// expected-warning@-1 {{'(unsafe)' is unnecessary for a constant public actor property with 'Sendable' type 'TestSendable', consider removing it}} {{14-22=}}
75+
76+
// https://github.com/swiftlang/swift/issues/78435
77+
static var actorStatic = 0
78+
// expected-error@-1 {{static property 'actorStatic' is not concurrency-safe because it is nonisolated global shared mutable state}}
79+
// expected-note@-2{{convert 'actorStatic' to a 'let' constant to make 'Sendable' shared state immutable}}{{10-13=let}}
80+
// expected-note@-3{{disable concurrency-safety checks if accesses are protected by an external synchronization mechanism}}{{3-3=nonisolated(unsafe) }}
81+
// expected-note@-4{{add '@MainActor' to make static property 'actorStatic' part of global actor 'MainActor'}}{{3-3=@MainActor }}
82+
}
83+
84+
enum EnumSpace {
85+
static let enumStaticLet = TestSendable()
86+
87+
static var enumStatic = 0
88+
// expected-error@-1 {{static property 'enumStatic' is not concurrency-safe because it is nonisolated global shared mutable state}}
89+
// expected-note@-2{{convert 'enumStatic' to a 'let' constant to make 'Sendable' shared state immutable}}{{10-13=let}}
90+
// expected-note@-3{{disable concurrency-safety checks if accesses are protected by an external synchronization mechanism}}{{3-3=nonisolated(unsafe) }}
91+
// expected-note@-4{{add '@MainActor' to make static property 'enumStatic' part of global actor 'MainActor'}}{{3-3=@MainActor }}
92+
}
93+
94+
@TestGlobalActor
95+
enum IsolatedEnumSpace {
96+
static let inferredGlobalActorStaticLet = TestSendable()
97+
98+
static var inferredGlobalActorStatic = 0
99+
}
100+
101+
public actor GlobalActorComputed: GlobalActor {
102+
static let storage = GlobalActorComputed()
103+
104+
public static var shared: GlobalActorComputed {
105+
Self.storage
106+
}
75107
}
76108

77109
@TestGlobalActor

0 commit comments

Comments
 (0)