error: fix macOS terminate hang + dump ObjC NSException details#1813
Open
lugt wants to merge 1 commit into
Open
error: fix macOS terminate hang + dump ObjC NSException details#1813lugt wants to merge 1 commit into
lugt wants to merge 1 commit into
Conversation
Two related fixes for the terminate handler on Apple platforms, so that uncaught exceptions (especially ObjC NSExceptions crossing the C++ boundary) actually crash with a non-zero exit and are diagnosable. 1. std::terminate() -> std::abort() in terminate_handler. On macOS the default terminate handler is libobjc's _objc_terminate(), which calls __cxa_rethrow on the still-in-flight exception. The rethrow_exception + catch(...) dispatch above does NOT clear the exception's __cxa "uncaught" state, so _objc_terminate would rethrow -> std::terminate -> _objc_terminate ..., looping forever instead of aborting. This affects ALL uncaught exceptions on macOS, not just ObjC. std::abort() terminates unconditionally (SIGABRT, exit 134). On other platforms the default handler already aborts, so the behaviour is equivalent. 2. Dump ObjC NSException details (name/reason/callStack) from catch(...). An NSException thrown across a C++ boundary does not derive from std::exception and lands in catch(...) with its details lost. An ObjC exception preprocessor records the most recent NSException and its throw-time call stack per thread; dump_objc_exception() prints them. Recording at throw time (rather than rethrowing inside catch(...)) avoids disturbing the C++ exception runtime - an earlier rethrow-based attempt turned out to be the same class of bug as SFTtech#1. The .mm is built only on Apple (enable_language(OBJCXX) guarded by APPLE); other platforms get no-op stubs. Tested on macOS 26.3 / Apple Silicon (LLVM clang 22, Qt 6.11): - uncaught std::runtime_error -> FATAL + exception info + stack, exit 134 - uncaught ObjC NSException -> FATAL + name/reason/callStack, exit 134 Both previously hung in an infinite std::terminate <-> __terminate loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lugt
force-pushed
the
feature/objc-exception-dump-in-terminate
branch
from
July 17, 2026 06:35
95e5dd2 to
1f05ca7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related fixes for the terminate handler on Apple platforms, so uncaught exceptions (especially ObjC
NSExceptions crossing the C++ boundary) crash with a non-zero exit and are diagnosable. Currently on macOS they hang forever in an infinite loop.1.
std::terminate()→std::abort()(the real bug)On macOS the default terminate handler is libobjc's
_objc_terminate().terminate_handlerrestores it (std::set_terminate(old_terminate_handler)) then callsstd::terminate(). But_objc_terminatedoes:The
rethrow_exception(e_ptr)+catch(...)dispatch earlier interminate_handlerdoes not clear the exception's__cxa"uncaught" state, so_objc_terminatesees an in-flight exception,__cxa_rethrows it, the rethrow is again uncaught →std::terminate→_objc_terminate→__cxa_rethrow→ ... an infinitestd::terminate↔__terminateloop instead of aborting.This affects all uncaught exceptions on macOS (verified with a plain
std::runtime_error), not just ObjC. On Linux the default handler aborts directly without inspecting the exception, so it was never visible there.Fix:
std::abort()terminates unconditionally (SIGABRT, exit 134). On other platforms the default handler already aborts, so behaviour is equivalent.2. Dump ObjC NSException details from
catch(...)An
NSExceptionthrown across a C++ boundary does not derive fromstd::exceptionand lands incatch(...)with its name/reason/callStack lost.install_objc_exception_tracker()installs anobjc_setExceptionPreprocessorthat records the most recentNSExceptionand its throw-time call stack ([NSThread callStackSymbols]) per thread.dump_objc_exception()prints them from thecatch(...)arm.catch(...)) avoids disturbing the C++ exception runtime — an earlier rethrow-based attempt (objc_exception_rethrow/std::rethrow_exception+@catch) turned out to be the same class of bug as Age of Empires II (and Definitive Edition) is being preferred over openage #1.The
.mmis built only on Apple (enable_language(OBJCXX)guarded byAPPLE); other platforms get no-op stubs.Testing (macOS 26.3, Apple Silicon; Qt 6.11, LLVM clang 22)
std::runtime_errorNSExceptionlldb confirmed the loop root cause:
old_terminate_handlerresolves tolibobjc.A.dylib_objc_terminate(), which__cxa_rethrow`s the in-flight exception.Default-handler baseline (no libopenage linked): an uncaught ObjC
NSExceptionaborts with exit 134 via ObjC's own uncaught-exception path — confirming libopenage'sterminate_handleris what introduced the hang.Changes
libopenage/error/handlers.cpp—std::terminate()→std::abort(); calldump_objc_exception()incatch(...); install/uninstall the tracker alongside the existing SIGSEGV/terminate handlers.libopenage/error/objc_exception.h/.mm—install_objc_exception_tracker()/uninstall_objc_exception_tracker()/dump_objc_exception().CMakeLists.txt—enable_language(OBJCXX)on Apple.libopenage/error/CMakeLists.txt— buildobjc_exception.mmon Apple.Relation to PR #1812
Independent — based on
master(9a5a7ccb), zero file overlap with #1812. Can be merged separately, in either order.🤖 Generated with Claude Code