Skip to content

Commit 25c2359

Browse files
authored
Merge pull request #82379 from gottesmm/release/6.2-152522631
[6.2][sema] Work around a double curry thunk actor isolation inference bug that is a knock on effect of ced96aa.
2 parents ca39957 + 845e1bc commit 25c2359

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)