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:
Kavon Farvardin
2021-11-02 18:23:19 -07:00
parent e6a1e23a9f
commit 4f28b87de9
17 changed files with 1607 additions and 419 deletions

View File

@@ -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())