From 1b2324edab9664ca45668b802e8a33d8a65dbefa Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 19:14:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(transpyv):=20=E4=BF=AE=E6=AD=A3=20mark=5Fmo?= =?UTF-8?q?dule=5Fclass=20=E7=9A=84=E8=A1=A8=E7=94=9F=E5=91=BD=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=EF=BC=88ASan=20=E6=82=AC=E7=A9=BA=E9=94=AE=E5=B4=A9?= =?UTF-8?q?=E6=BA=83=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:a(编译器缺陷 —— 上一提交自引入的跨文件悬空 HashTable) 现场:ASan access-violation(READ @0x1),rcx=0x1 string.strcmp (includes/string.vp:86) → HashTable._find_slot (_resize) → __setitem__ → set_int → StructGenMixin.mark_module_class → ClassGenDef.translate_class_def → TranslatorGenMixin.translate_children → Translator.translate → RunPhase1 → main 根因:`ModuleClasses` 原先在 `mark_module_class` 里**懒创建**,并长期挂在 Gen 上; 表体与键都取自当次文件的 pool,而 pool 在文件/遍切换时会被 reset ⇒ 下一个文件里 `_resize` 重新散列旧键时 strcmp 读到已回收内存(0x1)⇒ 崩溃。 改动(与既有 `VisibleStructs` 同口径:表与键同池、同文件生命周期): 1. `ModuleClasses` 改由 `reset_visible_structs`(每文件翻译前调用)**无条件新建** (不再区分 strict 分支),与键同池 ⇒ 无悬空。 2. `CurDefModuleSha1` 指向 `trans.ModuleSha1`(同一 pool 的串),同样在该函数里 清空,避免下一次 strcat/strcmp 读已回收内存。 3. `mark_module_class` 不再懒建表:池或表缺失时只记 `CurDefModuleSha1`、跳过登记 (宁可退化为改前行为,也不冒悬空风险)。 语义不变:规则 5.5 的证据链仍是 ① 结构体表按名(跨文件/跨遍)→ ② 正在定义类所属 模块 + find_struct_by_module → ③ 本文件已声明类归属表(自引用字段窗口 —— 登记与 查询在同一文件同一遍内,正是该表的作用域)。 --- App/lib/core/Gen/StructGenMixin.vp | 32 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 5636eaf..5382a37 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -153,8 +153,18 @@ novirtual class StructGenMixin: pool: 内存分配器(严格模式下创建 HashTable) strict: 1=严格模式(用户文件),0=宽松模式(includes 文件) + + `ModuleClasses` 也在此**每文件重建**:它的键是 pool 字符串,而 pool 在 + 文件/遍切换时会被 reset ⇒ 若表跨文件长期存活,`_find_slot` 会 strcmp 到 + 已回收的键(现场:ASan access-violation,rcx=0x1,栈 + mark_module_class → HashTable.set_int → _resize → _find_slot → strcmp)。 + 与 `VisibleStructs` 同口径(同样每文件新建、键同池),故无悬空。 + `CurDefModuleSha1` 指向 `trans.ModuleSha1`(同一 pool 的串),同样必须 + 每文件清空,否则下一次 strcat/strcmp 读的是已回收内存。 """ self.StrictVisibility = strict + self.CurDefModuleSha1 = None + self.ModuleClasses = hashtable.HashTable(pool) if strict != 0: # 严格模式:创建新的 HashTable 记录可见结构体 self.VisibleStructs = hashtable.HashTable(pool) @@ -195,18 +205,22 @@ novirtual class StructGenMixin: def mark_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> None: """登记"{sha1}.{类名}":**该模块声明了这个类**(类定义入口调用一次)。 - 键里带 SHA1 ⇒ 天然按"声明模块"区分,且与翻译顺序/模块切换无关(不需要 - 每文件重置):即使表里残留别的模块的登记,查询方只会命中 - (它自己那一对 sha1+类名),不会把别的模块的类错认成自己的。 + 键里带 SHA1 ⇒ 天然按"声明模块"区分。表由 `reset_visible_structs` **每文件 + 重建**(与键同池 ⇒ 无悬空),因此这里的登记只在本文件本遍内有效 —— + 恰好覆盖使用者(`_norm_type_arg_name` 规则 5.5)的窗口:判"类定义中"的 + 自引用字段时,登记与查询在同一文件同一遍内。 + + `CurDefModuleSha1` 同步记录("正在定义类所属模块"的权威来源,任何翻译 + 路径上都是 trans.ModuleSha1)。 """ - if pool is None or sha1 is None or name is None: + if sha1 is None or name is None: return - # 同步记录"正在定义类所属模块"(任何翻译路径上唯一的权威来源) self.CurDefModuleSha1 = sha1 - if self.ModuleClasses is None: - self.ModuleClasses = hashtable.HashTable(pool) - if self.ModuleClasses is None: - return + if pool is None or self.ModuleClasses is None: + # 未按文件初始化(该路径未调用 reset_visible_structs):不登记。 + # 不在此懒建表 —— 表必须与键同池、且随文件重置,否则会 strcmp 到 + # 已回收的键(ASan access-violation 现场)。 + return k_buf: str = self._module_class_key(pool, sha1, name) if k_buf is None: return