Retain self in deinit.


(Alfred Zien) #1

Hi! Recently I discovered a way to make a bad access crash in somewhat unexpected circumstances. Code to reproduce:

import Foundation

class Storage<T> {
  var val : T?
}
class A {
  let s : Storage<A>
  init(s : Storage<A>) {
    self.s = s
  }
  deinit {
    s.val = self;
  }
}

var storage : Storage<A> = Storage()
var a : A? = A(s: storage)

a = nil

DispatchQueue.main.async {
  print(storage.val as Any)
}

dispatchMain()

We saving self in storage on deinit. Storage will retain `a`, but after it `a` will be destroyed. So, `val` will point to object that was deallocated. When we will try to access it, it will crash.

Can we do something about it? Of course, this very evil thing to do, but, unfortunately, swift allows to do that. I have two suggestions:
1. Completely forbid accessing self in deinit, allowing readonly access only to properties with default getter method. This is very strict, but will catch such problems in compile time.
2. The bigger problem here, not just that it will crash, but it will crash in very unpredictable place. So, we can make it crash just after deinit method, if retain count was changed after deinit.

I'm new to swift evolution, so please forgive me if I'm doing something wrong :slight_smile: Thanks!


(Joe Groff) #2

deinit isn't allowed to "resurrect" the object in this way by escaping new references to the dying object. We could, and probably should, have a runtime check to catch this during object destruction.

-Joe

···

On Jul 21, 2017, at 11:01 AM, Alfred Zien via swift-evolution <swift-evolution@swift.org> wrote:

Hi! Recently I discovered a way to make a bad access crash in somewhat unexpected circumstances. Code to reproduce:

import Foundation

class Storage<T> {
  var val : T?
}
class A {
  let s : Storage<A>
  init(s : Storage<A>) {
    self.s = s
  }
  deinit {
    s.val = self;
  }
}

var storage : Storage<A> = Storage()
var a : A? = A(s: storage)

a = nil

DispatchQueue.main.async {
  print(storage.val as Any)
}

dispatchMain()

We saving self in storage on deinit. Storage will retain `a`, but after it `a` will be destroyed. So, `val` will point to object that was deallocated. When we will try to access it, it will crash.

Can we do something about it? Of course, this very evil thing to do, but, unfortunately, swift allows to do that. I have two suggestions:
1. Completely forbid accessing self in deinit, allowing readonly access only to properties with default getter method. This is very strict, but will catch such problems in compile time.
2. The bigger problem here, not just that it will crash, but it will crash in very unpredictable place. So, we can make it crash just after deinit method, if retain count was changed after deinit.

I'm new to swift evolution, so please forgive me if I'm doing something wrong :slight_smile: Thanks!