“When you eliminate the impossible, whatever remains, however improbable, must be the truth.”
Sir Arthur Conan Doyle, The Sign of Four.
Many years ago I worked on a very cool project, Juke. It was one of the pioneering music apps from EU and boasted a catalogue of 57 million songs. The app was rewritten in a very unique tech stack pioneered by our maverick CTO Guillaume.
Here’s a screenshot I found in my archives.

A quick detour to the architecture.
The app used a handcrafted toolchain using Djinni framework1 from Dropbox. The idea was wild: Use the MVVM architecture, write the UI layers in Java/Objective-C and the common business logic in C++. The ViewModels and all the core business logic like networking, audio, storage, encryption etc was written in C++. The app used platform components like networking or CoreData using a thin wrapper over the native components.

I led the efforts to port the iOS part to Swift from Objective-C. While I was adept at writing ObjC, writing the ViewControllers in Swift was a breeze compared to ObjC. C++/Java devs found it easier to write Swift code too. I also helped setup the build system and CI to incorporate Swift into it. In the end the buildchain consisted of a diverse set of tools like Carthage, Gradle, Conan, Cucumber and each CI builds ran for 45 minutes on a good day. Writing and debugging cross platform C++ was a tedious job with lots of footguns. At one point I was so frustrated with C++ that I even explored Swift on Android2 and Rust3 as an alternative.
The feature
Apart from crisp clear audio quality, one of the most popular features of the app was the feature to store songs offline. The screenshot above shows the functionality. Android users could even save it on their SD cards. Designing this feature in a cross-platform way was an interesting challenge. The details are bit hazy but I remember we used a salted hash and a nonce to encrypt the songs before storing them locally. The UI layers would call the core C++ encrypt/decrypt functions to get the job done.
The bug
In September 2017 Apple released iOS 11. One Friday morning we got a bug report from an angry user who couldn’t play their ~1GB offline songs after upgrading to iOS 11.0.1. After debugging for some time with no success on iOS 11, it became clear to me that something in the toolchain broke during the OS upgrade. I immediately saved a song offline on an iOS 10 device and upgraded it to iOS 11. As expected the song refused to play after the upgrade. I had found the smoking gun!
Now that I have found a way to reproduce the bug, I was staring at the daunting task ahead of me. I had to manually debug through each step, in XCode, along a trail that went from Swift -> Objective-C -> Objective-C++ -> C++ and back. The bug could be anywhere in this chain.
The second problem was running out of time. Not only it was Friday evening but Apple’s release cadence made the deadline even tighter for the bugfix. Apple publishes only 2 .ipsw4 updates, current and previous version. They had already released an update for iOS 11.0.1 and they would stop signing iOS 10.3.3 (the last iOS 10 release) anytime. I had no option but to go to the office over the weekend and hope Apple wouldn’t release a new .ipsw that weekend5.
Thus began my epic marathon race to debug and fix this error.
- Downgrade to iOS 10.3.3
- Download a song and see if it plays.
- Meticulously debug step by step and note down every tiny detail.
- Upgrade to iOS 11.0.1
- See if the downloaded song plays
- Repeat step 3.
This is how the Xcode debugging window looked like by the way. Imagine the scroll blindness with these symbols. Picture from the Internet.

You might be wondering why I took this expensive path to debug this issue. That’s because I had no idea where in the chain the bug was. The logs were not helpful and only brute force approach helped me reproduce the bug. Nevertheless it was not pleasant. Downgrading and upgrading was time consuming and I couldn’t afford to miss any step. The worst part was I wasn’t even sure which of the 4 languages caused the bug. Finding the needle in the Swift/ObjC/ObjC++/C++ haystack was a nightmare. I threw my hands up after few attempts and went for a walk along the Spree6.
During flâneuring I figured out I was on the wrong track by brute forcing it to the max. I was stepping through each step, logging the output, inspecting the variables and noting down anything worth noting in my diary. Instead of debugging every step starting from the ViewController, I could simply start with the encryption and decryption and see where it fails. I ran back to the office and started the journey and added copious breakpoints and logs around these areas. Soon, which means few upgrades/downgrades later, I hit the jackpot.
The meticulous note taking and logs helped me in the end. One of the parameters in the Swift encrypt/decrypt function was an inout parameter and it was null in iOS 11.0.1! This was the parameter that was crucial in decrypting the song. Something must have changed with iOS 11 that caused this bug. With this information in hand, I created a small playground project to emulate the behaviour. The bug was reproducible and I could pinpoint it to the inout qualifier. In the end, I removed the inout qualifier in my iOS app and tested few times. It worked like a charm.
I released an app update without further delay as Apple took days to approve the app. I made a mental note to follow up on Monday, closed the case and went home. But after spending a weekend on this bug I had no patience to go through the Swift changelog and RFCs to find out the root cause. I fixed the bug but hadn’t caught the culprit.
Archeological discovery in 2026.
We’re back in 2026. LLMs and AI coding agents have upended the software industry with their seemingly infinite tenacity to trawl through complex codebases. I have fixed many bugs in my journey so far but somehow I had never got over this decade old bug. Recently I told a mentee that documenting all the quirky things you encounter in your work is the best way to start writing a blog. I suddenly had an epiphany: What if I use an LLM to go back in time and understand what could have happened? I quickly entered the details as I remembered them into a prompt and fired Claude Opus 4.6.
Years ago while working on a complex iOS project I fixed a strange bug. I need help to identify and recreate that bug.
--- The story as I remember it ---
We built a cross-platform mobile app using Android, iOS. The Android part was written in Java, the iOS part was in Swift and the common logic ViewModels etc was written in C++ (14 I guess) using Djinni framework from Dropbox (https://djinni.xlcpp.dev/). The app for a music app (Juke) which streamed the music from Juke and Saturn-MediaMarkt servers. There was also facility to save the tracks offline. These tracks were encrypter using a salted hash or something, which is going to be important for this bug. This was in Germany around 2016-2018.
The bug:
One user had saved ~1GB of tracks for offline listening. During one iOS upgrade the user couldn't listen to the songs. I identified the problem as an issue with the decryption. The control flow was swift -> objC -> Objc++ -> C++ and back. I had to debug it step by step by DOWNGRADING the iOS version, and UPGRADING again several times and debug step by step to find out what was happening. I remember the issue was with swift `inout` parameter from the decrypting functions. I had a limited time window because older iOS versions were not available once newer updates are launched.
--
Can you identify and help me with this?
Claude Opus did several web searches and came back with results that blew me away.
Your timeframe (2016-2018) maps to these key transitions:
| iOS Version | Swift Version | Key Change |
|---|---|---|
| iOS 10 | Swift 3.0 (Sep 2016) | SE-0107: UnsafeRawPointer introduced, strict aliasing rules formalized |
| iOS 11 | Swift 4.0 (Sep 2017) | SE-0176: Exclusive Access to Memory enforced. inout semantics changed fundamentally |
| iOS 12 | Swift 4.2 (Sep 2018) | Exclusivity enforcement fully enabled at runtime |
The Most Likely Bug: SE-0176 Changed inout Semantics
Opus correctly identified the changes made in Swift 4 were behind this bug. More interestingly, Opus identified that other details I did not specify.
-
The decryption was using an
inoutparameter which read the encrypted bytes and wrote back the decrypted bytes to the same buffer. -
I fixed the bug by using a normal var instead of the
inoutfor the parameter.
After reading the Claude summary and going through the docs, I can now finally connect the dots.
Swift 3.0 used copy-in/copy-out semantics but the compiler was not enforcing exclusive access in Swift 3.0. With SE-01767 Swift 4.0 enforced Law of Exclusivity, which meant two accesses to the same inout8 variable are truly exclusive.
When crossing the Swift -> ObjC -> C++ boundary, the compiler would materialize a temporary copy of the buffer, pass a pointer to the temporary buffer to the ObjC/C++ side, and then copy back. With iOS 10/Swift 3, it passed the pointer directly (pass-by-reference optimization). When Swift changed from pass-by-reference to copy-in/copy-out for the bridged call, the function read from the copy (all zeros or uninitialized) instead of the actual encrypted header, producing the wrong decryption key. I remember discovering the parameter as NULL in iOS 11 which makes this theory plausible.
Here is a visual representation of the bug.

Conclusion
After discovering this insight, I feel like an old cop that has finally solved the most perplexing case of his career. Perhaps I should revisit the iSeries or Java SwingWorker9 bugs I encountered in 2008!
LLMs are already disrupting the way we write software, but to discover its powers to debug age old issues is absolutely mindblowing. I have an answer to the hardest bug I fixed a decade ago, but it also underscored the importance of writing things down when you encounter them. I can’t wait to see what magic we continue to weave using these futuristic tools, and what kind of bug hunts we go on in this newly discovered universe.
-
https://gist.github.com/samkhawase/5e7c5a7a6d70d21c71416bd0c046f1e5 ↩
-
Apple released the next version 11.0.3 on October 11, 2017, but I had no way of knowing their release cadence. ↩
-
My office was at Wallstraße, and interestingly enough, it’s the same word behind Wall Street, NY. ↩
-
https://github.com/swiftlang/swift-evolution/blob/main/proposals/0176-enforce-exclusive-access-to-memory.md ↩
-
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/#In-Out-Parameters ↩
-
https://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html ↩