Skip to content

Commit aee16d7

Browse files
authored
Merge pull request #82375 from gottesmm/pr-4e28ed16e5bf409f1bdd07e56b1074a13efabb7d
[sema] Work around a double curry thunk actor isolation inference bug that is a knock on effect of ced96aa.
2 parents 1ceeb70 + c28490b commit aee16d7

File tree

5 files changed

+169
-15
lines changed

5 files changed

+169
-15
lines changed

lib/Sema/CSApply.cpp

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,21 @@ namespace {
13131313
return callExpr;
13141314
}
13151315

1316+
std::optional<ActorIsolation>
1317+
getIsolationFromFunctionType(FunctionType *thunkTy) {
1318+
switch (thunkTy->getIsolation().getKind()) {
1319+
case FunctionTypeIsolation::Kind::NonIsolated:
1320+
case FunctionTypeIsolation::Kind::Parameter:
1321+
case FunctionTypeIsolation::Kind::Erased:
1322+
return {};
1323+
case FunctionTypeIsolation::Kind::GlobalActor:
1324+
return ActorIsolation::forGlobalActor(
1325+
thunkTy->getIsolation().getGlobalActorType());
1326+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
1327+
return ActorIsolation::forCallerIsolationInheriting();
1328+
}
1329+
}
1330+
13161331
/// Build a "{ args in base.fn(args) }" single-expression curry thunk.
13171332
///
13181333
/// \param baseExpr The base expression to be captured, if warranted.
@@ -1355,19 +1370,8 @@ namespace {
13551370
// we do not visit the inner autoclosure in the ActorIsolation checker
13561371
// meaning that we do not properly call setActorIsolation on the
13571372
// AbstractClosureExpr that we produce here.
1358-
switch (thunkTy->getIsolation().getKind()) {
1359-
case FunctionTypeIsolation::Kind::NonIsolated:
1360-
case FunctionTypeIsolation::Kind::Parameter:
1361-
case FunctionTypeIsolation::Kind::Erased:
1362-
break;
1363-
case FunctionTypeIsolation::Kind::GlobalActor:
1364-
thunk->setActorIsolation(ActorIsolation::forGlobalActor(
1365-
thunkTy->getIsolation().getGlobalActorType()));
1366-
break;
1367-
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
1368-
thunk->setActorIsolation(
1369-
ActorIsolation::forCallerIsolationInheriting());
1370-
break;
1373+
if (auto isolation = getIsolationFromFunctionType(thunkTy)) {
1374+
thunk->setActorIsolation(*isolation);
13711375
}
13721376

13731377
cs.cacheType(thunk);
@@ -1536,6 +1540,31 @@ namespace {
15361540
cs.cacheType(selfOpenedRef);
15371541
}
15381542

1543+
auto outerActorIsolation = [&]() -> std::optional<ActorIsolation> {
1544+
auto resultType = outerThunkTy->getResult();
1545+
// Look through all optionals.
1546+
while (auto opt = resultType->getOptionalObjectType())
1547+
resultType = opt;
1548+
auto f =
1549+
getIsolationFromFunctionType(resultType->castTo<FunctionType>());
1550+
if (!f)
1551+
return {};
1552+
1553+
// If we have a non-async function and our "inferred" isolation is
1554+
// caller isolation inheriting, then we do not infer isolation and just
1555+
// use the default. The reason why we are doing this is:
1556+
//
1557+
// 1. nonisolated for synchronous functions is equivalent to
1558+
// nonisolated(nonsending).
1559+
//
1560+
// 2. There is a strong invariant in the compiler today that caller
1561+
// isolation inheriting is always async. By using nonisolated here, we
1562+
// avoid breaking that invariant.
1563+
if (!outerThunkTy->isAsync() && f->isCallerIsolationInheriting())
1564+
return {};
1565+
return f;
1566+
}();
1567+
15391568
Expr *outerThunkBody = nullptr;
15401569

15411570
// For an @objc optional member or a member found via dynamic lookup,
@@ -1566,6 +1595,11 @@ namespace {
15661595
auto *innerThunk = buildSingleCurryThunk(
15671596
selfOpenedRef, memberRef, cast<DeclContext>(member),
15681597
outerThunkTy->getResult()->castTo<FunctionType>(), memberLocator);
1598+
assert((!outerActorIsolation ||
1599+
innerThunk->getActorIsolation().getKind() ==
1600+
outerActorIsolation->getKind()) &&
1601+
"If we have an isolation for our double curry thunk it should "
1602+
"match our single curry thunk");
15691603

15701604
// Rewrite the body to close the existential if warranted.
15711605
if (hasOpenedExistential) {
@@ -1587,6 +1621,11 @@ namespace {
15871621
outerThunk->setThunkKind(AutoClosureExpr::Kind::DoubleCurryThunk);
15881622
outerThunk->setParameterList(
15891623
ParameterList::create(ctx, SourceLoc(), selfParamDecl, SourceLoc()));
1624+
1625+
if (outerActorIsolation) {
1626+
outerThunk->setActorIsolation(*outerActorIsolation);
1627+
}
1628+
15901629
cs.cacheType(outerThunk);
15911630

15921631
return outerThunk;

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3219,6 +3219,65 @@ namespace {
32193219
return LazyInitializerWalking::InAccessor;
32203220
}
32213221

3222+
/// This function is a stripped down version of checkApply that only is
3223+
/// applied to curry thunks generated by the type checker that explicitly
3224+
/// have isolation put upon them by the typechecker to work around a bug in
3225+
/// 6.2. We do not perform any sort of actual inference... we only use it to
3226+
/// mark the apply as being isolation crossing if we have an autoclosure
3227+
/// with mismatching isolation.
3228+
///
3229+
/// We take advantage that we only can have two types of isolation on such
3230+
/// an autoclosure, global actor isolation and nonisolated(nonsending).
3231+
///
3232+
/// For more information, see the comment in buildSingleCurryThunk.
3233+
void perform62AutoclosureCurryThunkChecking(ApplyExpr *apply,
3234+
AutoClosureExpr *fn) {
3235+
// The isolation of the context that we are in.
3236+
std::optional<ActorIsolation> contextIsolation;
3237+
auto getContextIsolation = [&]() -> ActorIsolation {
3238+
if (contextIsolation)
3239+
return *contextIsolation;
3240+
3241+
auto declContext = const_cast<DeclContext *>(getDeclContext());
3242+
contextIsolation =
3243+
getInnermostIsolatedContext(declContext, getClosureActorIsolation);
3244+
return *contextIsolation;
3245+
};
3246+
3247+
std::optional<ActorIsolation> unsatisfiedIsolation;
3248+
3249+
// NOTE: Normally autoclosures did not have ActorIsolation set on it since
3250+
// we do not visit the function of the partial apply due to a bug. The
3251+
// only reason why it is set is b/c we are explicitly setting this in the
3252+
// type checker when we generate the single and double curry thunks.
3253+
auto fnTypeIsolation = fn->getActorIsolation();
3254+
if (fnTypeIsolation.isGlobalActor()) {
3255+
Type globalActor = fnTypeIsolation.getGlobalActor();
3256+
if (!(getContextIsolation().isGlobalActor() &&
3257+
getContextIsolation().getGlobalActor()->isEqual(globalActor)))
3258+
unsatisfiedIsolation = ActorIsolation::forGlobalActor(globalActor);
3259+
}
3260+
3261+
// If there was no unsatisfied actor isolation, we're done.
3262+
if (!unsatisfiedIsolation)
3263+
return;
3264+
3265+
// Record whether the callee isolation or the context isolation
3266+
// is preconcurrency, which is used later to downgrade errors to
3267+
// warnings in minimal checking.
3268+
auto calleeDecl = apply->getCalledValue(/*skipFunctionConversions=*/true);
3269+
bool preconcurrency =
3270+
getContextIsolation().preconcurrency() ||
3271+
(calleeDecl && getActorIsolation(calleeDecl).preconcurrency());
3272+
unsatisfiedIsolation =
3273+
unsatisfiedIsolation->withPreconcurrency(preconcurrency);
3274+
3275+
// At this point, we know a jump is made to the callee that yields
3276+
// an isolation requirement unsatisfied by the calling context, so
3277+
// set the unsatisfiedIsolationJump fields of the ApplyExpr appropriately
3278+
apply->setIsolationCrossing(getContextIsolation(), *unsatisfiedIsolation);
3279+
}
3280+
32223281
PreWalkResult<Pattern *> walkToPatternPre(Pattern *pattern) override {
32233282
// Walking into patterns leads to nothing good because then we
32243283
// end up visiting the AccessorDecls of a top-level
@@ -3336,6 +3395,19 @@ namespace {
33363395

33373396
partialApply->base->walk(*this);
33383397

3398+
// See if we have an autoclosure as our function. If so, check if we
3399+
// have a difference in isolation. If so, make this apply an
3400+
// isolation crossing apply.
3401+
//
3402+
// NOTE: This is just a work around for 6.2 to make checking of
3403+
// double curry thunks work correctly in the face of us not
3404+
// performing full type checking of autoclosures that are functions
3405+
// of the apply. We are doing this to make sure that we do not
3406+
// increase the surface area too much.
3407+
if (auto *fn = dyn_cast<AutoClosureExpr>(apply->getFn())) {
3408+
perform62AutoclosureCurryThunkChecking(apply, fn);
3409+
}
3410+
33393411
return Action::SkipNode(expr);
33403412
}
33413413
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-frontend -typecheck -strict-concurrency=complete -target %target-swift-5.1-abi-triple %s
2+
3+
// REQUIRES: concurrency
4+
5+
// We used to crash on this when processing double curry thunks. Make sure that
6+
// we do not do crash in the future.
7+
extension AsyncStream {
8+
@Sendable func myCancel() {
9+
}
10+
func myNext2(_ continuation: UnsafeContinuation<Element?, Never>) {
11+
}
12+
func myNext() async -> Element? {
13+
await withTaskCancellationHandler {
14+
unsafe await withUnsafeContinuation {
15+
unsafe myNext2($0)
16+
}
17+
} onCancel: { [myCancel] in
18+
myCancel()
19+
}
20+
}
21+
}

test/Concurrency/transfernonsendable.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,11 +2084,14 @@ func inferLocationOfCapturedTaskIsolatedSelfCorrectly() {
20842084

20852085
func d() {
20862086
a.block = c // expected-warning {{converting non-Sendable function value to '@MainActor @Sendable () -> Void' may introduce data races}}
2087-
// expected-tns-warning @-1 {{sending 'self' risks causing data races}}
2088-
// expected-tns-note @-2 {{task-isolated 'self' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
2087+
// expected-tns-warning @-1 {{non-Sendable '@MainActor () -> ()'-typed result can not be returned from main actor-isolated function to nonisolated context}}
2088+
// expected-tns-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
2089+
// expected-tns-warning @-3 {{sending 'self' risks causing data races}}
2090+
// expected-tns-note @-4 {{task-isolated 'self' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses}}
20892091
}
20902092

20912093
@MainActor
20922094
func c() {}
20932095
}
20942096
}
2097+

test/Concurrency/transfernonsendable_global_actor.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,22 @@ func localCaptureDataRace5() {
338338

339339
x = 2 // expected-tns-note {{access can happen concurrently}}
340340
}
341+
342+
func inferLocationOfCapturedActorIsolatedSelfCorrectly() {
343+
class A {
344+
var block: @MainActor () -> Void = {}
345+
}
346+
@CustomActor
347+
class B {
348+
let a = A()
349+
350+
func d() {
351+
a.block = c // expected-warning {{converting non-Sendable function value to '@MainActor @Sendable () -> Void' may introduce data races}}
352+
// expected-warning @-1 {{non-Sendable '@MainActor () -> ()'-typed result can not be returned from main actor-isolated function to global actor 'CustomActor'-isolated context}}
353+
// expected-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
354+
}
355+
356+
@MainActor
357+
func c() {}
358+
}
359+
}

0 commit comments

Comments
 (0)