The patch I wrote for Ghostty is already in place, but I would definitely appreciate more definitive info and guidance around exception unwinding through Swift frames.
AIUI, the approach is correct. You will need to actually have an ObjC shim to catch the exception. Swift was designed without exceptions for various reasons (not trying to be opaque, but that is more a language design thing) - not trying to be dismissive of it, I agree with the rationale.
ObjC uses the itanium style exception handling similar to C++ (except where it doesn't and falls back to SjLj or SEH), but for the purposes of this topic, you can assume that the caveats are unimportant. Given that Swift does not support exceptions, the frontend does not generate a landingpad for the frame. As such, an exception propagating through the stack will not be possible to intercept in that activation record. As per the specification, unwinding through the foreign frame is UB - no unwind record (.eh_frame entry) is emitted nor (and obviously no LSDA/personality is associated for the catch) and thus the stack cannot be unwound.
If you wish to catch an ObjC exception, you will need to do that on the ObjC side. I suspect that the debug builds got away with it as the frames were canonical and simple enough that the unwinder was able to propagate through, though the catch would not occur in the Swift side, though it conceivable that it was caught in a deeper ObjC frame.
Note that none of this is Swift specific, it is simply that Swift does not participate in the itanium exception model as it does not have exceptions. Interop with any language which does not participate will be a barrier for exception propagation.
I should add that, when you find cases like this where Apple's APIs unavoidably raise an ObjC exception that is expected to be caught as part of their normal operation, it is worth filing a feedback report with Apple if you have the time to do so.
I've never understood why, on Obj-C platforms, Swift doesn't simply include a base Obj-C exception handler to ensure the exceptions are handled cleanly and obviously, and don't run the risk of silent success or have to worry about Swift stack mingling. This may be more of an Apple than Swift thing, but I really don't understand why these bombs are left for unsuspecting devs to discover.
Do you have a proposal for how that would function?
The entry point is Swift, which does not participate in exceptions. In order to do this, you would need a wrapper for each FFI call (negating the possibility of the optimizations we do today for interop). Also consider something like -fno-exceptions in C++ and __attribute__((__noexcept__)) (which technically is meant for async exceptions, but does in practice prevent an unwind entry from being emitted) in C which also allow some C/C++/ObjC functions to also present as barriers to exception propagation. Swift has the mechanism to protect the developer here: overlays allow you to translate the exception to an error (as was done in this case). This simply is not a Swift specific issue.
As @Joe_Groff mentioned, this is not something that is intentional - reporting the edge cases to Apple via Feedback allows the appropriate shims to be added.
Given it's UB, it's more likely to cause a crash than a silent success. As for exception handling, there is a default AppKit one. Unfortunately, Swift frames stand in the way and break it as the unwinding (the operation the system goes through when encountering an exception) does not understand the Swift stack frames and cannot safely make their way back up the call stack to where that base handler is (according to my understanding, as per @compnerd's above comments). Also, as they just now mentioned as I was typing this out, to mitigate this, they'd have to place handling code in front of the Swift frames, which isn't as simple as just including some code somewhere (it's dependent on how each Swift program calls into Objective-C).
I've considered this. My main concern is how much of a black hole Apple's feedback reporting system is. I don't have much faith that much will come of such a report (not only to mention the niche-ness of the bug).
It might be nice to have an unwind record that reliably triggers a crash when the unwinder tries to enter a Swift frame, so that it is at least immediately apparent that this is unsupported when this happens.
This is not true, either in general or in this specific case. The most common symptom of unwinding through a synchronous Swift frame—but not the only one—is leaking everything that was on the stack that Swift was expecting to get a chance to free.
Thanks for the clarification. You are correct here. I think I was weighing my own experience with this Ghostty bug too heavily when I made that statement.