From c34795eefc3561e068ef883020665c444918c4b3 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Fri, 12 Jul 2024 21:49:35 -0700 Subject: [PATCH] [Concurrency] Diagnose captures of `self` in a task created in deinit. This is done by diagnosing captures of `self` in escaping `sending` or `@Sendable` closures inside a deinit, which almost certainly means `self` will outlive deinit at runtime, which is a fatal error. This is a common mistake to make when creating isolated tasks inside nonisolated deinits to workaround the lack of synchrnous isolated deinits in Swift 6. (cherry picked from commit 18b747c1815f5c82026b6c6e3a84a4b103fb74c5) --- include/swift/AST/DiagnosticsSema.def | 3 ++ lib/Sema/TypeCheckConcurrency.cpp | 20 +++++++++++- test/Concurrency/self_escapes_deinit.swift | 37 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 test/Concurrency/self_escapes_deinit.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 76627e870563c..ffd28324f115b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -5520,6 +5520,9 @@ ERROR(non_sendable_isolated_capture,none, ERROR(implicit_async_let_non_sendable_capture,none, "capture of %1 with non-sendable type %0 in 'async let' binding", (Type, DeclName)) +ERROR(self_capture_deinit_task,none, + "capture of 'self' in a closure that outlives deinit", + ()) ERROR(implicit_non_sendable_capture,none, "implicit capture of %1 requires that %0 conforms to `Sendable`", (Type, DeclName)) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index e50768b313fec..acb578d128a95 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -2793,6 +2793,25 @@ namespace { if (capture.isOpaqueValue()) continue; + auto *closure = localFunc.getAbstractClosureExpr(); + + // Diagnose a `self` capture inside an escaping `sending` + // `@Sendable` closure in a deinit, which almost certainly + // means `self` would escape deinit at runtime. + auto *explicitClosure = dyn_cast_or_null(closure); + auto *dc = getDeclContext(); + if (explicitClosure && isa(dc) && + !explicitClosure->getType()->isNoEscape() && + (explicitClosure->isPassedToSendingParameter() || + explicitClosure->isSendable())) { + auto var = dyn_cast_or_null(capture.getDecl()); + if (var && var->isSelfParameter()) { + ctx.Diags.diagnose(explicitClosure->getLoc(), + diag::self_capture_deinit_task) + .warnUntilSwiftVersion(6); + } + } + // If the closure won't execute concurrently with the context in // which the declaration occurred, it's okay. auto decl = capture.getDecl(); @@ -2817,7 +2836,6 @@ namespace { if (type->hasError()) continue; - auto *closure = localFunc.getAbstractClosureExpr(); if (closure && closure->isImplicit()) { auto *patternBindingDecl = getTopPatternBindingDecl(); if (patternBindingDecl && patternBindingDecl->isAsyncLet()) { diff --git a/test/Concurrency/self_escapes_deinit.swift b/test/Concurrency/self_escapes_deinit.swift new file mode 100644 index 0000000000000..352256dfdb861 --- /dev/null +++ b/test/Concurrency/self_escapes_deinit.swift @@ -0,0 +1,37 @@ +// RUN: %target-typecheck-verify-swift -strict-concurrency=complete -disable-availability-checking + +@MainActor +class C { + let x: Int = 0 + + deinit { + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + Task { @MainActor in + _ = self + } + + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + Task { + _ = x + } + } +} + +func enqueueSomewhereElse(_ closure: @escaping @Sendable () -> Void) {} + +@MainActor +class C2 { + let x: Int = 0 + + deinit { + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + enqueueSomewhereElse { + _ = self + } + + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + enqueueSomewhereElse { + _ = self.x + } + } +}