Skip to content

Commit 845e1bc

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 (cherry picked from commit c28490b)
1 parent f589a7f commit 845e1bc

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
@@ -1312,6 +1312,21 @@ namespace {
13121312
return callExpr;
13131313
}
13141314

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

13721376
cs.cacheType(thunk);
@@ -1535,6 +1539,31 @@ namespace {
15351539
cs.cacheType(selfOpenedRef);
15361540
}
15371541

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

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

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

15911630
return outerThunk;

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3235,6 +3235,65 @@ namespace {
32353235
return LazyInitializerWalking::InAccessor;
32363236
}
32373237

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

33533412
partialApply->base->walk(*this);
33543413

3414+
// See if we have an autoclosure as our function. If so, check if we
3415+
// have a difference in isolation. If so, make this apply an
3416+
// isolation crossing apply.
3417+
//
3418+
// NOTE: This is just a work around for 6.2 to make checking of
3419+
// double curry thunks work correctly in the face of us not
3420+
// performing full type checking of autoclosures that are functions
3421+
// of the apply. We are doing this to make sure that we do not
3422+
// increase the surface area too much.
3423+
if (auto *fn = dyn_cast<AutoClosureExpr>(apply->getFn())) {
3424+
perform62AutoclosureCurryThunkChecking(apply, fn);
3425+
}
3426+
33553427
return Action::SkipNode(expr);
33563428
}
33573429
}
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
@@ -2057,11 +2057,14 @@ func inferLocationOfCapturedTaskIsolatedSelfCorrectly() {
20572057

20582058
func d() {
20592059
a.block = c // expected-warning {{converting non-Sendable function value to '@MainActor @Sendable () -> Void' may introduce data races}}
2060-
// expected-tns-warning @-1 {{sending 'self' risks causing data races}}
2061-
// 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}}
2060+
// expected-tns-warning @-1 {{non-Sendable '@MainActor () -> ()'-typed result can not be returned from main actor-isolated function to nonisolated context}}
2061+
// expected-tns-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
2062+
// expected-tns-warning @-3 {{sending 'self' risks causing data races}}
2063+
// 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}}
20622064
}
20632065

20642066
@MainActor
20652067
func c() {}
20662068
}
20672069
}
2070+

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)