From 5240aaaffa1c881186d9501b9d457528d31291c3 Mon Sep 17 00:00:00 2001 From: Yuki Date: Thu, 16 Jul 2026 14:48:04 +0800 Subject: [PATCH] =?UTF-8?q?[2031]=20=E4=BF=AE=E5=A4=8D=E5=88=87=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E6=97=B6=20s7=20did?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/plugins/goldfish/src/s7.c | 4 ++ devel/2031.md | 66 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 devel/2031.md diff --git a/TeXmacs/plugins/goldfish/src/s7.c b/TeXmacs/plugins/goldfish/src/s7.c index 567624a9c7..d727e846b9 100644 --- a/TeXmacs/plugins/goldfish/src/s7.c +++ b/TeXmacs/plugins/goldfish/src/s7.c @@ -59953,6 +59953,10 @@ static no_return void unbound_variable_error_nr(s7_scheme *sc, s7_pointer sym) #endif for (int32_t i = start; i < end; i++) { + /* main_names has only 430 entries but main_names_index is sized + for 443, so the length-20..23 buckets run into trailing NULL + slots; skip NULLs to avoid dereferencing them in levenshtein. */ + if (main_names[i] == NULL) continue; int32_t diff = levenshtein(sc, sym_name, sym_len, main_names[i], sym_len) + 1; /* perhaps same but i+/-1 as well */ if (sym_name[0] != main_names[i][0]) diff++; if (diff < min_diff) diff --git a/devel/2031.md b/devel/2031.md new file mode 100644 index 0000000000..1ca46ce688 --- /dev/null +++ b/devel/2031.md @@ -0,0 +1,66 @@ +# [2031] 修复切主题时 s7 "did you mean" 建议解引用 NULL 导致 SIGSEGV + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 +- [2030.md](2030.md) - QML 弹窗系统(同期主题相关改动) + +## 2 任务相关的代码文件 +- `TeXmacs/plugins/goldfish/src/s7.c` — `unbound_variable_error_nr()` 内的"did you mean"建议表(`main_names` / `main_names_index`) + +## 3 如何测试 + +### 3.1 复现(修复前) +切主题会触发未绑定变量 `set-pretty-preference*`,s7 在报错前为其计算"did you mean"建议,遍历到 NULL 解引用崩溃: +```bash +xmake b stem +xmake r stem # 启动后在菜单里切换主题 → SIGSEGV +``` + +### 3.2 验证(修复后) +切主题不再崩溃,且仍能给出合理的拼写建议: +```bash +xmake b stem +xmake r stem # 切换主题正常,无段错误 +``` + +## 4 如何提交 + +```bash +gf fmt --changed-since=main # 格式化本次改动的 .cpp/.hpp/.scm +``` + +## 5 What +1. 在 `unbound_variable_error_nr()` 遍历 `main_names` 的循环里,对取到的表项增加 NULL 判空,遇到 NULL 直接 `continue`,避免把它传给 `levenshtein()` 解引用。 + +## 6 Why + +这是 commit `7daf2e6d7`([2004] 把 `s7.c` 从 `3rdparty/s7/` 迁到 `goldfish/src/`)新引入的 regression。 + +s7 在报"未绑定变量"错误前,会按变量名的字符长度分桶,用 levenshtein 距离在一张内置符号表 `main_names` 里找最接近的拼写建议("did you mean …")。桶的边界由 `main_names_index` 给出,`sym_len` 长度的桶覆盖区间 `[main_names_index[sym_len-2], main_names_index[sym_len-1])`。 + +旧版(`3rdparty/s7/`)的 `main_names` 填满了 `MAIN_NAMES_SIZE`(445 项全有效),所有桶都落在有效区。新版 `goldfish/src/` 重构时删减/重排了这张表,**实际只填了 430 条**(索引 0–429 有效,430–442 是 NULL),但: + +- `MAIN_NAMES_SIZE` 仍是 **443**(`#define MAIN_NAMES_SIZE 443`,`main_names[443]` 后 13 个槽位为零初始化的 NULL 指针); +- `main_names_index` 仍按"填满 443"计算,末尾几个桶的边界被 NULL 区覆盖。 + +逐桶核对(实际 430 条有效,索引 ≥430 为 NULL): + +| sym_len | 桶区间 | 状态 | +|---------|--------|------| +| 2 … 19 | 全落在 [0, 425) | 全部有效 | +| **20** | `[425, 432)` | **跨越边界**:425–429 有效,430–431 为 NULL | +| **21** | `[432, 435)` | 完全落在 NULL 区 | +| **22** | `[435, 440)` | 完全落在 NULL 区 | +| **23** | `[440, 442)` | 完全落在 NULL 区 | + +切主题时调用的 `set-pretty-preference*` 恰为 **22 字符**,落在完全为 NULL 的桶 `[435, 440)`,遍历第一项就取到 NULL。`levenshtein()` 内部对 NULL 字符串解引用 → SIGSEGV。 + +> 备注:用户最初印象是 20 字符,实际 `set-pretty-preference*` 为 22 字符;但触发条件是 20–23 这几个桶整体落入 NULL 区,结论不变。 + +## 7 How +修复采用最小侵入的"判空跳过":在遍历循环开头加一行 `if (main_names[i] == NULL) continue;`。 + +之所以选这个方案而非"把 `MAIN_NAMES_SIZE` / `main_names_index` 改成与 430 严格一致": +- s7 是 vendored 的第三方单文件(4 万+ 行),最小改动便于后续跟踪上游; +- `main_names` 由脚本生成,精确重算 22 个桶边界易引入新的对齐错误,而 NULL 跳过对任何"表未填满"的情况都鲁棒; +- 不影响建议质量——NULL 槽位本就不含符号,跳过它们等价于从未定义过这些槽。