# 功能变更 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)` - `bool removeControl(ControlHandle)`、`void clearControls()` - `Control* findControl(ControlHandle)`、`std::vector 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)