DEV Community

Art Oz
Art Oz

Posted on • Edited on

I Asked Claude to Create a Memory Leak in a Task and It Failed

Last week, I asked Claude to create a memory leak in Swift's Task API. It gave me code and claimed the Task captures self strongly, creating a retain cycle. It suggested using [weak self].

I knew something was off. Tasks don't create retain cycles by default, that's literally their design advantage.

I diasgreed with him and he started to explain why I am right. The problem is that other than trusting his words I did not have any other way to prove I am right.

Swift Compiler

It turns out the Swift compiler creates something called SIL (Swift Intermediate Language) - an intermediate representation that shows exactly what the compiler does with your code before turning it into machine code.
This is the bash order you could use, preferably with “demangle” option, it’s just makes reading easier.

bash
swiftc -emit-sil MemoryLeak.swift | xcrun swift-demangle > output.sil

Output

 %14 = function_ref @(extension in Swift):Swift.Task< where B == Swift.Never>.init(priority: Swift.TaskPriority?, operation: __owned @isolated(any) () async -> A) -> Swift.Task<A, Swift.Never> : $@convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Sendable, τ_0_1 == Never> (@in Optional<TaskPriority>, @sil_sending @owned @isolated(any) @async @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for <τ_0_0>, @thin Task<τ_0_0, Never>.Type) -> @owned Task<τ_0_0, Never> // user: %15
  %15 = apply %14<(), Never>(%3, %13, %2) : $@convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Sendable, τ_0_1 == Never> (@in Optional<TaskPriority>, @sil_sending @owned @isolated(any) @async @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for <τ_0_0>, @thin Task<τ_0_0, Never>.Type) -> @owned Task<τ_0_0, Never> // user: %17
  dealloc_stack %3 : $*Optional<TaskPriority>     // id: %16
  release_value %15 : $Task<(), Never>            // id: %17
  %18 = tuple ()                                  // user: %19
  return %18 : $()                                // id: %19
} // end sil function 'MemoryLeak.LeakyViewController.startLeakyTask() -> ()'
Enter fullscreen mode Exit fullscreen mode

This is a part we need to analyze, the whole file is about 500 lines of code. If not demangling it I could look at the code infinitely.

Release Value

Lines that told the whole story:
release_value %15 : $Task<(), Never>

By release_value instruction:

  • Task created
  • Task handle released immediately
  • Task NOT stored No cycle created.

You can read about release_value here: [(https://apple-swift.readthedocs.io/en/latest/SIL.html#release-value)]

Analysis

Task → ViewController 

(one-way)
ViewController → Task? NO

No cycle = No leak
Enter fullscreen mode Exit fullscreen mode

The deinit WILL be called. There is no memory leak.

Conclusion

Trust, but verify. Especially with AI.

Top comments (0)