mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
basic implementation of flow-isolation for SE-327
Flow-isolation is a diagnostic SIL pass that finds unsafe accesses to properties in initializers and deinitializers that cannot gain isolation to otherwise protect those accesses from concurrent modifications. See SE-327 for more details about how and why it exists. This commit includes changes and features like: - The removal of the escaping-use restriction - Flow-isolation that works properly with `defer` statements - Flow-isolation with an emphasis on helpful diagnostics. It also includes known issues like: - Local / nonescaping functions are not analyzed by flow-isolation, despite it being technically possible. The main challenge in supporting it efficiently is that such functions do not have a single exit-point, like a `defer`. In particular, arbitrary functions can throw so there are points where nonisolation should _not_ flow out of the function at a call-site in the initializer, etc. - The implementation of the flow-isolation pass is not particularly memory efficient; it relies on BitDataflow even though the particular flow problem is simple. So, a more efficient implementation would be specialized for this particular problem, etc. There are also some changes to the Swift language itself: defer will respect its context when deciding its property access kind. Previously, a defer in an initializer would always access a stored property through its accessor methods, instead of doing so directly like its enclosing function might. This inconsistency is unfortunate, so for Swift 6+ we make this consistent. For Swift 5, only a defer in a function that is a member of the following kinds of types will gain this consistency: - an actor type - any nominal type that is actor-isolated, excluding UnsafeGlobalActor. These types are still rather new, so there is much less of a chance of breaking expected behaviors around defer. In particular, the danger is that users are relying on the behavior of defer triggering a property observer within an init or deinit, when it would not be triggering it without the defer.
This commit is contained in:
@@ -1985,6 +1985,45 @@ static bool isPolymorphic(const AbstractStorageDecl *storage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns true iff a defer's storage access kind should always
|
||||
/// match the access kind of its immediately enclosing function.
|
||||
///
|
||||
/// In Swift 5 and earlier, this was not true, meaning that property observers,
|
||||
/// etc, would be invoked in initializers or deinitializers if a property access
|
||||
/// happens within a defer, but not when outside the defer.
|
||||
static bool deferMatchesEnclosingAccess(const FuncDecl *defer) {
|
||||
assert(defer->isDeferBody());
|
||||
|
||||
// In Swift 6+, then yes.
|
||||
if (defer->getASTContext().isSwiftVersionAtLeast(6))
|
||||
return true;
|
||||
|
||||
// If the defer is part of a function that is a member of an actor or
|
||||
// concurrency-aware type, then yes.
|
||||
if (auto *deferContext = defer->getParent()) {
|
||||
if (auto *funcContext = deferContext->getParent()) {
|
||||
if (auto *type = funcContext->getSelfNominalTypeDecl()) {
|
||||
if (type->isAnyActor())
|
||||
return true;
|
||||
|
||||
switch (getActorIsolation(type)) {
|
||||
case swift::ActorIsolation::Unspecified:
|
||||
case swift::ActorIsolation::GlobalActorUnsafe:
|
||||
break;
|
||||
|
||||
case swift::ActorIsolation::ActorInstance:
|
||||
case swift::ActorIsolation::DistributedActorInstance:
|
||||
case swift::ActorIsolation::Independent:
|
||||
case swift::ActorIsolation::GlobalActor:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isDirectToStorageAccess(const DeclContext *UseDC,
|
||||
const VarDecl *var, bool isAccessOnSelf) {
|
||||
if (!var->hasStorage())
|
||||
@@ -1994,6 +2033,11 @@ static bool isDirectToStorageAccess(const DeclContext *UseDC,
|
||||
if (AFD == nullptr)
|
||||
return false;
|
||||
|
||||
// Check if this is a function representing a defer.
|
||||
if (auto *func = dyn_cast<FuncDecl>(AFD))
|
||||
if (func->isDeferBody() && deferMatchesEnclosingAccess(func))
|
||||
return isDirectToStorageAccess(func->getParent(), var, isAccessOnSelf);
|
||||
|
||||
// The property reference is for immediate class, not a derived class.
|
||||
if (AFD->getParent()->getSelfNominalTypeDecl() !=
|
||||
var->getDeclContext()->getSelfNominalTypeDecl())
|
||||
|
||||
Reference in New Issue
Block a user