完成 StellarX 核心重构、缺陷修复与测试收口

This commit is contained in:
Codex
2026-09-30 01:23:18 +08:00
parent 9155a86a8a
commit 568ffc40eb
85 changed files with 5011 additions and 645 deletions
@@ -0,0 +1,137 @@
# 功能变更 ID: Feature-20260915-0001
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
## 基本信息
- ID: Feature-20260915-0001
- 标题: 全量缺陷修复与 API 调整
- 状态:非 UI 与人工 UI 已验证,待提交
- 类型:修改 / 删除 / 重命名
- 级别:L3 重大
- 模块: Control / Window / Canvas / Dialog / TabControl / Table / SxLogger / Test
- 版本 / 分支: 当前工作区
- 环境: Windows + Visual Studio 2022 + MSVC v143 + EasyX
- 负责人: Codex 协作修改
## 变更背景
- 背景:
- 代码审查发现控件回调期间所有权变化、空 TabControl、快照尺寸、几何残影、Dialog 关闭、Table coverage、日志滚动、MBCS 文本、Tooltip 和事件循环等问题。
- 原有测试主要依赖 `KEY` 场景和手工观察,缺少不创建窗口的逻辑验证、ASan 验证以及可重复的外部 UI 断言。
- 目标:
- 修复高、中、低严重度缺陷,消除明确的 UAF、越界、残影、数据截断和所有权丢失风险。
- 通过稳定句柄和延迟变更事务收口控件容器生命周期。
- 建立可重复的逻辑、像素不变量和真实 UI 回归验证。
- 明确破坏性 API 的迁移方式。
- 不做什么:[可选]
- 不迁移 Unicode,继续使用 MBCS/GBK 风格字符处理。
- 不把 GUI 改造成多线程模型。
- 不在本次将 `StellarX.Core` 改为 DLL,也不处理安装包制作。
## 变更内容
- 变更摘要:
- 修复 14 项高、中、低严重度问题。
- 将核心框架独立为 `StellarX.Core` 静态库,并让应用、单元测试和 UI Fixture 共用该实现。
- 新增 Native Unit Test、ASan、EasyX UI Fixture 和 PowerShell 自动化入口。
- 新增项:[可选]
- `StellarX::ControlHandle` 及受控控件所有权接口。
- `MutationTransaction` 延迟变更机制。
- `SxEventLoopInternal.h` 和 `SxTextInternal.h` 内部测试辅助。
- `StellarX.Tests`、`StellarX.UiFixture` 和 `scripts/test.ps1`。
- 修改项:[可选]
- Button/Label/TextBox/Canvas/Table/TabControl 的 geometry、snapshot、coverage 和绘制提交逻辑。
- Dialog 关闭和 overlay 收口逻辑。
- SxLogger sink 配置和滚动逻辑。
- Tooltip tick、MBCS 密码掩码和事件循环批处理。
- 删除 / 废弃项:[可选]
- 删除 `Window::getControls()` 和 `Canvas::getControls()` 的 mutable 容器入口。
- 删除 `Canvas::clearAllControls()`。
- `ControlText::lpszFace` 废弃并替换为 `faceName`。
- 旧的按文本/旧参数添加 Tab 控件方式不再作为首选接口。
- 受影响的文件 / 类 / 函数:
- 工程:`StellarX.Core.vcxproj`、`imGui-easyX.vcxproj`、`imGui-easyX.sln`。
- 所有权和事务:`CoreTypes.h`、`Control.h/.cpp`、`Window.h/.cpp`、`Canvas.h/.cpp`。
- 控件和重绘:`Button.h/.cpp`、`Label.h/.cpp`、`TextBox.cpp`、`Table.cpp`、`Dialog.cpp`、`TabControl.h/.cpp`。
- 日志:`SxLog.h/.cpp`。
- 示例入口:`z-testDome.cpp`。
- 测试:`tests/StellarXTests.cpp`、`tests/UiFixture.cpp`、`scripts/test.ps1`、`scripts/run-ui-tests.ps1`。
- 对外 API / 属性变化:[可选]
- `ControlHandle addControl(std::unique_ptr<Control>)`
- `bool removeControl(ControlHandle)`、`void clearControls()`
- `Control* findControl(ControlHandle)`、`std::vector<ControlHandle> getControlHandles() const`
- `ControlText::faceName` 替代 `lpszFace`。
- `bool SxLogger::setConfig(const SxLogConfig&)`。
- `size_t TabControl::addTab(...)`、`bool setActiveIndex(int)`、`tryAddToPage(...)`。
## 行为对照
- 变更前:
- 回调中直接删除当前或兄弟控件可能使遍历中的裸指针失效。
- 空 TabControl 激活和绘制路径可能访问不存在的页签。
- 控件几何变化、Tooltip、Dialog 和 Table 的局部 coverage 可能留下旧像素或污染快照。
- 日志滚动失败时可能截断原日志;字体名可能引用悬空内存;密码掩码可能按字节而非逻辑字符生成。
- 事件循环每轮只处理一条消息并固定休眠,消息积压时响应和重绘收口不稳定。
- 变更后:
- 控件增删清空在最外层遍历事务结束后提交,回调期间不会释放正在使用的遍历对象。
- 空页签路径安全返回,待激活索引在首个页签加入后应用,非法运行期索引返回 `false`。
- 几何 setter 先恢复旧 coverage,再登记旧新区域并集;快照、绘制和 coverage 使用一致的矩形语义。
- 日志 sink 候选打开成功后才原子替换,滚动重命名失败时继续 append 原文件。
- MBCS 文本按逻辑字符处理,Tooltip 由 tick 链驱动到期和清除。
- 兼容性说明:不兼容
- 迁移说明:[可选]
- 调用方应使用稳定 `ControlHandle`,不再保存可变控件容器引用。
- 将 `lpszFace` 改为 `faceName`,将旧 Tab 添加路径迁移到索引 API。
- 具体示例见 [`MIGRATION.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/MIGRATION.md)。
## 全量缺陷修复矩阵
| 严重度 | 问题 | 根因与修复 | 主要验证 |
|---|---|---|---|
| 高 | 回调删除当前控件导致 UAF | 引入稳定 `ControlHandle` 和 `MutationTransaction`;事件、绘制、按钮回调期间的 Add/Remove/Clear 延迟提交。 | self/sibling/remove/clear/add 回调测试;x64 ASan |
| 高 | 空 TabControl 激活索引越界 | 空容器绘制不访问末元素;待激活索引延迟到首个页签加入后应用;非法索引返回 `false`。 | 空容器设置 0/大索引并绘制;随后新增页签 |
| 中 | Button 快照尺寸恒不匹配 | 提取统一快照矩形计算,比较、保存和 coverage 使用相同宽高。 | hover/click/redraw/hide 像素不变量 |
| 中 | 运行时几何 setter 遗留旧像素 | setter 记录旧 coverage、恢复并释放旧快照,再更新矩形并登记旧新区域并集。 | Button/Label/TextBox/Canvas/Table/Tab 移动、缩放、隐藏 |
| 中 | Dialog 清理触发额外全量重绘 | `Close()` 统一登记 overlay 关闭,清理只释放对象和快照,Window 在 sweep 后统一重绘。 | 两层异步 Dialog 关闭上层后下层可见、可交互 |
| 中 | Table 宽度、列宽和 coverage 不一致 | 计算最小宽度并夹紧请求值;列宽总和严格等于有效内容宽;按 MBCS 边界省略文本。 | 极窄 Table、多列长文本、分页按钮和 coverage |
| 中 | 日志滚动失败截断原日志 | 滚动名加入毫秒和递增序号;检查 rename 错误;失败时重新 append 原文件,不执行 trunc。 | 同毫秒滚动、同名文件、无效路径 |
| 中 | ControlText 字体名悬空和地址比较 | 使用拥有型 `std::string faceName`;EasyX/GDI 调用使用 `.c_str()`;比较按字符串内容。 | 临时字符串析构、相同内容不同地址、内容变化测量 |
| 中 | `setConfig()` 不同步文件 sink | 锁内构造并打开候选 sink,成功后替换;禁用、路径、append 和 rotate 变化同步重开。 | 启用/禁用、切换文件、无效路径和输出一致性 |
| 中 | 同名页签可同时激活 | 通过索引排除当前页签,不按标题排除;允许重复标题但只保留一个激活页。 | 两个同名页签来回切换 |
| 低 | Tooltip 静止时不会到期显示 | 增加 Control tick 链,Window 传入单调时钟;到期时置脏并登记重绘;禁用和移出统一 hide。 | 假时钟、静止鼠标、到期显示和移出清除 |
| 低 | 密码掩码按字节生成 | 复用 MBCS 字符边界遍历,每个逻辑字符生成一个 `*`,非法尾字节仍保证前进。 | ASCII、GBK 双字节、非法尾字节 |
| 低 | 事件循环每轮只处理一条消息并固定 Sleep | 提取批处理器,每轮最多 128 条;活动轮不休眠,空闲轮 `Sleep(1)`;批次尾统一收口 resize/重绘。 | 一次注入 1000 条消息;关闭和 resize 顺序 |
| 低 | 按文本添加页控件失败时静默销毁 | `tryAddToPage` 失败原样返还 `unique_ptr`;索引重载为首选,文本重载只匹配首个同名页签。 | 不存在标题、非法索引和同名标题 |
## 验证与落地
- 验证方式:
- 使用 VS Native Unit Test 对所有权、Tab、文本、Tooltip、事件循环和日志逻辑进行测试。
- 使用 Debug x64 ASan 对回调生命周期和核心逻辑进行内存安全验证。
- 使用真实 EasyX UI Fixture 和 Win32 `SendInput`、`SetWindowPos` 驱动窗口。
- 使用 JSON 状态、关键像素、后备缓冲 PNG 和 Fixture 日志进行断言。
- 验证结果:
- 2026-09-29 执行 `scripts/test.ps1 -Suite NonUI`,Debug x64、Debug x64 ASan、Release x64、Debug Win32 均为 15/15 通过。
- x64 ASan 未报告内存错误;KEY1 至 KEY6 均编译、链接通过。
- 2026-09-30 为 `scripts/test.ps1` 补充 UTF-8 BOM 后,使用 Windows PowerShell 5.1 重跑 `NonUI`,结果保持通过。
- 本轮按约定未启动自动 UI Fixture;用户于 2026-09-30 按 `TESTING.md` 手工完成 KEY1 至 KEY6,暂未发现明显问题。
- 2026-07-22 的 UI 六类场景连续三轮通过记录保留为历史证据;当前快照以 2026-09-30 的人工验收结果为准。
- TRX 等诊断产物位于未跟踪的 `TestResults/`。
- 关联 BUG / Fix:[可选]
- 历史重绘和 Tab 相关记录见 `开发记录/BUG`、`开发记录/Fix` 目录。
- 本次新增内容以本记录的严重度矩阵统一收口,避免为同一批次重复创建 14 组 BUG/Fix 文档。
- Commit:
- 当前工作区未提交
- PR:[可选]
- 发布版本:[可选]
- 备注:[可选]
- 当前 `Render` 入口复用 UI Fixture,尚未形成完全独立的进程内 Native Render 测试类别。
- GUI 仍限定在主线程,字符集仍为 MBCS。
## 相关文档
- [`Module-20260915-0001-StellarX核心库与自动化测试基础设施.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/模块/Module-20260915-0001-StellarX核心库与自动化测试基础设施.md)
- [`MIGRATION.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/MIGRATION.md)
- [`TESTING.md`](D:/programming/imGUI-easyX/imGui-easyX/开发记录/TESTING.md)