前置条件
- macOS Sequoia (15.2)
- MacBook Pro 2023-Apple M2 Pro (4能效核、8性能核、32GB内存、2TB磁盘)
- OpenSCAD 2024.12.13 (或更高版本)
- QScintilla 2.14.1
- Xcode 16.2
问题现象
输入法切换到 “简体拼音”, 然后按下自带键盘上的 左侧 Shift 与 - 按键,目的是按出下划线,会导致 OpenSCAD 闪退:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
------------------------------------- Translated Report (Full Report Below) ------------------------------------- Process: OpenSCAD [87606] Path: /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD Identifier: org.openscad.OpenSCAD Version: 2024.12 (2024.12.30) Code Type: ARM-64 (Native) Parent Process: launchd [1] User ID: 501 Date/Time: 2025-01-07 10:43:50.0163 +0800 OS Version: macOS 15.2 (24C101) Report Version: 12 Anonymous UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Sleep/Wake UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Time Awake Since Boot: 1700000 seconds Time Since Wake: 5033 seconds System Integrity Protection: enabled Crashed Thread: 0 Dispatch queue: com.apple.main-thread Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Termination Reason: Namespace SIGNAL, Code 6 Abort trap: 6 Terminating Process: OpenSCAD [87606] Application Specific Information: abort() called Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x18fec7720 __pthread_kill + 8 1 libsystem_pthread.dylib 0x18fefff70 pthread_kill + 288 2 libsystem_c.dylib 0x18fe0c908 abort + 128 3 libc++abi.dylib 0x18feb644c abort_message + 132 4 libc++abi.dylib 0x18fea4a24 demangling_terminate_handler() + 320 5 libobjc.A.dylib 0x18fb4d3f4 _objc_terminate() + 172 6 libc++abi.dylib 0x18feb5710 std::__terminate(void (*)()) + 16 7 libc++abi.dylib 0x18feb8cdc __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 88 8 libc++abi.dylib 0x18feb8c84 __cxa_throw + 92 9 libqscintilla2_qt6.15.dylib 0x1025f20f8 0x10245c000 + 1663224 10 libqscintilla2_qt6.15.dylib 0x1025bb4e8 0x10245c000 + 1438952 11 libqscintilla2_qt6.15.dylib 0x1025ed018 0x10245c000 + 1642520 12 libqscintilla2_qt6.15.dylib 0x10246f358 QsciScintillaBase::keyPressEvent(QKeyEvent*) + 448 13 QtWidgets 0x1039d17d8 QWidget::event(QEvent*) + 556 14 QtWidgets 0x103a4f780 QFrame::event(QEvent*) + 56 15 QtWidgets 0x103988c58 QApplicationPrivate::notify_helper(QObject*, QEvent*) + 272 16 QtWidgets 0x103989cec QApplication::notify(QObject*, QEvent*) + 972 17 OpenSCAD 0x100a85440 OpenSCADApp::notify(QObject*, QEvent*) + 28 18 QtCore 0x104dd124c QCoreApplication::notifyInternal2(QObject*, QEvent*) + 292 19 QtWidgets 0x1039e43a0 QWidgetWindow::event(QEvent*) + 168 20 QtWidgets 0x103988c58 QApplicationPrivate::notify_helper(QObject*, QEvent*) + 272 21 QtWidgets 0x103989af8 QApplication::notify(QObject*, QEvent*) + 472 22 OpenSCAD 0x100a85440 OpenSCADApp::notify(QObject*, QEvent*) + 28 23 QtCore 0x104dd124c QCoreApplication::notifyInternal2(QObject*, QEvent*) + 292 24 QtGui 0x102e1b110 QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyEvent*) + 260 25 QtGui 0x102e70c24 QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 396 26 QtGui 0x102e70810 QWindowSystemInterface::flushWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 284 27 libqcocoa.dylib 0x1038446d4 -[QNSView(Keys) handleKeyEvent:] + 1760 28 libqcocoa.dylib 0x10384540c -[QNSView(Keys) keyDown:] + 76 29 AppKit 0x193c77e5c -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 316 30 AppKit 0x193c77b50 -[NSWindow(NSEventRouting) sendEvent:] + 284 31 libqcocoa.dylib 0x10384d16c -[QNSWindow sendEvent:] + 824 32 AppKit 0x1944b4378 -[NSApplication(NSEventRouting) sendEvent:] + 2360 33 libqcocoa.dylib 0x1037f2330 -[QNSApplication sendEvent:] + 68 34 AppKit 0x1940bb4e8 -[NSApplication _handleEvent:] + 60 35 AppKit 0x193b44088 -[NSApplication run] + 520 36 libqcocoa.dylib 0x1037fde9c QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1904 37 QtCore 0x104ddabc4 QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 532 38 QtCore 0x104dd18d8 QCoreApplication::exec() + 112 39 OpenSCAD 0x100a34c9c gui(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>&, std::__1::__fs::filesystem::path const&, int, char**) + 2076 40 OpenSCAD 0x100716b10 main + 18796 41 dyld 0x18fb80274 start + 2840 |
问题复现
1. 打开 OpenSCAD,然后输入法选择 “简体拼音”:
2. 同时按下下图红色框中的左侧 Shift 与 - 按键:
问题定位
1. 下载编译 OpenSCAD 源代码:
1 2 3 4 5 6 7 8 9 10 11 |
$ git clone https://github.com/openscad/openscad.git $ cd openscad $ git submodule update --init --recursive $ ./scripts/macosx-build-homebrew.sh $ cmake -B build -DEXPERIMENTAL=1 -DCMAKE_BUILD_TYPE=Debug $ cmake --build build |
2. 下载编译 QScintilla 2.14.1 源代码:
1 2 3 4 5 6 7 8 9 |
$ wget https://www.riverbankcomputing.com/static/Downloads/QScintilla/2.14.1/QScintilla_src-2.14.1.tar.gz $ tar xvf QScintilla_src-2.14.1.tar.gz $ cd QScintilla_src-2.14.1/src $ qmake CONFIG+=debug qscintilla.pro $ make |
3. 替换 HomeBrew 安装目录下的非调试版本:
1 2 3 |
$ mv /opt/homebrew/Cellar/qscintilla2/2.14.1_4/lib/libqscintilla2_qt6.15.2.1.dylib /opt/homebrew/Cellar/qscintilla2/2.14.1_4/lib/libqscintilla2_qt6.15.2.1.dylib.bak $ cp libqscintilla2_qt6_debug.15.2.1.dylib /opt/homebrew/Cellar/qscintilla2/2.14.1_4/lib/libqscintilla2_qt6.15.2.1.dylib |
4. 启动 Xcode 选择 "Debug" -> "Attach to Process"。
问题解析
调试可以看到问题出在下图位置:
从上面的调用堆栈可以看到 macOS 的一次按键点击事件,给出了 2 个 UTF-8 字符 “——”,每个字符长度为 3 。而QT 的 QScintilla 库预期只会出现一个字符,导致给出的缓冲区不足,诱发异常抛出。
只是奇怪的是 OpenSCAD 的代码明明已经进行了相关的异常捕获,但是却毫无效果。
具体出问题的原因如下图:
具体代码片段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
size_t UTF32FromUTF8(const char *s, size_t len, unsigned int *tbuf, size_t tlen) { size_t ui = 0; for (size_t i = 0; i < len;) { unsigned char ch = s[i]; const unsigned int byteCount = UTF8BytesOfLead[ch]; unsigned int value; if (i + byteCount > len) { // Trying to read past end but still have space to write if (ui < tlen) { tbuf[ui] = ch; ui++; } break; } if (ui == tlen) { throw std::runtime_error("UTF32FromUTF8: attempted write beyond end"); } i++; switch (byteCount) { case 1: value = ch; break; case 2: value = (ch & 0x1F) << 6; ch = s[i++]; value += TrailByteValue(ch); break; case 3: value = (ch & 0xF) << 12; ch = s[i++]; value += TrailByteValue(ch) << 6; ch = s[i++]; value += TrailByteValue(ch); break; default: value = (ch & 0x7) << 18; ch = s[i++]; value += TrailByteValue(ch) << 12; ch = s[i++]; value += TrailByteValue(ch) << 6; ch = s[i++]; value += TrailByteValue(ch); break; } tbuf[ui] = value; ui++; } return ui; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// Handle key presses. void QsciScintillaBase::keyPressEvent(QKeyEvent *e) { int modifiers = 0; if (e->modifiers() & Qt::ShiftModifier) modifiers |= SCMOD_SHIFT; if (e->modifiers() & Qt::ControlModifier) modifiers |= SCMOD_CTRL; if (e->modifiers() & Qt::AltModifier) modifiers |= SCMOD_ALT; if (e->modifiers() & Qt::MetaModifier) modifiers |= SCMOD_META; int key = commandKey(e->key(), modifiers); if (key) { bool consumed = false; sci->KeyDownWithModifiers(key, modifiers, &consumed); if (consumed) { e->accept(); return; } } QString text = e->text(); if (!text.isEmpty() && text[0].isPrint()) { // 此处假定字符的数量总是 1 导致当传入的字符数量为2 的时候出现异常,后续修改也在此处进行处理 QByteArray bytes = textAsBytes(text); sci->AddCharUTF(bytes.data(), bytes.length()); e->accept(); } else { QAbstractScrollArea::keyPressEvent(e); } } |
后续进展
当前(2025.01.08)已经发送邮件给项目的开发老师,等待后续回复。
临时修改的话,可以参考如下进行调整:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
// Handle key presses. void QsciScintillaBase::keyPressEvent(QKeyEvent *e) { int modifiers = 0; if (e->modifiers() & Qt::ShiftModifier) modifiers |= SCMOD_SHIFT; if (e->modifiers() & Qt::ControlModifier) modifiers |= SCMOD_CTRL; if (e->modifiers() & Qt::AltModifier) modifiers |= SCMOD_ALT; if (e->modifiers() & Qt::MetaModifier) modifiers |= SCMOD_META; int key = commandKey(e->key(), modifiers); if (key) { bool consumed = false; sci->KeyDownWithModifiers(key, modifiers, &consumed); if (consumed) { e->accept(); return; } } QString text = e->text(); if (!text.isEmpty()) { bool consumed = false; for (int i = 0; i< text.length(); i++) { if (text[i].isPrint()) { consumed = true; QString v(text[i]); QByteArray bytes = textAsBytes(v); sci->AddCharUTF(bytes.data(), bytes.length()); } } if(consumed) { e->accept(); } } else { QAbstractScrollArea::keyPressEvent(e); } } |