6

I've read How to demonstrate memory leak and zombie objects in Xcode Instruments? but that's for objective-c. The steps don't apply.

From reading here I've understood zombies are objects which are:

  • deallocated
  • but something pointer is still trying to point to them and send messages to them.

not exactly sure how that's different from accessing a deallocated object.

I mean in Swift you can do:

var person : Person? = Person(name: "John")
person = nil
print(person!.name)

Is person deallocated? Yes!

Are we trying to point to it? Yes!

So can someone share the most common mistake which leads to creating a dangling pointer?

mfaani
  • 33,269
  • 19
  • 164
  • 293
  • 1
    I wouldn't worry about that in Swift. Just make sure you don't use force-unwrapped optionals (I only ever use them for `IBOutlets`) and you won't have a problem. – EmilioPelaez Oct 29 '18 at 16:22
  • That's exactly what I thought. Does this [here](https://stackoverflow.com/questions/8840168/whats-the-difference-between-abandoned-memory-and-a-memory-leak) apply: _say you have a cache whose entries are instances of NSData that were downloaded from some URL where the URL contains a session ID in the URL and that session ID + URL are used as the key to look up stuff in the cache. Now, say the user logs out, causing the session ID to be destroyed. If the cache isn't also pruned of all entries specific to that session ID, then all of those NSData objects will be abandoned_ – mfaani Oct 29 '18 at 16:29
  • 3
    Note that the Swift example you give isn't an example of a dangling pointer – you're setting the reference to `nil`, meaning that you no longer have a reference to the object, regardless of whether it's still allocated. Perhaps the simplest example of obtaining an dangling pointer in Swift is with `Unmanaged`, e.g `class C {}; var c = C(); Unmanaged.passUnretained(c).release()`. `c` is now a dangling pointer. This isn't a "common mistake" though – and you should never be able to obtain a dangling pointer in Swift without dipping into such unsafe constructs (because Swift is a safe by default). – Hamish Oct 29 '18 at 16:29
  • 2
    That said, there is currently a footgun without temporary pointer conversions that can create dangling pointers, e.g `let ptr = UnsafePointer([1, 2, 3])` – `ptr` is a dangling pointer as the array-to-pointer conversion produces a pointer only valid for the duration of the call. Hoping to warn (and eventually error) on such conversions in https://github.com/apple/swift/pull/20070. – Hamish Oct 29 '18 at 16:50
  • Oops: *with temporary pointer conversions – Hamish Oct 29 '18 at 18:18

4 Answers4

6

This is not a dangling pointer or a zombie. When you use ! you're saying "if this is nil, then crash." You should not think of person as a pointer in Swift. It's a value. That value may be .some(T) or it may be .none (also called nil). Neither of those is dangling. They're just two different explicit values. Swift's nil is nothing like null pointers in other languages. It only crashes like null pointers when you explicitly ask it to.

To create zombies, you'll need to be using something like Unmanaged. This is extremely uncommon in Swift.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • From experience I know it's not a zombie. But based on what I'm reading, it fits the description ie. 1. It's deallocated 2. I'm pointing to it. So what am I missing? – mfaani Oct 29 '18 at 16:31
  • 1
    You're not pointing to it. There is no pointer in your code above. Swift pointers all have `Unsafe` in their names. – Rob Napier Oct 29 '18 at 16:32
  • I understand your (edited) answer now. But not your comment. If `Person` is a class type then isn't be pointed to using a pointer? Seems I'm not understanding something very foundational. Let me look more into `Unsafe` – mfaani Oct 29 '18 at 16:33
  • No. It's a strong reference. That's not the same thing as a pointer. See particularly the docs on `UnsafePointer` for the differences. Pointers are a low-level concept in Swift and not needed for most code. Most code deals in values and references. – Rob Napier Oct 29 '18 at 16:34
  • I don't understand the distinction. When I hear 'reference' I think of a pointer. Apparently I'm wrong. I'll look into the docs on `UnsafePointer`. Thanks – mfaani Oct 29 '18 at 16:36
  • 2
    @Honey References are implemented by pointers underneath, but then again, so are all branching statements (function calls, `return`, `if`/`else`), arrays, closures and many other entities. Pointers underpin *many* things, that doesn't mean those things are equivalent to pointers. – Alexander Oct 29 '18 at 17:17
  • 1
    @Honey In every case where a pointer underpins the implementation of one of the higher level abstractions I mentioned, there are some mechanisms implemented that distinguish the behavior of the abstraction from the behavior of naive pointer use. For example, a strong reference can never be a dangling reference. That's because a strong reference has special behavior that ensures that objects it point to are kept alive (by contributing to their non-zero retain count). – Alexander Oct 29 '18 at 17:22
  • @Honey On the other hand, naive pointer use can cause dangling references, because the thing they point to has no idea that its lifetime is being relied upon by an external entity (the pointer). Hyperlinks have the same issue. When I link to `foo.com`, The web server at `foo.com` has no idea that I made a link to it here. The server owner could shutdown the server at any point, breaking my `foo.com` link. That's the analog of a dangling pointer in the HTTP world. Now imagine if I had a hyperlink, but also e-mailed the owner, telling him I depend on his service, asking him to keep it up. – Alexander Oct 29 '18 at 17:24
  • @Honey He would have a "customer count" (analog to retain count), which he would see is non-zero, so he would courteously keep his server running to serve his customers, keeping our links from breaking. That's the analog of a strong reference. – Alexander Oct 29 '18 at 17:25
  • @Honey The key distinction between my "notify the service owner before starting to link to their service, and notify them after stopping my linking" and "just blindly link" is the distinction between strong refs and raw pointers. Notice that my scheme is still underpinned by hyperlinks, but with more constraints. – Alexander Oct 29 '18 at 17:30
  • @Alexander Thanks a lot. Just to be crystal clear. By strong ref you mean `var name: String`. By raw pointer you mean: `var myInt: UnsafePointer!` ? – mfaani Oct 29 '18 at 19:08
  • 1
    @Honey `String` is a struct, so it's directly stored inline, and not by pointer/reference. But something like `class C {}; let c = C()` is what I mean by a strong ref. And you're correct for pointer, although the `!` isn't relevant. – Alexander Oct 29 '18 at 20:25
6

Here's zombie attack in under 15 lines of code:

class Parent { }

class Child {
    unowned var parent: Parent // every child needs a parent

    init(parent: Parent) {
        self.parent = parent
    }
}

var parent: Parent? = Parent()
let child = Child(parent: parent!) // let's pretend the forced unwrap didn't happen
parent = nil // let's deallocate this bad parent
print(child.parent) // BOOM!!!, crash

What happens in this code is that Child holds an unowned reference to Parent, which becomes invalid once Parent gets deallocated. The reference holds a pointer to the no longer living parent (RIP), and accessing that causes a crash with a message similar to this:

Fatal error: Attempted to read an unowned reference but object 0x1018362d0 was already deallocated2018-10-29 20:18:39.423114+0200 MyApp[35825:611433] Fatal error: Attempted to read an unowned reference but object 0x1018362d0 was already deallocated

Note The code won't work in a Playground, you need a regular app for this.

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 4
    Technically speaking, that’s not a dangling pointer as Swift keeps track of unowned references in order to ensure it always traps on attempting to access it after all the strong references have gone. You can use `unowned(unsafe)` in order to remove this safety net and get an actual dangling pointer which yields undefined behaviour for an access after deallocation. – Hamish Oct 29 '18 at 18:28
  • @Hamish fair point, removed the "dangling pointer" reference. – Cristik Oct 29 '18 at 18:29
  • 2
    It may also be confusing to call this a "zombie" since it won't show up in anything zombie-related in the Cocoa ecosystem. It's a Swift-specific thing, an unowned reference, rather than a dangling pointer. But I think this adds a lot to the discussion (pointing out that there is a Swift-specific thing that can cause a memory-related spooky-crash-at-a-distance without Unsafe), as long as readers aren't led to believe that this has anything to do with NSZombie. – Rob Napier Oct 29 '18 at 19:15
  • also here you won't get the EXC_BAD_ACCESS that comes out of the same principle but doesn't let you know who is the parent, when it go deallocated, who is the child and when it tried to access the parent. This is the dramatic part of this problem. but great explanation – Cublax Oct 24 '20 at 21:01
5

Zombie objects are Objective-C objects which have been deallocated, but still receive messages.

In Objective-C, __unsafe_unretained can be used to create an additional pointer to an object which does not increase the reference count. In Swift that would be unowned(unsafe).

Here is a self-contained example:

import Foundation

class MyClass: NSObject {
    func foo() { print("foo"); }

    deinit { print("deinit") }
}

unowned(unsafe) let obj2: MyClass
do {
    let obj1 = MyClass()
    obj2 = obj1
    print("exit scope")
}
obj2.foo()

obj1 is a pointer to a newly created object, and obj2 is another pointer to the same object, but without increasing the reference counter. When the do { ... } block is left, the object is deallocated. Sending a message to it via obj2 causes the Zombie error:

exit scope
deinit
*** -[ZombieTest.MyClass retain]: message sent to deallocated instance 0x1005748d0

This can also happen if you use Managed (e.g. to convert pointers to Swift objects to C void pointers in order to pass them to C callback functions) and you don't choose the correct retained/unretained combination. A contrived example is:

let obj2: MyClass
do {
    let obj1 = MyClass()
    obj2 = Unmanaged.passUnretained(obj1).takeRetainedValue()
    print("exit scope")
}
obj2.foo()
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • "without increasing the reference counter" how is that different from a weak reference? – mfaani Jul 29 '21 at 22:03
  • A weak reference would be set to nil if the object is deallocated. But you would get the same Zombie message with a unowned(unsafe) reference (to an instance of a NSObject), so you are right that the Managed juggling is not needed. – So this is actually very similar to what Cristik said, only with unowned(unsafe) instead of unowned, and a subclass of NSObject, so that you really get the Zombie error message. – Martin R Jul 30 '21 at 04:14
1

Swift Zombie Object

Zombie Object is a deallocated object(ref count is nil) which behaves itself like alive(receive messages). It is possible because of dangling pointer.

dangling pointer points on some address in the memory where data is unpredictable

  • Objective-C has unsafe_unretained[About] property attributes. [Example]

  • Swift has

    • unowned(unsafe)[About] reference
    • Unmanaged - a wrapper for non ARC Objc- code
unowned(unsafe) let unsafeA: A

func main() {
    let a = A() // A(ref count = 1)
    unsafeA = a 
} // A(ref count = 0), deinit() is called

func additional() {
    unsafeA.foo() //<- unexpected
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205