修复相机 HLR 结果处理中的三处正确性缺陷
B1 mergeEnvelope:命中 isNull() 的 3DSOLID 时 pSolidAll 已赋值但未从 arptrs 摘除, 后续 deleteArrayVoidPtrs 会把它删掉,返回野指针。改为记下标、循环结束后统一 removeAt。 B2 process_result:outputWithoutID.append(entAr) 误写在 for 循环体内,每轮把累积数组 整体再追加一次,同一指针在 outputWithoutID 中出现多次,AddToModelSpace 第二次失败后 DELETE_PTR 会删掉已入库的实体。append 移到循环外,并先 entAr.removeAll()。 B3 process_result:erase 之后把 it 重置成 begin(),for 的 ++it 会跳过新的首元素, 多个构筑物时漏处理。改为 while + 先取 next 迭代器。 同时更新 HRL-MODEL-refactor.md:去掉未实施的内存账本方案,验证方法改为行为观察。
This commit is contained in:
1 parent
ce886ff952
commit
39dbab85bf
2 files changed
+54
-21
No files matched your search
+22
-10
@@ -2,10 +2,10 @@
|
||||
|
||||
> 范围:`SourceCode/Code2026/CoreLibrary/AecDbCamera.cpp`(约 2900 行)。
|
||||
> 基线:main @ 7870a2b(PR #76「修复相机 HLR 计算链路上的几处内存泄漏」已合并)。下文行号均为该版本。
|
||||
> 本文记录三件事:已修的泄漏、**尚未修**的泄漏与正确性缺陷、以及一次评估后放弃的改造(组合设备块展开)。
|
||||
> 本文记录四件事:已修的泄漏、**尚未修**的泄漏、已修的正确性缺陷,以及一次评估后放弃的改造(组合设备块展开)。
|
||||
|
||||
> 目录:
|
||||
> 1 调用链与所有权约定 · 2 已修复的泄漏 · 3 尚未修复的泄漏 · 4 尚未修复的正确性缺陷
|
||||
> 1 调用链与所有权约定 · 2 已修复的泄漏(PR #76) · 3 尚未修复的泄漏 · 4 已修复的正确性缺陷
|
||||
> 5 已放弃的改造:组合设备块展开 · 6 验证方法
|
||||
|
||||
## 1. 调用链与所有权约定
|
||||
@@ -63,6 +63,8 @@ AecDbXCamera::refresh() :2121
|
||||
| L4 | mergeEnvelope :774 | `arptrs` 里全部克隆实体 | 勾了"构筑物融合",但构筑物里没有可用的 3DSOLID(全是 Region / 网格 / 空 solid) | `deleteArrayVoidPtrs` 后再返回 |
|
||||
| L5 | process_result :1123 | `subAr` 里整块 HLR 结果 | `CoreTools::makeBlockReference` 失败(块表被锁 / 只读 / `tbl->add` 失败) | 失败时 `deleteArrayVoidPtrs(subAr)` |
|
||||
|
||||
> L3 严格来说是**防御性**的:`clipResultBySection` 只清空 `it->second` 的内容、不 `erase` key,所以 `result1` / `result2` 的 map 不会变空,这处早退目前不可达。保留释放是为了以后改裁剪逻辑时不出事。
|
||||
|
||||
### 关于 L1/L2 的量级
|
||||
|
||||
补充实体是**每个选中实体**产出的(一条管道 1~3 个中心线/流向箭头,一个管件若干),所以这两处不是"漏一个对象",而是"漏一整个剖面的补充几何"。而且触发条件恰好是"刷新了但没出图"这种高频操作。
|
||||
@@ -95,7 +97,9 @@ for (...)
|
||||
|
||||
因为 `ar` 恒为空,现有所有实现都不会往 `output` 里塞东西,所以**目前确实不漏**。但它是一个"结果容器"却没有任何回收,哪天某个 override 在空 `ar` 下也 append 就会开始漏。建议加一行 `deleteContainerOfArx<AcDbEntity>(output);` 兜底。
|
||||
|
||||
## 4. 尚未修复的正确性缺陷
|
||||
## 4. 已修复的正确性缺陷
|
||||
|
||||
下面三条都是**原代码**的问题,已在本分支修复。保留分析过程是为了说明为什么这么改。
|
||||
|
||||
### B1 `mergeEnvelope` 可能返回悬空指针(use-after-free)
|
||||
|
||||
@@ -124,7 +128,7 @@ return pSolidAll; // ← 返回已释放指针
|
||||
|
||||
触发条件:找到的"第一个 solid"恰好 `isNull()`,且之后再没有有效 solid。此时 `pSolidAll` 非空但指向一个空 solid,它仍留在 `arptrs` 里,被 :815 的 `deleteArrayVoidPtrs` 删掉,然后被当作结果返回。`preprocess` :921 立刻 `xData.Write(pEnvelope, ...)` → UAF。
|
||||
|
||||
修法方向:把"找第一个可用 solid"和"是否成功"分开表达,例如用下标而不是裸指针记录命中位置,命中且非 `isNull()` 时才 `removeAt` 并置标志位。
|
||||
**已修复**:改成“只记下标、循环结束后再 `removeAt`”——`nSolidIndex` 为 -1 表示没找到,`pSolidAll` 只在命中有效 solid 时才赋值。这样“指针非空”和“已从容器摘除”两件事不再脱节。
|
||||
|
||||
### B2 `outputWithoutID.append(entAr)` 写在循环体内
|
||||
|
||||
@@ -152,7 +156,7 @@ for (int i(0); i != arResult.length(); ++i)
|
||||
|
||||
目前打不到:`arAdditional` 里只有带 `_ENVELOPE_` 标记的融合实体,在 :998 走 `continue` 分支。属于埋着的雷,一旦以后往 `arAdditional` 里放别的补充实体就会炸。
|
||||
|
||||
修法:把 `outputWithoutID.append(entAr)` 移到内层循环之外(与 `entAr` 的清空节奏对齐)。
|
||||
**已修复**:在内层循环前加 `entAr.removeAll()`,把 `outputWithoutID.append(entAr)` 移到循环之后,并加 `if (!entAr.isEmpty())` 保护。这样 `entAr` 的清空节奏和 append 节奏一致,每个实体只出现一次。
|
||||
|
||||
### B3 `outputWithID` 的 erase 循环会跳元素
|
||||
|
||||
@@ -170,10 +174,13 @@ for (; it != outputWithID.end(); ++it)
|
||||
|
||||
`erase` 后重置到 `begin()`,紧接着 for 的 `++it` 又走一步 → **跳过新的首元素**。有多个构筑物 id 时会漏处理一个(不会被转成 `arEnvelope`,也不会从 `outputWithID` 里移除)。
|
||||
|
||||
修法:改成 `while (it != outputWithID.end())` 手动控制,或 `erase` 后用 `it = outputWithID.erase(it)` 的返回值。
|
||||
**已修复**:改成 `while` 循环 + 显式 `++it`;`erase` 之前先算好 `itNext`,`erase` 之后 `it = itNext`。
|
||||
(没有用 `it = outputWithID.erase(it)` 的返回值,因为 ARX 20~22 走 C++03,那时 `map::erase` 返回 `void`。)
|
||||
|
||||
## 5. 已放弃的改造:组合设备块展开
|
||||
|
||||
> 本节内容未实施,仅作记录。
|
||||
|
||||
### 5.1 需求
|
||||
|
||||
组合设备(`pl_zhsb` 从库里插入的设备)在模型空间里是一个 `AcDbBlockReference`。相机做 HLR 时,这个块是**整块**参与计算的,结果又被 `process_result` 打回一个块。期望改成"把块内实体取出来,逐个参与 HLR",这样块内实体各自的剖切表现(中心线、系统颜色、遮挡关系)才能正确。
|
||||
@@ -212,7 +219,12 @@ for (; it != outputWithID.end(); ++it)
|
||||
|
||||
## 6. 验证方法
|
||||
|
||||
1. **泄漏量化**:Debug 版在 ARX 加载时设 `_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF)`,退出 CAD 会打印泄漏块数与地址;配合在 `hlrCalc` 出口打印 `arAdditional_all.length()`,可以量化"刷新 N 次漏多少"。
|
||||
2. **定点验证 L1/L3**:把相机移到完全没有实体的位置(或把剖切面拖到模型外)连续刷新 50 次,观察内存增量。
|
||||
3. **定点验证 L2**:只选轴网 + 一个远处实体,让 HLR 结果为空,刷新若干次。
|
||||
4. **回归**:正常剖切出图、构筑物融合开/关各跑一遍,确认输出几何与修改前一致(本次改动只加释放,不改数据流)。
|
||||
本工程 Debug 也链接 Release CRT(`cmakelists.txt` 里 `CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL"`,且 Debug 会剥掉 `_DEBUG`),所以 `_CrtSetDbgFlag` / `_CrtDumpMemoryLeaks` 用不了,只能靠行为观察。
|
||||
|
||||
1. **定点验证 L1/L2/L3**:把相机移到完全没有实体的位置(或把剖切面拖到模型外)连续刷新 50 次。修之前每次都会攒下一轮补充实体与轴网克隆,任务管理器里私有内存单调上升;修之后应回到基线并保持平稳。
|
||||
2. **定点验证 L5**:选中一个块参照参与剖切(块打块失败的场景不易构造,主要看正常路径的内存曲线不随刷新次数增长)。
|
||||
3. **回归(重点)**:B1~B3 改的是控制流,必须实测。
|
||||
- 多个构筑物同时参与剖切(验证 B3 的 erase 循环不再跳元素,且每个构筑物都被正确处理);
|
||||
- 块参照参与剖切(验证 B2 之后块内实体只被 append 一次,不会在后续刷新时出现"实体已被删除"的崩溃);
|
||||
- 构筑物融合开 / 关各跑一遍,输出几何与修改前逐项比对。
|
||||
4. **L1~L6 只加释放、不改数据流**,理论上输出不变;只要出现几何缺失或崩溃,优先怀疑释放点误删了数据库内实体。
|
||||
Reference in new issue
Block a user