From cad855ab5f18c8bf7338750b712989efd9617c95 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Sun, 19 Jul 2026 12:50:23 +0000 Subject: [PATCH 23/71] fix(macos): build ObjC++ frontend at C++98, with an optional gcc-4.2 override Replaces the -fnext-runtime guess. The launch crash (nil class in prepareForMethodLookup / objc_msgSendSuper from -[NSWindow makeFirstResponder:]) is the known modern-gcc Objective-C++ miscompile (fltk/fltk#1286, mplayer, blackomega). The established-good combination for such code is Apple gcc-4.2 at C++98; what is NOT yet established is whether modern gcc at C++98 also avoids it. The opaque SessionBridge means the .mm files depend on no C++23 engine code (only plain structs/pointers cross the boundary), so they can be compiled independently of the C++23 engine. Add two cache knobs on the `contour` target: CONTOUR_MACOS_OBJCXX_COMPILER -- compiler for the .mm files (e.g. /usr/bin/g++-4.2); empty = default (modern gcc). CONTOUR_MACOS_OBJCXX_STD -- C++ standard for the .mm files (default gnu++98). Experiment order: (1) modern gcc + gnu++98; (2) gcc-4.2 + gnu++98. Make SessionBridge.h and both .mm files strictly C++98-clean: no nullptr (-> NULL), no in-class member initializers, no enum-with-fixed-base, and C headers (//) rather than the C++ wrappers so an old compiler (gcc-4.2) can parse them. The C++23 engine is untouched. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01AeG2jwYMX5gdvvtUnx3PJa --- src/contour_macos/CMakeLists.txt | 38 ++++++++++++++++++++-------- src/contour_macos/SessionBridge.h | 42 ++++++++++++++++++------------- src/contour_macos/TerminalView.mm | 11 ++++---- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/contour_macos/CMakeLists.txt b/src/contour_macos/CMakeLists.txt index 40bd6489..8930f864 100644 --- a/src/contour_macos/CMakeLists.txt +++ b/src/contour_macos/CMakeLists.txt @@ -47,7 +47,28 @@ target_compile_options(contour_macos_render_probe PRIVATE ${_contour_macos_warn_ # The interactive app: AppKit window + view + input (Objective-C++). APPLE-only. if(APPLE) + # Modern GCC miscompiles Objective-C++ that talks to Apple's ObjC runtime: AppKit + # messaging our classes crashes (nil class in prepareForMethodLookup / objc_msgSendSuper), + # the same failure FLTK/mplayer/blackomega hit with modern GCC (fltk/fltk#1286). The known + # working combination is Apple's gcc-4.2 at C++98. The opaque SessionBridge makes the .mm + # files depend on no C++23 engine code (only plain structs/pointers cross the boundary), so + # we can compile them differently from the C++23 engine. Two independently-selectable knobs: + # + # CONTOUR_MACOS_OBJCXX_COMPILER -- path to a compiler for the .mm files (e.g. + # /usr/bin/g++-4.2). Empty = use the default (modern gcc). + # CONTOUR_MACOS_OBJCXX_STD -- C++ standard for the .mm files (default gnu++98). + # + # Experiment order: (1) modern gcc + gnu++98; (2) if still broken, gcc-4.2 + gnu++98. + set(CONTOUR_MACOS_OBJCXX_COMPILER "" CACHE STRING + "Compiler for the macOS ObjC++ frontend sources (empty = default). e.g. /usr/bin/g++-4.2") + set(CONTOUR_MACOS_OBJCXX_STD "gnu++98" CACHE STRING + "C++ standard for the macOS ObjC++ frontend sources.") + + if(CONTOUR_MACOS_OBJCXX_COMPILER) + set(CMAKE_OBJCXX_COMPILER "${CONTOUR_MACOS_OBJCXX_COMPILER}") + endif() enable_language(OBJCXX) + set(_app_sources TerminalView.mm main.mm @@ -58,17 +79,12 @@ if(APPLE) add_executable(contour ${_app_sources} ${_app_headers}) target_link_libraries(contour PRIVATE contour_macos) target_compile_options(contour PRIVATE ${_contour_macos_warn_opts}) - # GCC defaults to the GNU Objective-C runtime, but macOS AppKit uses Apple's NeXT - # runtime. Without this, AppKit messaging our classes finds a mismatched class - # structure and crashes in the ObjC runtime (nil class in prepareForMethodLookup). - # Emit NeXT-runtime ObjC so our classes interoperate with the system frameworks. - target_compile_options(contour PRIVATE $<$:-fnext-runtime>) - # ObjC++ sources need to compile as Objective-C++; .mm is auto-detected, but set the - # standard to match the engine (C++23) for the C++ portions they include. - set_target_properties(contour PROPERTIES - CXX_STANDARD 23 - OBJCXX_STANDARD 23 - ) + + # Force the ObjC++ standard explicitly (works even with gcc-4.2, which predates CMake's + # OBJCXX_STANDARD handling), and leave the target's plain C++ standard unset (no C++ TUs). + set_source_files_properties(${_app_sources} PROPERTIES + COMPILE_OPTIONS "-std=${CONTOUR_MACOS_OBJCXX_STD}") + # CoreGraphics is not a standalone linkable framework on 10.6 (like CoreText); its # symbols come via ApplicationServices. Link ApplicationServices, not CoreGraphics. target_link_libraries(contour PRIVATE diff --git a/src/contour_macos/SessionBridge.h b/src/contour_macos/SessionBridge.h index 81adb92e..8ba63be8 100644 --- a/src/contour_macos/SessionBridge.h +++ b/src/contour_macos/SessionBridge.h @@ -10,36 +10,44 @@ // below. The implementation (SessionBridge.cpp) is compiled as ordinary C++ and is the only // place that includes engine + session headers. -#include -#include +// C headers (not /): this header is included by the .mm files, which may be +// compiled by an old compiler (gcc-4.2) where the C++ wrappers are unreliable. The C +// headers put uint32_t/uint8_t/size_t in the global namespace, which is what this header uses. +#include +#include namespace contour_macos { class TerminalSession; // opaque to the .mm side +// NB: this header is included by the ObjC++ (.mm) files, which are compiled at -std=gnu++98 +// to avoid a modern-GCC Objective-C++ miscompile (see the app CMakeLists). It must therefore +// stay strictly C++98-clean: no in-class member initializers, no enum-with-fixed-base, no +// nullptr. The C++23 SessionBridge.cpp includes it too, where C++98 is a valid subset. + /// Host callbacks, invoked from the engine's parser thread. Each receives the opaque /// `userData` the bridge was created with (the TerminalView). Implementations must marshal -/// any UI work onto the main thread themselves. +/// any UI work onto the main thread themselves. Zero-initialize before use. struct BridgeCallbacks { - void* userData = nullptr; - void (*requestRedraw)(void* userData) = nullptr; - void (*setTitle)(void* userData, char const* utf8Title) = nullptr; - void (*bell)(void* userData) = nullptr; - void (*copyToClipboard)(void* userData, char const* utf8Data) = nullptr; - /// Returns a freshly malloc()'d UTF-8 C string the bridge will free(), or nullptr. - char* (*readClipboard)(void* userData) = nullptr; - void (*onClosed)(void* userData) = nullptr; + void* userData; + void (*requestRedraw)(void* userData); + void (*setTitle)(void* userData, char const* utf8Title); + void (*bell)(void* userData); + void (*copyToClipboard)(void* userData, char const* utf8Data); + /// Returns a freshly malloc()'d UTF-8 C string the bridge will free(), or null. + char* (*readClipboard)(void* userData); + void (*onClosed)(void* userData); }; /// Font configuration passed across the boundary as primitives. struct BridgeFontConfig { - char const* family = "Menlo"; - double sizePt = 14.0; - int dpiX = 96; - int dpiY = 96; + char const* family; + double sizePt; + int dpiX; + int dpiY; }; /// Creates a session sized to (widthPx, heightPx) and spawns the shell's read loop is NOT @@ -67,7 +75,7 @@ void bridge_resize(TerminalSession* session, int widthPx, int heightPx); // --- input (all coordinates in view pixels, top-left origin) --- /// Modifier bitmask matching the engine's Modifier enum: Shift=1 Alt=2 Control=4 Super=8. -enum BridgeModifier : uint32_t +enum BridgeModifier { BridgeMod_Shift = 1, BridgeMod_Alt = 2, @@ -76,7 +84,7 @@ enum BridgeModifier : uint32_t }; /// Special (non-text) keys, matching a subset of vtbackend::Key. -enum BridgeKey : int +enum BridgeKey { BridgeKey_None = 0, BridgeKey_Up, diff --git a/src/contour_macos/TerminalView.mm b/src/contour_macos/TerminalView.mm index e0bcbd05..a32ac2f2 100644 --- a/src/contour_macos/TerminalView.mm +++ b/src/contour_macos/TerminalView.mm @@ -9,8 +9,9 @@ #include -#include -#include +// C headers (the .mm may be built by gcc-4.2 under gnu++98): strdup lives in . +#include +#include namespace { @@ -97,9 +98,9 @@ char* cbReadClipboard(void* userData) NSPasteboard* pb = [NSPasteboard generalPasteboard]; NSString* s = [pb stringForType:NSPasteboardTypeString]; if (!s) - return nullptr; + return NULL; char const* utf8 = [s UTF8String]; - return utf8 ? strdup(utf8) : nullptr; + return utf8 ? strdup(utf8) : NULL; } void cbOnClosed(void* userData) @@ -162,7 +163,7 @@ void cbOnClosed(void* userData) { TerminalSession* s = [self session]; contour_macos::bridge_destroy(s); - _session = nullptr; + _session = NULL; [super dealloc]; }