Skip to content

Commit 50c9826

Browse files
authored
Merge pull request #38043 from kavon/5.5-restricted-self-actorinit
[5.5] Restrict uses of 'self' in actor initializers
2 parents d572e13 + 995cb48 commit 50c9826

File tree

8 files changed

+690
-33
lines changed

8 files changed

+690
-33
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,22 @@ 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+
NOTE(actor_convenience_init,none,
181+
"convenience initializers allow non-isolated use of 'self' once "
182+
"initialized",
183+
())
184+
185+
186+
171187

172188
ERROR(variable_addrtaken_before_initialized,none,
173189
"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: 134 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,21 @@ 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,
500+
bool suggestConvenienceInit) const;
501+
502+
void handleLoadUseFailureForActorInit(const DIMemoryUse &Use,
503+
ActorInitKind ActorKind) const;
504+
483505
void handleLoadUseFailure(const DIMemoryUse &Use,
484506
bool SuperInitDone,
485507
bool FailedSelfUse);
@@ -866,9 +888,13 @@ void LifetimeChecker::doIt() {
866888

867889
void LifetimeChecker::handleLoadUse(const DIMemoryUse &Use) {
868890
bool IsSuperInitComplete, FailedSelfUse;
891+
ActorInitKind ActorKind = ActorInitKind::None;
869892
// If the value is not definitively initialized, emit an error.
870893
if (!isInitializedAtUse(Use, &IsSuperInitComplete, &FailedSelfUse))
871894
return handleLoadUseFailure(Use, IsSuperInitComplete, FailedSelfUse);
895+
// Check if it involves 'self' in a restricted actor init.
896+
if (isRestrictedActorInitSelf(Use, &ActorKind))
897+
return handleLoadUseFailureForActorInit(Use, ActorKind);
872898
}
873899

874900
static void replaceValueMetatypeInstWithMetatypeArgument(
@@ -1177,6 +1203,7 @@ static FullApplySite findApply(SILInstruction *I, bool &isSelfParameter) {
11771203

11781204
void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
11791205
bool IsSuperInitDone, FailedSelfUse;
1206+
ActorInitKind ActorKind = ActorInitKind::None;
11801207

11811208
// inout uses are generally straight-forward: the memory must be initialized
11821209
// before the "address" is passed as an l-value.
@@ -1195,6 +1222,11 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
11951222
return;
11961223
}
11971224

1225+
// 'self' cannot be passed 'inout' from some kinds of actor initializers.
1226+
if (isRestrictedActorInitSelf(Use, &ActorKind))
1227+
reportIllegalUseForActorInit(Use, ActorKind, "be passed 'inout'",
1228+
/*suggestConvenienceInit=*/false);
1229+
11981230
// One additional check: 'let' properties may never be passed inout, because
11991231
// they are only allowed to have their initial value set, not a subsequent
12001232
// overwrite.
@@ -1357,12 +1389,20 @@ static bool isLoadForReturn(SingleValueInstruction *loadInst) {
13571389
}
13581390

13591391
void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
1392+
13601393
// The value must be fully initialized at all escape points. If not, diagnose
13611394
// the error.
13621395
bool SuperInitDone, FailedSelfUse, FullyUninitialized;
1396+
ActorInitKind ActorKind = ActorInitKind::None;
13631397

13641398
if (isInitializedAtUse(Use, &SuperInitDone, &FailedSelfUse,
13651399
&FullyUninitialized)) {
1400+
1401+
// no escaping uses of 'self' are allowed in restricted actor inits.
1402+
if (isRestrictedActorInitSelf(Use, &ActorKind))
1403+
reportIllegalUseForActorInit(Use, ActorKind, "be captured by a closure",
1404+
/*suggestConvenienceInit=*/true);
1405+
13661406
return;
13671407
}
13681408

@@ -1746,6 +1786,100 @@ bool LifetimeChecker::diagnoseReturnWithoutInitializingStoredProperties(
17461786
return true;
17471787
}
17481788

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

0 commit comments

Comments
 (0)