Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions devel/1229.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 1229 从 pr_4399 迁移 moebius 优化

从 `pr_4399` 分支挑选对用户可感知的优化迁移到主干侧分支。选择标准:
绝对收益够大(微秒级分配器压力优化不单独迁移)、或正确性修复。
分支复用自早前性能调研,任务文档重建于此。

## 1 scheme 解析器三处越界读修复(迁移自 687b0d3d8)

### What

`moebius/moebius/data/scheme_der.cpp` 三处「先读后判界」的越界读改为
循环顶先判界再读字符:

1. `string_to_scheme_tree` 词元扫描分支:`ch= s[end_index]` 在缓冲区
末尾越界读一字节;
2. 引号串扫描分支:同款越界;
3. `unslash` 尾部 `ch= s[i]` 越界——同样改为循环顶读取。

另有两处顺带修正:

- `string_to_scheme_tree` 的 `replace(s,"\015","")` 整串拷贝改为先探测
含 CR 才替换(启动加载全部 .scm 的必经路径,绝大多数文件无 CR);
- `moebius/bench/Data/Scheme/block_bench.cpp` 资源路径失效修复
(`bench/` → `moebius/bench/`,bench 目录迁移后路径未跟上)。

### Why

越界读是未定义行为(ASAN 会报);scheme 解析是启动加载全部 .scm
的必经路径。性能持平(block_bench 5.69→5.77 ns/char,噪声内)。

### 验证

- 新建 `moebius/tests/moebius/data/scheme_der_test.cpp`(9 用例:词元在
缓冲区末尾、末尾反斜杠、引号转义、未闭合引号、注释、quote 糖、
CR 剔除、block 多表达式),随 commit 一并迁移
- `xmake test "moebius_tests/*"` 全过

### 涉及文件

- `moebius/moebius/data/scheme_der.cpp`
- `moebius/bench/Data/Scheme/block_bench.cpp`
- `moebius/tests/moebius/data/scheme_der_test.cpp`(新建)
4 changes: 2 additions & 2 deletions moebius/bench/Data/Scheme/block_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ int
main () {
lolly::init_tbox ();
string buffer;
url u= url_pwd () * url ("bench/Data/Scheme/dictionary.scm");
url u= url_pwd () * url ("moebius/bench/Data/Scheme/dictionary.scm");
load_string (u, buffer, false);
bench.minEpochIterations (10)
.batch (N (buffer))
Expand All @@ -30,7 +30,7 @@ main () {
bench.run ("serializing large group of simple element",
[&] { scheme_tree_to_block (parsed_tree); });

u= url_pwd () * url ("bench/Data/Scheme/virtual-font.scm");
u= url_pwd () * url ("moebius/bench/Data/Scheme/virtual-font.scm");
load_string (u, buffer, false);
bench.batch (N (buffer))
.run ("parsing group of complex tree",
Expand Down
34 changes: 22 additions & 12 deletions moebius/moebius/data/scheme_der.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ scm_unquote (string s) {
******************************************************************************/
void
unslash (string& s, int i, int end_index, string& r, int& r_index) {
char ch= s[i];
// 循环顶先判界再读字符:原实现末尾的 ch= s[i] 会越界读一字节
while (i < end_index) {
char ch= s[i];
if ((ch == '\\') && ((i + 1) < end_index)) {
i++;
ch= s[i];
Expand All @@ -68,7 +69,6 @@ unslash (string& s, int i, int end_index, string& r, int& r_index) {
r_index++;
}
i++;
ch= s[i];
}
}

Expand Down Expand Up @@ -142,14 +142,16 @@ string_to_scheme_tree (string& s, int& i, const int length) {
i++;
int end_index = i;
const int start_index= i;
char ch = s[end_index];
unsigned char* types = char_type;
while (!(ch == '\"') && end_index < length) {
char ch;
unsigned char* types= char_type;
// 先判界再读字符:原实现的 ch= s[end_index] 会越界读末尾一字节
while (end_index < length) {
ch= s[end_index];
if (ch == '\"') break;
if (types[(unsigned char) ch] & CT_ESC) {
if (end_index < length - 1) end_index++;
}
end_index++;
ch= s[end_index];
}
const int r_size = 1; // N ("\"");
int quoted_index= r_size;
Expand All @@ -171,15 +173,16 @@ string_to_scheme_tree (string& s, int& i, const int length) {
default: {
int end_index = i;
const int start_index= i;
char ch = s[end_index];
unsigned char* types = char_type;
while (end_index < length &&
!(types[(unsigned char) ch] & (CT_SPC | CT_PAREN))) {
char ch;
unsigned char* types= char_type;
// 先判界再读字符,避免词元结尾在缓冲区末尾时越界读
while (end_index < length) {
ch= s[end_index];
if (types[(unsigned char) ch] & (CT_SPC | CT_PAREN)) break;
if (types[(unsigned char) ch] & CT_ESC) {
if (end_index < length - 1) end_index++;
}
end_index++;
ch= s[end_index];
}
const int r_size = 0; // empty string
int token_index= r_size;
Expand All @@ -197,7 +200,14 @@ string_to_scheme_tree (string& s, int& i, const int length) {
scheme_tree
string_to_scheme_tree (string s) {
if (!char_type_init) init_char_type ();
s = replace (s, "\015", "");
// 含 CR 时才做整串替换拷贝
bool has_cr= false;
for (int k= 0; k < N (s); k++)
if (s[k] == '\015') {
has_cr= true;
break;
}
if (has_cr) s= replace (s, "\015", "");
int i = 0;
const int length= N (s);
return string_to_scheme_tree (s, i, length);
Expand Down
89 changes: 89 additions & 0 deletions moebius/tests/moebius/data/scheme_der_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/** \file scheme_der_test.cpp
* \copyright GPLv3
* \details Unit tests for scheme source parsing (string_to_scheme_tree)
* \date 2026
*/

#include "moe_doctests.hpp"

#include "tree_helper.hpp"

#include <moebius/data/scheme.hpp>
#include <moebius/tree_label.hpp>

using namespace moebius;

using namespace moebius::data;

static string
atom_of (scheme_tree t) {
return t->label;
}

TEST_SUITE ("scheme_der") {

TEST_CASE ("parse simple list") {
scheme_tree t= string_to_scheme_tree ("(a b c)");
CHECK (is_tuple (t));
CHECK_EQ (N (t), 3);
CHECK (atom_of (t[0]) == "a");
CHECK (atom_of (t[2]) == "c");
}

TEST_CASE ("parse token at buffer end") {
// 词元恰在缓冲区末尾结束(原实现越界读末尾一字节)
scheme_tree t= string_to_scheme_tree ("(abc)");
CHECK_EQ (N (t), 1);
CHECK (atom_of (t[0]) == "abc");
}

TEST_CASE ("parse trailing backslash at end") {
// 转义字符位于缓冲区末尾(原实现 unslash 越界读)
scheme_tree t= string_to_scheme_tree ("abc\\");
CHECK (atom_of (t) == "abc\\");
}

TEST_CASE ("parse quoted string with escapes") {
scheme_tree t= string_to_scheme_tree ("\"a\\nb\\tc\\\\d\"");
string s= atom_of (t);
CHECK (s == "\"a\nb\tc\\d\"");
}

TEST_CASE ("parse unclosed quote at end") {
// 未闭合引号在缓冲区末尾截断,补上收尾引号
scheme_tree t= string_to_scheme_tree ("\"abc");
CHECK (atom_of (t) == "\"abc\"");
}

TEST_CASE ("parse skips comments") {
scheme_tree t= string_to_scheme_tree ("; comment\n(foo)");
CHECK (is_tuple (t));
CHECK_EQ (N (t), 1);
CHECK (atom_of (t[0]) == "foo");
}

TEST_CASE ("parse quote sugar") {
scheme_tree t= string_to_scheme_tree ("'x");
CHECK (is_tuple (t));
CHECK_EQ (N (t), 2);
CHECK (atom_of (t[0]) == "'");
CHECK (atom_of (t[1]) == "x");
}

TEST_CASE ("parse with carriage returns") {
// CR 字符被整串剔除,词元内 CR 直接拼接
scheme_tree t= string_to_scheme_tree ("(a\015b c\015)");
CHECK_EQ (N (t), 2);
CHECK (atom_of (t[0]) == "ab");
CHECK (atom_of (t[1]) == "c");
}

TEST_CASE ("block parse concatenated expressions") {
scheme_tree t= block_to_scheme_tree ("(a) (b) (c)");
CHECK (is_tuple (t));
CHECK_EQ (N (t), 3);
CHECK_EQ (N (t[1]), 1);
CHECK (atom_of (t[1][0]) == "b");
}

} // TEST_SUITE
Loading