swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.58k stars 10.35k forks source link

block with inout parameter that returns a sending value crashes #75982

Open mattmassicotte opened 2 months ago

mattmassicotte commented 2 months ago

Description

I think this has something to do with the inout + sending combination in the with function. Removing either allows the code to be processed succesfully.

Reproduction

public class Context {
    let value: Int

    init(value: Int) {
        self.value = value
    }
}

public struct Transaction {
    public static func with<T>(block: (inout Transaction) throws -> sending T) throws -> sending T {
        var txn = Transaction()

        return try block(&txn)
    }
}

actor MyActor {
    private var context = Context(value: 0)

    public func withContext<T>(_ block: sending (Context) throws -> T) async throws -> sending T {
        let value: T = try Transaction.with { txn in
            return try block(context)
        }

        // ERROR: returning 'self'-isolated 'value' as a 'sending' result risks causing data races
        return value
    }
}

Stack dump

1.  Apple Swift version 6.0-dev (LLVM 311c8b35213308e, Swift 488a207cc9d2994)
2.  Compiling with the current language version
3.  While evaluating request ExecuteSILPipelineRequest(Run pipelines { Mandatory Diagnostic Passes + Enabling Optimization Passes } on SIL for test)
4.  While running pass #225 SILFunctionTransform "TransferNonSendable" on SILFunction "@$s4test7MyActorC11withContextyxxAA0E0CKXEYaKlFxAA11TransactionVzKYTXEfU_".
 for expression at [test.swift:21:39 - line:23:3] RangeText="{ txn in
            return try block(context)
        "
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x0000000105d4eab4 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend           0x0000000105d4d280 llvm::sys::RunSignalHandlers() + 112
2  swift-frontend           0x0000000105d4f0fc SignalHandler(int) + 304
3  libsystem_platform.dylib 0x00000001965eb584 _sigtramp + 56
4  libsystem_pthread.dylib  0x00000001965bac20 pthread_kill + 288
5  libsystem_c.dylib        0x00000001964c7a30 abort + 180
6  libsystem_c.dylib        0x00000001964c6d20 err + 0
7  swift-frontend           0x0000000105f34d24 void swift::Diagnostic::gatherArgs<swift::DeclBaseName, bool, llvm::StringRef>(swift::DeclBaseName, bool, llvm::StringRef) (.cold.1) + 0
8  swift-frontend           0x00000001011e7e18 swift::ApplyInstBase<swift::TryApplyInst, swift::TryApplyInstBase, true>::getSelfArgument() const + 112
9  swift-frontend           0x00000001012862d0 (anonymous namespace)::TransferNonSendableImpl::emitDiagnostics() + 5784
10 swift-frontend           0x0000000101284a68 (anonymous namespace)::TransferNonSendable::run() + 528
11 swift-frontend           0x00000001012a916c swift::SILPassManager::runPassOnFunction(unsigned int, swift::SILFunction*) + 1432
12 swift-frontend           0x00000001012a9fd8 swift::SILPassManager::runFunctionPasses(unsigned int, unsigned int) + 1060
13 swift-frontend           0x00000001012ac744 swift::SILPassManager::execute() + 596
14 swift-frontend           0x00000001012a714c swift::SILPassManager::executePassPipelinePlan(swift::SILPassPipelinePlan const&) + 72
15 swift-frontend           0x00000001012a70cc swift::ExecuteSILPipelineRequest::evaluate(swift::Evaluator&, swift::SILPipelineExecutionDescriptor) const + 68
16 swift-frontend           0x00000001012e24d4 swift::SimpleRequest<swift::ExecuteSILPipelineRequest, std::__1::tuple<> (swift::SILPipelineExecutionDescriptor), (swift::RequestFlags)1>::evaluateRequest(swift::ExecuteSILPipelineRequest const&, swift::Evaluator&) + 28
17 swift-frontend           0x00000001012c2a30 swift::ExecuteSILPipelineRequest::OutputType swift::Evaluator::getResultUncached<swift::ExecuteSILPipelineRequest, swift::ExecuteSILPipelineRequest::OutputType swift::evaluateOrFatal<swift::ExecuteSILPipelineRequest>(swift::Evaluator&, swift::ExecuteSILPipelineRequest)::'lambda'()>(swift::ExecuteSILPipelineRequest const&, swift::ExecuteSILPipelineRequest::OutputType swift::evaluateOrFatal<swift::ExecuteSILPipelineRequest>(swift::Evaluator&, swift::ExecuteSILPipelineRequest)::'lambda'()) + 204
18 swift-frontend           0x00000001012a7328 swift::executePassPipelinePlan(swift::SILModule*, swift::SILPassPipelinePlan const&, bool, swift::irgen::IRGenModule*) + 64
19 swift-frontend           0x00000001012c4ef8 swift::runSILDiagnosticPasses(swift::SILModule&) + 192
20 swift-frontend           0x0000000100ae3b94 swift::CompilerInstance::performSILProcessing(swift::SILModule*) + 80
21 swift-frontend           0x00000001008a2e70 performCompileStepsPostSILGen(swift::CompilerInstance&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, int&, swift::FrontendObserver*) + 796
22 swift-frontend           0x00000001008a276c swift::performCompileStepsPostSema(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 1216
23 swift-frontend           0x00000001008ae248 withSemanticAnalysis(swift::CompilerInstance&, swift::FrontendObserver*, llvm::function_ref<bool (swift::CompilerInstance&)>, bool) + 160
24 swift-frontend           0x00000001008a4414 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 708
25 swift-frontend           0x00000001008a3ae8 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 2344
26 swift-frontend           0x00000001006b8e5c swift::mainEntry(int, char const**) + 3096
27 dyld                     0x00000001962320e0 start + 2360

Expected behavior

I think it's still a little unclear if this code should actually compile cleanly or produce an error due to:

https://github.com/swiftlang/swift/issues/75473

Environment

Apple Swift version 6.0-dev (LLVM 311c8b35213308e, Swift 488a207cc9d2994) Target: arm64-apple-macosx14.0

Additional information

No response

gottesmm commented 2 months ago

This doesn't crash with my fix from https://github.com/swiftlang/swift/pull/76076.

Now I get:

test.swift:22:15: error: returning 'self'-isolated 'self.context' as a 'sending' result risks causing data races
20 |    public func withContext<T>(_ block: sending (Context) throws -> T) async throws -> sending T {
21 |        let value: T = try Transaction.with { txn in
22 |            return try block(context)
   |               |- error: returning 'self'-isolated 'self.context' as a 'sending' result risks causing data races
   |               `- note: returning 'self'-isolated 'self.context' risks causing data races since the caller assumes that 'self.context' can be safely sent to other isolation domains
23 |        }
24 | 

There is something funky going on here. I am going out of town, I'll look further when I get back.

gottesmm commented 2 months ago

I do think that there does need to be an error here, just the error is messed up in some way.

.with expects its closure parameter to have a sending result... so the closure has a sending result... but block does not have a sending result. So you are returning a non-sending thing into a sending thing. Since context is actor isolated and the result of body is not sending, I would expect its result to be actor isolated as well... and one cannot return an actor isolated value as a sending result.

mattmassicotte commented 2 months ago

Yes, I agree with your analysis here. I think (hope) that had I not run into other issues, I would have figured that out myself. I'll be satisified with the crash resolved, but if you want to use this issue to look more closely at the error I'm all for it.

gottesmm commented 2 months ago

Yes. While I am here, I want to fix this.