Skip to content

Commit 2e61dfe

Browse files
committed
restrict uses of 'self' in certain actor initializers
A uniqueness rule for 'self' is enforced throughout the following types of actor initializers: 1. synchronous 2. global-actor isolated This means that 'self' cannot be used for anything, even after 'self' is fully initialized, other than for access to stored properties. Method calls are considered escaping uses of 'self', since it is passed implicitly to the method of computed property when invoked. Also, an actor's convenience init now treats self as nonisolated. This provides a way to perform follow-up work after self is fully initialized, when creating the actor from a synchronous context. Resolves rdar://78790369
1 parent f40d349 commit 2e61dfe

File tree

8 files changed

+614
-33
lines changed

8 files changed

+614
-33
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ ERROR(variable_defer_use_uninit,none,
168168
ERROR(self_closure_use_uninit,none,
169169
"'self' captured by a closure before all members were initialized", ())
170170

171+
/// false == sync; true == global-actor isolated
172+
ERROR(self_use_actor_init,none,
173+
"this use of actor 'self' %select{can only|cannot}0 appear in "
174+
"%select{an async|a global-actor isolated}0 initializer",
175+
(bool))
176+
ERROR(self_disallowed_actor_init,none,
177+
"actor 'self' %select{can only|cannot}0 %1 from "
178+
"%select{an async|a global-actor isolated}0 initializer",
179+
(bool, StringRef))
180+
181+
182+
171183

172184
ERROR(variable_addrtaken_before_initialized,none,
173185
"address of %select{variable|constant}1 '%0' taken before it is"

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,23 @@ bool DIMemoryObjectInfo::isElementLetProperty(unsigned Element) const {
432432
return false;
433433
}
434434

435+
ConstructorDecl *DIMemoryObjectInfo::isActorInitSelf() const {
436+
// is it 'self'?
437+
if (!MemoryInst->isVar())
438+
if (auto decl =
439+
dyn_cast_or_null<ClassDecl>(getASTType()->getAnyNominal()))
440+
// is it for an actor?
441+
if (decl->isActor())
442+
if (auto *silFn = MemoryInst->getFunction())
443+
// is it a designated initializer?
444+
if (auto *ctor = dyn_cast_or_null<ConstructorDecl>(
445+
silFn->getDeclContext()->getAsDecl()))
446+
if (ctor->isDesignatedInit())
447+
return ctor;
448+
449+
return nullptr;
450+
}
451+
435452
//===----------------------------------------------------------------------===//
436453
// DIMemoryUse Implementation
437454
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ class DIMemoryObjectInfo {
164164
return false;
165165
}
166166

167+
/// Returns the initializer if the memory use is 'self' and appears in an
168+
/// actor's designated initializer. Otherwise, returns nullptr.
169+
ConstructorDecl *isActorInitSelf() const;
170+
167171
/// True if this memory object is the 'self' of a derived class initializer.
168172
bool isDerivedClassSelf() const { return MemoryInst->isDerivedClassSelf(); }
169173

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@ namespace {
384384

385385
using BlockStates = BasicBlockData<LiveOutBlockState>;
386386

387+
enum class ActorInitKind {
388+
None, // not an actor init
389+
Plain, // synchronous, not isolated to global-actor
390+
PlainAsync, // asynchronous, not isolated to global-actor
391+
GlobalActorIsolated // isolated to global-actor (sync or async).
392+
};
393+
387394
/// LifetimeChecker - This is the main heavy lifting for definite
388395
/// initialization checking of a memory object.
389396
class LifetimeChecker {
@@ -480,6 +487,20 @@ namespace {
480487
bool diagnoseReturnWithoutInitializingStoredProperties(
481488
const SILInstruction *Inst, SILLocation loc, const DIMemoryUse &Use);
482489

490+
/// Returns true iff the use involves 'self' in a restricted kind of
491+
/// actor initializer. If a non-null Kind pointer was passed in,
492+
/// then the specific kind of restricted actor initializer will be
493+
/// written out. Otherwise, the None initializer kind will be written out.
494+
bool isRestrictedActorInitSelf(const DIMemoryUse& Use,
495+
ActorInitKind *Kind = nullptr) const;
496+
497+
void reportIllegalUseForActorInit(const DIMemoryUse &Use,
498+
ActorInitKind ActorKind,
499+
StringRef ProblemDesc) const;
500+
501+
void handleLoadUseFailureForActorInit(const DIMemoryUse &Use,
502+
ActorInitKind ActorKind) const;
503+
483504
void handleLoadUseFailure(const DIMemoryUse &Use,
484505
bool SuperInitDone,
485506
bool FailedSelfUse);
@@ -866,9 +887,13 @@ void LifetimeChecker::doIt() {
866887

867888
void LifetimeChecker::handleLoadUse(const DIMemoryUse &Use) {
868889
bool IsSuperInitComplete, FailedSelfUse;
890+
ActorInitKind ActorKind = ActorInitKind::None;
869891
// If the value is not definitively initialized, emit an error.
870892
if (!isInitializedAtUse(Use, &IsSuperInitComplete, &FailedSelfUse))
871893
return handleLoadUseFailure(Use, IsSuperInitComplete, FailedSelfUse);
894+
// Check if it involves 'self' in a restricted actor init.
895+
if (isRestrictedActorInitSelf(Use, &ActorKind))
896+
return handleLoadUseFailureForActorInit(Use, ActorKind);
872897
}
873898

874899
static void replaceValueMetatypeInstWithMetatypeArgument(
@@ -1177,6 +1202,7 @@ static FullApplySite findApply(SILInstruction *I, bool &isSelfParameter) {
11771202

11781203
void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
11791204
bool IsSuperInitDone, FailedSelfUse;
1205+
ActorInitKind ActorKind = ActorInitKind::None;
11801206

11811207
// inout uses are generally straight-forward: the memory must be initialized
11821208
// before the "address" is passed as an l-value.
@@ -1195,6 +1221,10 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
11951221
return;
11961222
}
11971223

1224+
// 'self' cannot be passed 'inout' from some kinds of actor initializers.
1225+
if (isRestrictedActorInitSelf(Use, &ActorKind))
1226+
reportIllegalUseForActorInit(Use, ActorKind, "be passed 'inout'");
1227+
11981228
// One additional check: 'let' properties may never be passed inout, because
11991229
// they are only allowed to have their initial value set, not a subsequent
12001230
// overwrite.
@@ -1357,12 +1387,19 @@ static bool isLoadForReturn(SingleValueInstruction *loadInst) {
13571387
}
13581388

13591389
void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
1390+
13601391
// The value must be fully initialized at all escape points. If not, diagnose
13611392
// the error.
13621393
bool SuperInitDone, FailedSelfUse, FullyUninitialized;
1394+
ActorInitKind ActorKind = ActorInitKind::None;
13631395

13641396
if (isInitializedAtUse(Use, &SuperInitDone, &FailedSelfUse,
13651397
&FullyUninitialized)) {
1398+
1399+
// no escaping uses of 'self' are allowed in restricted actor inits.
1400+
if (isRestrictedActorInitSelf(Use, &ActorKind))
1401+
reportIllegalUseForActorInit(Use, ActorKind, "be captured by a closure");
1402+
13661403
return;
13671404
}
13681405

@@ -1746,6 +1783,89 @@ bool LifetimeChecker::diagnoseReturnWithoutInitializingStoredProperties(
17461783
return true;
17471784
}
17481785

1786+
bool LifetimeChecker::isRestrictedActorInitSelf(const DIMemoryUse& Use,
1787+
ActorInitKind *Kind) const {
1788+
1789+
auto result = [&](ActorInitKind k, bool isRestricted) -> bool {
1790+
if (Kind)
1791+
*Kind = k;
1792+
return isRestricted;
1793+
};
1794+
1795+
// Currently: being synchronous, or global-actor isolated, means the actor's
1796+
// self is restricted within the init.
1797+
if (auto *ctor = TheMemory.isActorInitSelf()) {
1798+
if (getActorIsolation(ctor).isGlobalActor()) // global-actor isolated?
1799+
return result(ActorInitKind::GlobalActorIsolated, true);
1800+
else if (!ctor->hasAsync()) // synchronous?
1801+
return result(ActorInitKind::Plain, true);
1802+
else
1803+
return result(ActorInitKind::PlainAsync, false);
1804+
}
1805+
1806+
return result(ActorInitKind::None, false);
1807+
}
1808+
1809+
void LifetimeChecker::reportIllegalUseForActorInit(
1810+
const DIMemoryUse &Use,
1811+
ActorInitKind ActorKind,
1812+
StringRef ProblemDesc) const {
1813+
switch(ActorKind) {
1814+
case ActorInitKind::None:
1815+
case ActorInitKind::PlainAsync:
1816+
llvm::report_fatal_error("this actor init is never problematic!");
1817+
1818+
case ActorInitKind::Plain:
1819+
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_actor_init,
1820+
false, ProblemDesc);
1821+
break;
1822+
1823+
case ActorInitKind::GlobalActorIsolated:
1824+
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_actor_init,
1825+
true, ProblemDesc);
1826+
break;
1827+
}
1828+
}
1829+
1830+
void LifetimeChecker::handleLoadUseFailureForActorInit(
1831+
const DIMemoryUse &Use,
1832+
ActorInitKind ActorKind) const {
1833+
assert(TheMemory.isAnyInitSelf());
1834+
SILInstruction *Inst = Use.Inst;
1835+
1836+
// While enum instructions have no side-effects, they do wrap-up `self`
1837+
// such that it can escape our analysis. So, enum instruction uses are only
1838+
// acceptable if the enum is part of a specific pattern of usage that is
1839+
// generated for a failable initializer.
1840+
if (auto *enumInst = dyn_cast<EnumInst>(Inst)) {
1841+
if (isFailableInitReturnUseOfEnum(enumInst))
1842+
return;
1843+
1844+
// Otherwise, if the use has no possibility of side-effects, then its OK.
1845+
} else if (!Inst->mayHaveSideEffects()) {
1846+
return;
1847+
1848+
// While loads can have side-effects, we are not concerned by them here.
1849+
} else if (isa<LoadInst>(Inst)) {
1850+
return;
1851+
}
1852+
1853+
// Everything else is disallowed!
1854+
switch(ActorKind) {
1855+
case ActorInitKind::None:
1856+
case ActorInitKind::PlainAsync:
1857+
llvm::report_fatal_error("this actor init is never problematic!");
1858+
1859+
case ActorInitKind::Plain:
1860+
diagnose(Module, Use.Inst->getLoc(), diag::self_use_actor_init, false);
1861+
break;
1862+
1863+
case ActorInitKind::GlobalActorIsolated:
1864+
diagnose(Module, Use.Inst->getLoc(), diag::self_use_actor_init, true);
1865+
break;
1866+
}
1867+
}
1868+
17491869
/// Check and diagnose various failures when a load use is not fully
17501870
/// initialized.
17511871
///

0 commit comments

Comments
 (0)