Skip to content

Commit 97c3bf3

Browse files
committed
[rbi] Remove code that caused us to misidentify certain captured parameters as sending.
Specifically, this code was added because otherwise we would in swift 5 + strict-concurrency mode emit two warnings, one at the AST level and one at the SIL level. Once we are in swift-6 mode, this does not happen since we stop compiling at the AST level since we will emit the AST level diagnostic as an error. To do this, we tried to pattern match what the AST was erroring upon and treat the parameter as disconnected instead of being isolated. Sadly, this resulted in us treating certain closure cases incorrectly and not emit a diagnostic (creating a concurrency hole). Given that this behavior results in a bad diagnostic only to avoid emitting two diagnostics in a mode which is not going to last forever... it really doesn't make sense to keep it. We really need a better way to handle these sorts of issues. Perhaps a special semantic parameter put on the function that squelches certain errors. But that is something for another day. The specific case it messes up is: ``` class NonSendable { func action() async {} } @mainactor final class Foo { let value = NonSendable() func perform() { Task { [value] in await value.action() // Should emit error but do not. } } } ``` In this case, we think that value is sending... when it isnt and we should emit an error. rdar://146378329
1 parent d652100 commit 97c3bf3

File tree

5 files changed

+51
-39
lines changed

5 files changed

+51
-39
lines changed

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -537,34 +537,23 @@ static bool isAsyncLetBeginPartialApply(PartialApplyInst *pai) {
537537

538538
/// Returns true if this is a function argument that is able to be sent in the
539539
/// body of our function.
540+
///
541+
/// Needs to stay in sync with SILIsolationInfo::get(SILArgument *).
540542
static bool canFunctionArgumentBeSent(SILFunctionArgument *arg) {
541543
// Indirect out parameters can never be sent.
542544
if (arg->isIndirectResult() || arg->isIndirectErrorResult())
543545
return false;
544546

545-
// If we have a function argument that is closure captured by a Sendable
546-
// closure, allow for the argument to be sent.
547-
//
548-
// DISCUSSION: The reason that we do this is that in the case of us
549-
// having an actual Sendable closure there are two cases we can see:
550-
//
551-
// 1. If we have an actual Sendable closure, the AST will emit an
552-
// earlier error saying that we are capturing a non-Sendable value in a
553-
// Sendable closure. So we want to squelch the error that we would emit
554-
// otherwise. This only occurs when we are not in swift-6 mode since in
555-
// swift-6 mode we will error on the earlier error... but in the case of
556-
// us not being in swift 6 mode lets not emit extra errors.
557-
//
558-
// 2. If we have an async-let based Sendable closure, we want to allow
559-
// for the argument to be sent in the async let's statement and
560-
// not emit an error.
561-
//
562-
// TODO: Once the async let refactoring change this will no longer be needed
563-
// since closure captures will have sending parameters and be
564-
// non-Sendable.
565-
if (arg->isClosureCapture() &&
566-
arg->getFunction()->getLoweredFunctionType()->isSendable())
567-
return true;
547+
// If we have a closure capture...
548+
if (arg->isClosureCapture()) {
549+
// And that closure capture is from an async let, treat it as sending. This
550+
// is because we allow for disconnected values to be sent into async let
551+
// closures.
552+
if (auto declRef = arg->getFunction()->getDeclRef();
553+
declRef && declRef.isAsyncLetClosure) {
554+
return true;
555+
}
556+
}
568557

569558
// Otherwise, we only allow for the argument to be sent if it is explicitly
570559
// marked as a 'sending' parameter.

lib/SILOptimizer/Utils/SILIsolationInfo.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -995,11 +995,15 @@ SILIsolationInfo SILIsolationInfo::get(SILArgument *arg) {
995995
// async lets.
996996
//
997997
// This pattern should only come up with async lets. See comment in
998-
// isTransferrableFunctionArgument.
998+
// canFunctionArgumentBeSent in RegionAnalysis.cpp. This code needs to stay in
999+
// sync with that code.
9991000
if (!fArg->isIndirectResult() && !fArg->isIndirectErrorResult() &&
1000-
fArg->isClosureCapture() &&
1001-
fArg->getFunction()->getLoweredFunctionType()->isSendable())
1002-
return SILIsolationInfo::getDisconnected(false /*nonisolated(unsafe)*/);
1001+
fArg->isClosureCapture()) {
1002+
if (auto declRef = arg->getFunction()->getDeclRef();
1003+
declRef && declRef.isAsyncLetClosure) {
1004+
return SILIsolationInfo::getDisconnected(false /*nonisolated(unsafe)*/);
1005+
}
1006+
}
10031007

10041008
// Before we do anything further, see if we have an isolated parameter. This
10051009
// handles isolated self and specifically marked isolated.

test/Concurrency/sendable_checking.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,13 +463,15 @@ func preconcurrencyContext(_: @escaping @Sendable () -> Void) {}
463463
struct DowngradeForPreconcurrency {
464464
func capture(completion: @escaping @MainActor () -> Void) {
465465
preconcurrencyContext {
466-
Task {
467-
completion()
466+
Task { // expected-tns-warning {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
467+
completion() // expected-tns-note {{closure captures 'completion' which is accessible to code in the current task}}
468468
// expected-warning@-1 {{capture of 'completion' with non-Sendable type '@MainActor () -> Void' in a '@Sendable' closure}}
469469
// expected-warning@-2 {{capture of 'completion' with non-Sendable type '@MainActor () -> Void' in an isolated closure}}
470470
// expected-note@-3 2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
471471
// expected-warning@-4 {{expression is 'async' but is not marked with 'await'; this is an error in the Swift 6 language mode}}
472472
// expected-note@-5 {{calls to parameter 'completion' from outside of its actor context are implicitly asynchronous}}
473+
// expected-complete-and-tns-warning @-7 {{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
474+
// expected-complete-and-tns-note @-7 {{closure captures 'completion' which is accessible to code in the current task}}
473475
}
474476
}
475477
}

test/Concurrency/transfernonsendable.swift

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,13 +1671,12 @@ extension MyActor {
16711671
_ = self
16721672
_ = sc
16731673

1674-
Task { // expected-warning {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
1675-
// expected-note @-1 {{Passing value of non-Sendable type '() async -> ()' as a 'sending' argument to initializer 'init(name:priority:operation:)' risks causing races in between local and caller code}}
1676-
_ = sc
1674+
Task { // expected-warning {{passing closure as a 'sending' parameter risks causing data races between 'self'-isolated code and concurrent execution of the closure}}
1675+
_ = sc // expected-note {{closure captures 'self'-isolated 'sc'}}
16771676
}
16781677

1679-
Task { // expected-note {{access can happen concurrently}}
1680-
_ = sc
1678+
Task { // expected-warning {{passing closure as a 'sending' parameter risks causing data races between 'self'-isolated code and concurrent execution of the closure}}
1679+
_ = sc // expected-note {{closure captures 'self'-isolated 'sc'}}
16811680
}
16821681
}
16831682
}
@@ -2001,3 +2000,22 @@ nonisolated(nonsending) func testCallNonisolatedNonsending(_ x: NonSendableKlass
20012000
// expected-ni-note @-1 {{sending nonisolated(nonsending) task-isolated 'x' to nonisolated global function 'useValueAsyncConcurrent' risks causing data races between nonisolated and nonisolated(nonsending) task-isolated uses}}
20022001
// expected-ni-ns-note @-2 {{sending task-isolated 'x' to @concurrent global function 'useValueAsyncConcurrent' risks causing data races between @concurrent and task-isolated uses}}
20032002
}
2003+
2004+
func avoidThinkingClosureParameterIsSending() {
2005+
@MainActor
2006+
final class Foo {
2007+
let value = NonSendableKlass()
2008+
2009+
func perform() {
2010+
Task { [value] in
2011+
await value.asyncCall() // expected-ni-warning {{sending 'value' risks causing data races}}
2012+
// expected-ni-note @-1 {{sending main actor-isolated 'value' to nonisolated instance method 'asyncCall()' risks causing data races between nonisolated and main actor-isolated uses}}
2013+
}
2014+
2015+
Task {
2016+
await value.asyncCall() // expected-ni-warning {{sending 'self.value' risks causing data races}}
2017+
// expected-ni-note @-1 {{sending main actor-isolated 'self.value' to nonisolated instance method 'asyncCall()' risks causing data races between nonisolated and main actor-isolated uses}}
2018+
}
2019+
}
2020+
}
2021+
}

test/Concurrency/transfernonsendable_typed_errors.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,12 @@ extension MyActor {
6161
_ = self
6262
_ = sc
6363

64-
Task { // expected-error {{sending value of non-Sendable type '() async -> ()' risks causing data races}}
65-
// expected-note @-1 {{Passing value of non-Sendable type '() async -> ()' as a 'sending' argument to initializer 'init(name:priority:operation:)' risks causing races in between local and caller code}}
66-
_ = sc
64+
Task { // expected-error {{passing closure as a 'sending' parameter risks causing data races between 'self'-isolated code and concurrent execution of the closure}}
65+
_ = sc // expected-note {{closure captures 'self'-isolated 'sc'}}
6766
}
6867

69-
Task { // expected-note {{access can happen concurrently}}
70-
_ = sc
68+
Task { // expected-error {{passing closure as a 'sending' parameter risks causing data races between 'self'-isolated code and concurrent execution of the closure}}
69+
_ = sc // expected-note {{closure captures 'self'-isolated 'sc'}}
7170
}
7271
}
7372
}

0 commit comments

Comments
 (0)