Skip to content

Commit c28490b

Browse files
committed
[sema] Work around a double curry thunk actor isolation inference bug that is a knock on effect of ced96aa.
Specifically, there is currently a bug in TypeCheckConcurrency.cpp where we do not visit autoclosures. This causes us to never set the autoclosure's ActorIsolation field like all other closures. For a long time we were able to get away with this just by relying on the isolation of the decl context of the autoclosure... but with the introduction of nonisolated(nonsending), we found cases where the generated single curry autoclosure would necessarily be different than its decl context (e.x.: a synchronous outer curry thunk that is nonisolated that returns an inner curry thunk that is nonisolated(nonsending)). This problem caused us to hit asserts later in the compiler since the inner closure was actually nonisolated(nonsending), but we were thinking that it should have been concurrent. To work around this problem, I changed the type checker in ced96aa to explicitly set the isolation of single curry thunk autoclosures when it generates them. The reason why we did this is that it made it so that we did not have to have a potential large source break in 6.2 by changing TypeCheckConcurrency.cpp to visit all autoclosures it has not been visiting. This caused a follow on issue where since we were now inferring the inner autoclosure to have the correct isolation, in cases where we were creating a double curry thunk for an access to a global actor isolated field of a non-Sendable non-global actor isolated nominal type, we would have the outer curry thunk have unspecified isolation instead of main actor isolation. An example of this is the following: ```swift class A { var block: @mainactor () -> Void = {} } class B { let a = A() func d() { a.block = c // Error! Passing task isolated 'self' to @mainactor closure. } @mainactor func c() {} } ``` This was unintentional. To work around this, this commit changes the type checker to explicitly set the double curry thunk isolation to the correct value when the type checker generates the double curry thunk in the same manner as it does for single curry thunks and validates that if we do set the value to something explicitly that it has the same value as the single curry thunk. rdar://152522631
1 parent 286f975 commit c28490b

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)