Uploaded image for project: 'Swift'
  1. Swift
  2. SR-15240

async stack corruption when passing enums with associated values

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: Medium
    • Resolution: Done
    • Component/s: Compiler
    • Labels:
    • Environment:

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

      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)))
      

        Attachments

          Activity

            People

            Assignee:
            Unassigned Unassigned
            Reporter:
            taylorswift taylor swift
            Votes:
            4 Vote for this issue
            Watchers:
            13 Start watching this issue

              Dates

              Created:
              Updated:
              Resolved: