Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-15240] async stack corruption when passing enums with associated values #57562

Closed
tayloraswift opened this issue Sep 24, 2021 · 2 comments
Closed
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself concurrency Feature: umbrella label for concurrency language features

Comments

@tayloraswift
Copy link
Contributor

Previous ID SR-15240
Radar rdar://problem/83635955
Original Reporter @Kelvin13
Type Bug
Status Resolved
Resolution Done
Environment

$ swiftc --version
Swift version 5.6-dev (LLVM a29f52d415422f3, Swift db90ea2)
Target: x86_64-unknown-linux-gnu

Additional Detail from JIRA
Votes 4
Component/s Compiler
Labels Bug, Concurrency
Assignee None
Priority Medium

md5: 87a891916f6d0d016927266c747ddfcd

Issue Description:

the following test program should give each user “guest” permissions, but gives them “admin” permissions instead!

// async-stack-corruption.swift 

struct Users
{
    enum Access 
    {
        case guest
        case admin(Int)
        case developer(Int, Int, Int, Int)
    }
    actor State  
    {
        init()
        {
        }
        func set(permissions:(user:Int, access:Access?)) 
        {
            print(permissions)
        }
    }
    
    let state:State = .init()
    
    func set(permissions:(user:Int, access:Access?)) async 
    {
        await self.state.set(permissions: permissions)
    }
}
@main 
enum Main 
{
    static 
    func main() async
    {
        let users:Users             = .init()
        let stream:AsyncStream<Int> = .init 
        {
            for i in 0 ..< 10
            {
                $0.yield(i) 
            }
            $0.finish()
        }
        for await i:Int in stream 
        {
            await users.set(permissions: (i, .guest))
        }
    }
}
$ swiftc -parse-as-library async-stack-corruption.swift 
$ ./async-stack-corruption 
(user: 0, access: Optional(main.Users.Access.admin(0)))
(user: 1, access: Optional(main.Users.Access.admin(0)))
(user: 2, access: Optional(main.Users.Access.admin(0)))
(user: 3, access: Optional(main.Users.Access.admin(0)))
(user: 4, access: Optional(main.Users.Access.admin(0)))
(user: 5, access: Optional(main.Users.Access.admin(0)))
(user: 6, access: Optional(main.Users.Access.admin(0)))
(user: 7, access: Optional(main.Users.Access.admin(0)))
(user: 8, access: Optional(main.Users.Access.admin(0)))
(user: 9, access: Optional(main.Users.Access.admin(0)))

Removing the associated value from the `admin` case will instead give them `developer` permissions!

The problem does not occur when the async stream is replaced with a normal `for` loop. Removing the user index causes the program to crash with a segmentation fault instead.

Fortunately, this issue is not present in the RELEASE-5.5 binary, only in the nightlies.

This issue is present in the 5.5-RELEASE toolchain. A modified reproduction is given below:

struct Users
{
    enum Access 
    {
        case guest
        case admin(Int)
        case developer(Int, Int, Int, Int)
    }
    
    private 
    actor User 
    {
        init()
        {
        }
        
        func set(permissions:(Int, Access?))
        {
            print(permissions)
        }
    }
    
    private 
    let users:[Int: User] = [0: .init()]
    
    func set(permissions:(Int, Access?)) async 
    {
        print(permissions)
        guard let user:User = self.users[permissions.0]
        else 
        {
            print(" \(permissions.0) ")
            return  
        }
        await user.set(permissions: permissions)
    }
}

@main 
enum Main 
{
    static 
    func main() async
    {
        let coordinator:Users = .init()
        let stream:AsyncStream<Int> = .init 
        {
            for i in 0 ..< 10
            {
                $0.yield(i) 
            }
            $0.finish()
        }

        for await i:Int in stream
        {
            if i != 0 
            {
                continue 
            }
            await coordinator.set(permissions: (i, .guest))
        }
    }
}

$ swiftc --version 
Swift version 5.5 (swift-5.5-RELEASE) 
Target: x86_64-unknown-linux-gnu 
$ swiftc -O -parse-as-library async-stack-corruption-5.5.swift 
$ ./async-stack-corruption-5.5 
(0, Optional(main.Users.Access.admin(144))) 
(0, Optional(main.Users.Access.admin(144)))
@benlangmuir
Copy link
Member

@swift-ci create

@tayloraswift
Copy link
Contributor Author

appears to be fixed, after testing with a locally-compiled swift toolchain

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself concurrency Feature: umbrella label for concurrency language features
Projects
None yet
Development

No branches or pull requests

2 participants