Repository navigation
fix(jsonc): 处理 Sourcery 复查意见——超大整数、内存占用、同名窗口 - #42
Conversation
复查 #41 提出 5 条,逐条实测后处理 3 条: 1. 超大整数绕过验证(真问题):Python 3.10.7+ 限制整数字符串转换位数(默认 4300 位),3.10.12+/3.11.4+/3.12+ 的 json C scanner 会强制执行并抛 ValueError——它不是 JSONDecodeError,会逃出 save_pipeline 与 load_agent_config 的 except 分支直接冲到 MCP 工具调用之外。现在转成 JsoncDecodeError。 2. 大输入耗尽内存(真问题):预处理原先用 list(text) 逐字符复制,指针数组让 峰值内存达到文件大小的 ~9 倍。改为按片段拼接后降到 ~2 倍(7.3 MB 输入实测 66 MB → 14.7 MB)。 3. 同名窗口选错(真问题):压力测试按标题连接窗口,而 connect_window 走名字 注册表,同名窗口会互相覆盖。标题不唯一时跳过,避免误操作其他窗口。 另两条实测为误报,未改: - 「字面 emoji 键与代理对转义键撞车会静默覆盖」:标准库 json 的 C scanner 本身就会合成代理对,两个键在重复键检查时已是同一个,we 与 MaaFramework 都会拒绝。仍在 _sanitize_surrogates 里加了显式的撞车检查作为防御。 - 「仅 CR 结尾的行注释吞掉后续 JSON」:meojson 的行注释扫描同样只认 \n, 仅用 CR 换行的文件在 MaaFramework 里也会被整段吞掉。改成也认 \r 会让 MaaMCP 接受 MaaFramework 拒绝的文件,破坏 parity,保持现状并补测试。 验证:真 MaaFramework 32 例边界对照无回归;20000 例随机合法 JSON 与标准库 差分 0 处不一致;pytest 329 passed / 16 skipped
There was a problem hiding this comment.
你好——我发现了 3 个问题
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="tests/test_performance_stress.py" line_range="274" />
<code_context>
)
+ # connect_window 按标题查名字注册的表,同名窗口会互相覆盖,
+ # 可能连到另一个同名窗口上去——标题不唯一时宁可跳过。
+ if window_list.count(window_name) > 1:
+ pytest.skip(
+ f"窗口标题 {window_name!r} 不唯一(同名窗口 "
</code_context>
<issue_to_address>
**Stress test targets the wrong window**
When another `find_window_list` call updates the registry between the uniqueness check and `connect_window`, `connect_window` resolves the title through the updated registry and connects to the newly registered same-title window, so the stress test sends input to the wrong application.
Make discovery and connection atomic, or connect using a stable window identity instead of looking up the title after the check.
</issue_to_address>
### Comment 2
<location path="tests/test_jsonc.py" line_range="251" />
<code_context>
+ pytest.skip("当前解释器没有整数位数限制")
+
+ old = sys.get_int_max_str_digits()
+ setter(20)
+ try:
+ try:
</code_context>
<issue_to_address>
**Huge-integer test fails before parsing**
When the test runs on an interpreter that provides `sys.set_int_max_str_digits`, `setter(20)` raises `ValueError` because the minimum nonzero limit is 640, before the `try` block, so `loads` is never exercised and the test fails.
Set the limit to a supported value such as 640 and use an integer longer than that limit in the test.
Also at `tests/test_jsonc.py:250`.
</issue_to_address>
### Comment 3
<location path="tests/test_jsonc.py" line_range="237" />
<code_context>
+ """注释替换成等长空白,长度不变(错误位置才对得上原文)。"""
+ text = '{\n// 注释\n"A": 1 /* 块注释 */\n}'
+
+ assert len(_strip_comments(text)) == len(text)
+
+ def test_huge_integer_reported_as_decode_error(self) -> None:
</code_context>
<issue_to_address>
**Comment removal regression passes**
When `_strip_comments` stops removing comments but still returns a string of the input length, `test_comment_stripping_preserves_length` asserts only equal lengths, so `_strip_comments` returning its input unchanged passes despite leaving both comments intact.
Also assert that the returned text has the comments blanked while preserving the input length.
</issue_to_address>Original comment in English
Hey - I've found 3 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="tests/test_performance_stress.py" line_range="274" />
<code_context>
)
+ # connect_window 按标题查名字注册的表,同名窗口会互相覆盖,
+ # 可能连到另一个同名窗口上去——标题不唯一时宁可跳过。
+ if window_list.count(window_name) > 1:
+ pytest.skip(
+ f"窗口标题 {window_name!r} 不唯一(同名窗口 "
</code_context>
<issue_to_address>
**Stress test targets the wrong window**
When another `find_window_list` call updates the registry between the uniqueness check and `connect_window`, `connect_window` resolves the title through the updated registry and connects to the newly registered same-title window, so the stress test sends input to the wrong application.
Make discovery and connection atomic, or connect using a stable window identity instead of looking up the title after the check.
</issue_to_address>
### Comment 2
<location path="tests/test_jsonc.py" line_range="251" />
<code_context>
+ pytest.skip("当前解释器没有整数位数限制")
+
+ old = sys.get_int_max_str_digits()
+ setter(20)
+ try:
+ try:
</code_context>
<issue_to_address>
**Huge-integer test fails before parsing**
When the test runs on an interpreter that provides `sys.set_int_max_str_digits`, `setter(20)` raises `ValueError` because the minimum nonzero limit is 640, before the `try` block, so `loads` is never exercised and the test fails.
Set the limit to a supported value such as 640 and use an integer longer than that limit in the test.
Also at `tests/test_jsonc.py:250`.
</issue_to_address>
### Comment 3
<location path="tests/test_jsonc.py" line_range="237" />
<code_context>
+ """注释替换成等长空白,长度不变(错误位置才对得上原文)。"""
+ text = '{\n// 注释\n"A": 1 /* 块注释 */\n}'
+
+ assert len(_strip_comments(text)) == len(text)
+
+ def test_huge_integer_reported_as_decode_error(self) -> None:
</code_context>
<issue_to_address>
**Comment removal regression passes**
When `_strip_comments` stops removing comments but still returns a string of the input length, `test_comment_stripping_preserves_length` asserts only equal lengths, so `_strip_comments` returning its input unchanged passes despite leaving both comments intact.
Also assert that the returned text has the comments blanked while preserving the input length.
</issue_to_address>| ) | ||
| # connect_window 按标题查名字注册的表,同名窗口会互相覆盖, | ||
| # 可能连到另一个同名窗口上去——标题不唯一时宁可跳过。 | ||
| if window_list.count(window_name) > 1: |
There was a problem hiding this comment.
🟡 中 · 压力测试针对了错误的窗口
如果另一次 find_window_list 调用在唯一性检查与 connect_window 之间更新了注册表,connect_window 会通过更新后的注册表解析标题,并连接到新注册的同名窗口,因此压力测试会向错误的应用程序发送输入。
应将发现窗口和连接窗口设为原子操作,或者使用稳定的窗口标识进行连接,而不是在检查后再查找标题。
面向 AI 代理的提示
在 `tests/test_performance_stress.py` 第 274 行:
**压力测试针对了错误的窗口**
如果另一次 `find_window_list` 调用在唯一性检查与 `connect_window` 之间更新了注册表,`connect_window` 会通过更新后的注册表解析标题,并连接到新注册的同名窗口,因此压力测试会向错误的应用程序发送输入。
应将发现窗口和连接窗口设为原子操作,或者使用稳定的窗口标识进行连接,而不是在检查后再查找标题。Original comment in English
🟡 Medium · Stress test targets the wrong window
When another find_window_list call updates the registry between the uniqueness check and connect_window, connect_window resolves the title through the updated registry and connects to the newly registered same-title window, so the stress test sends input to the wrong application.
Make discovery and connection atomic, or connect using a stable window identity instead of looking up the title after the check.
Prompt for AI agents
In `tests/test_performance_stress.py` at line 274:
**Stress test targets the wrong window**
When another `find_window_list` call updates the registry between the uniqueness check and `connect_window`, `connect_window` resolves the title through the updated registry and connects to the newly registered same-title window, so the stress test sends input to the wrong application.
Make discovery and connection atomic, or connect using a stable window identity instead of looking up the title after the check.CI 失败原因:sys.set_int_max_str_digits(20) 抛 ValueError (解释器要求上限至少 640),改为 640 + 700 位整数。 另补一个 monkeypatch 测试:不依赖解释器版本与限制是否生效,直接验证 loads 会把 json.loads 抛出的 ValueError 转成 JsoncDecodeError。
Sourcery 在 #42 上提了 3 条,其中 setter(20) 那条已在上一提交修掉 (解释器要求位数上限至少 640),另两条: 1. test_comment_stripping_preserves_length 断言太弱:只断言长度相等, _strip_comments 原样返回输入也能通过。现在同时断言注释内容确实被 置空、且抹掉后仍是合法 JSON。 2. 压力测试的 TOCTOU:标题唯一性检查与 connect_window 之间,注册表若被 别的 find_window_list() 刷新过,可能连到另一个同名窗口。现在连上之后 核对 controller 的 hwnd 与校验过的窗口一致,对不上就跳过,不发任何输入。
Follow-up to #41 —— 处理 Sourcery 在 #41 上提出的 5 条复查意见。
逐条实测后处理 3 条真问题,另 2 条确认为误报。
已修(3 条)
1. 超大整数绕过验证(
maa_mcp/jsonc.py)Python 3.10.7+ 限制整数字符串的转换位数(默认 4300 位);3.10.12+ / 3.11.4+ /
3.12+ 的 json C scanner 会强制执行并抛
ValueError。它不是JSONDecodeError,会逃出
save_pipeline与load_agent_config的except分支,直接以异常形式冲到MCP 工具调用之外。
现在在
loads里把它转成JsoncDecodeError(invalid number: Exceeds the limit ...)。2. 大输入耗尽内存(
maa_mcp/jsonc.py)预处理原先用
list(text)逐字符复制,指针数组让峰值内存达到文件大小的 ~9 倍(7.3 MB 输入 → 66 MB 峰值)。改为按片段拼接(注释只占文件一小部分,只复制
变动区间),峰值降到 ~2 倍:
3. 同名窗口选错(
tests/test_performance_stress.py)压力测试按标题连接窗口,而
connect_window走的是名字注册表(
register_by_name),同名窗口会互相覆盖——显式指定标题并不能消除歧义,可能把输入发给另一个同名窗口。现在标题在
find_window_list()结果中出现多次时直接跳过;并且在
connect_window之后再核对一次实际连接到的 hwnd 与校验过的 hwnd 是否一致,不一致就跳过(覆盖查找与连接之间的 TOCTOU 窗口)。
确认为误报,未改(2 条)
4. 「字面 emoji 键与代理对转义键撞车会静默覆盖」
标准库
json的 C scanner 本身就会合成代理对——{"😀": 1, "😀": 2}在重复键检查时两个键已经是同一个,标准库与 MaaFramework 都会拒绝。我们与
MaaFramework 行为一致,不存在覆盖。
不过仍在
_sanitize_surrogates里补了显式的撞车检查作为防御:即使换成不做代理对合成的纯 Python scanner,也不会出现静默覆盖。
5. 「仅 CR 结尾的行注释吞掉后续 JSON」
meojson 的行注释扫描同样只认
\n——实测 MaaFramework 对// c\r{"A": ...}也是拒绝的(整段被吞掉)。若改成也认
\r,MaaMCP 就会接受 MaaFramework 拒绝的文件,破坏「同一份文件在两边解析结果相同」的前提。
保持现状,并在
_strip_comments的 docstring 与测试里把这个行为写清楚,避免以后被当成 bug「修掉」。
第二轮复查补充
Sourcery 在本 PR 上又复审了一轮,追加 3 条意见,均已处理:
sys.set_int_max_str_digits设成 20:低于解释器允许的最小非零值(640),会直接抛
ValueError而不是进入被测分支。改为setter(640)+700 位数字字面量,并补一条不依赖解释器版本的 monkeypatch 转换测试。
替换成空白、且结果可被标准库解析。
验证
pytest tests/ -q→ 330 passed / 16 skippedblack --check通过🤖 Generated with Claude Code