diff --git a/devel/2050.md b/devel/2050.md new file mode 100644 index 0000000000..5c4ea08e66 --- /dev/null +++ b/devel/2050.md @@ -0,0 +1,43 @@ +# [2050] 修复 get_bounding_rectangle 在 editor 无效时 SIGSEGV + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 2 任务相关的代码文件 +- `src/Scheme/L5/glue_l5_extra.hpp` - `get_bounding_rectangle` 加入 `is_nil(ed)` 空指针检查 +- `src/Edit/Interface/edit_mouse.cpp` - 拖拽事件改为异步分发,避免 GC 重入导致 editor vtable 损坏 + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +``` +xmake b stem +``` + +### 3.2 非确定性测试(文档验证) +在包含图片的文档中对图片进行鼠标拖拽操作(`start-drag-left`),确认不再崩溃。 + +## 4 如何提交 + +提交前执行以下最少步骤: + +```bash +gf fmt --changed-since=main +xmake b stem +``` + +## 5 What +两处修改配合修复 SIGSEGV: + +1. **`glue_l5_extra.hpp`**:在 `get_bounding_rectangle` 中增加 `if (is_nil(ed)) return array();` 空指针守卫 +2. **`edit_mouse.cpp`**:将所有拖拽事件(`start-drag-left/right`、`dragging-left/right`、`end-drag-left/right`)从同步 `call_mouse_event` 改为异步 `delayed_call_mouse_event`,通过 `(delayed (:idle 1) ...)` 排队在空闲时执行,保证 FIFO 顺序 + +## 6 Why +Qt 事件循环处理鼠标事件时,`handle_mouse` 检测到图片上的 `start-drag-left`,同步调用 `call_mouse_event` 进入 Scheme。Scheme 的 `mouse-event` handler 触发 `(tree-bounding-rectangle t)`,回调 C++ 的 `get_bounding_rectangle`。该函数对 `get_current_editor()` 返回的 editor 调用虚函数 `get_window_extents()`。 + +在此同步 Scheme 调用期间,`tmscm_object_rep` 的构造/析构会触发 S7 GC。GC 过程可能通过 `gc_free` 回调或 finalizer 重新进入 Scheme,再回调 C++ 函数。在此 GC 重入路径中,`edit_interface_rep` 对象的 vtable 已损坏(对象可能处于部分析构状态),导致虚调用 `get_window_extents()` 分发到相邻 vtable 槽位的 `cancel_menu_action` thunk 上并 SIGSEGV。 + +`is_nil(ed)` 守卫作为纵深防御覆盖 editor 为 null 的情况,但单靠它不够——因为 editor 的 `rep` 指针非空,只是指向的对象 vtable 已损坏。 + +## 7 How +**edit_mouse.cpp**:将拖拽事件从 `call_mouse_event`(同步 call 进 Scheme)改为 `delayed_call_mouse_event`(`eval` 拼装 `(delayed (:idle 1) (mouse-event ...))` 排队)。同类型事件全部异步后,FIFO 顺序自然保持(`start-drag-left` 先入队先执行),消除了同步 Scheme 执行期间的 GC 重入窗口。 diff --git a/src/Edit/Interface/edit_mouse.cpp b/src/Edit/Interface/edit_mouse.cpp index ba46b140be..6a9ad428f1 100644 --- a/src/Edit/Interface/edit_mouse.cpp +++ b/src/Edit/Interface/edit_mouse.cpp @@ -1225,15 +1225,21 @@ edit_interface_rep::handle_mouse (string kind, SI x, SI y, int m, time_t t, SI dist= (SI) (5 * PIXEL / magf); rew = detect_left_drag ((void*) this, rew, x, y, t, m, dist); if (rew == "start-drag-left") { - call_mouse_event (rew, left_x, left_y, m, t, data); + delayed_call_mouse_event (rew, left_x, left_y, m, t, data); delayed_call_mouse_event ("dragging-left", x, y, m, t, data); } + else if (rew == "dragging-left" || rew == "end-drag-left") { + delayed_call_mouse_event (rew, x, y, m, t, data); + } else { rew= detect_right_drag ((void*) this, rew, x, y, t, m, dist); if (rew == "start-drag-right") { - call_mouse_event (rew, right_x, right_y, m, t, data); + delayed_call_mouse_event (rew, right_x, right_y, m, t, data); delayed_call_mouse_event ("dragging-right", x, y, m, t, data); } + else if (rew == "dragging-right" || rew == "end-drag-right") { + delayed_call_mouse_event (rew, x, y, m, t, data); + } else call_mouse_event (rew, x, y, m, t, data); } } diff --git a/src/Scheme/L5/glue_l5_extra.hpp b/src/Scheme/L5/glue_l5_extra.hpp index 3e469852f8..9d9b887c34 100644 --- a/src/Scheme/L5/glue_l5_extra.hpp +++ b/src/Scheme/L5/glue_l5_extra.hpp @@ -116,7 +116,8 @@ cpp_error () { inline array get_bounding_rectangle (tree t) { - editor ed = get_current_editor (); + editor ed= get_current_editor (); + if (is_nil (ed)) return array (); rectangle wr = ed->get_window_extents (); path p = reverse (obtain_ip (t)); selection sel = ed->search_selection (p * start (t), p * end (t));