fix(type): 统一注入期类归属与导入上下文记账,修占位表爆与属性标记误解析

- StubMergerStore 新增共享导入上下文记账(AppendImportedModule / AppendFromImport /
  ImportCtx / AllocImportCtx / NotePlainImport),ImportsGenMixin 与 VpiReader 共用同一份
  口径;顺带移除 add_from_import 的 256 缓冲静默截断,VpiReader 删除两份私有副本

- 注入窗口按「名字归属」判据解析(is_module_class + CurDefModuleSha1):
  _forward_declare_ptr_from_node 的 Name 分支、resolve_annotation_type 的 Name/Attribute
  分支均补齐,使同一 stub 内先声明/先引用的类产出**具名占位**而不是 Ptr(i8),并新增
  _is_tattr_marker 让 t.attr.* 属性标记不参与类型联合竞争

- VpiReader._inject_class_struct_declare:命中前向声明占位时不再早退,改为「占位 Ty
  原地写入」(对齐 ClassGenDef.fill_fwd),修 StructTable 爆表(table full, cannot
  register Translator)

- 清除全部一次性探针:[DBG-GEN](VpiReader / StructGenMixin / ClassGenDef)、
  [DBG-ATTR](ExprGenAttr)、[MODSHA1](CallGenMixin)

根因记录:vpi 注入期漏记 `import X as Y -> from_imports "Y:X"`,使 GenTypes.VarEntry.Spec
(TSpec.TypeSpec | t.CPtr)取不到模块 SHA1、退化为 i8* 并被永久冻结
(CallGenCtor.vp:897 .PtrLevel 属性访问失败)。
This commit is contained in:
TermiNexus committed 2026-10-02 17:29:38 +08:00
1 parent 2c33effee1
commit 15b416b527
9 files changed
+460 -410

No files matched your search

+63 -110
View File
@@ -1723,76 +1723,15 @@ def _extract_sha1_from_path(vpi_path: str) -> str:
# ============================================================
# _stub_add_imported - 追加模块名到空格分隔列表(stub 上下文收集用)
# stub 导入上下文记账:一律走 StubMergerStore(单一真源)
#
# 与 HandlesImports.add_imported_module 逻辑一致,但内联在本模块,
# 避免 VpiReader → HandlesImports → StubMerger → VpiReader 循环依赖。
# 原 _stub_add_imported / _stub_add_from 是本模块内的两份私有副本,与翻译期
# ImportsGenMixin.add_imported_module / add_from_import 重复;其中 Import 分支
# 只抄了 imported_modules 的追加,漏掉 from_imports 的别名映射,导致 vpi 注解
# 前向声明解析不到模块 SHA1(GenTypes.VarEntry.Spec.PtrLevel 失败根因)。
# 记账已统一到 StubMergerStore.AppendImportedModule / AppendFromImport /
# NotePlainImport(纯记账、无索引副作用,正合 stub 上下文所需)。
# ============================================================
def _stub_add_imported(pool: memhub.MemBuddy*,
cur: str, name: str) -> str:
"""追加模块名到空格分隔列表(pool 分配),返回新列表"""
if name is None:
return cur
if cur is None:
nlen: sizet = string.strlen(name)
buf: str = pool.alloc(nlen + 1)
if buf is not None:
string.strcpy(buf, name)
return buf
old_len: sizet = string.strlen(cur)
name_len: sizet = string.strlen(name)
buf2: str = pool.alloc(old_len + 1 + name_len + 1)
if buf2 is not None:
string.strcpy(buf2, cur)
buf2[old_len] = ' '
string.strcpy(buf2 + old_len + 1, name)
return buf2
# ============================================================
# _stub_add_from - 追加 from-import 映射("local:mod[:orig]" 空格分隔)
# ============================================================
def _stub_add_from(pool: memhub.MemBuddy*,
cur: str, local: str, mod: str, orig: str) -> str:
"""追加 from-import 映射(pool 分配),返回新列表
[D7·截断纪律] 旧实现固定 256 缓冲 + `snprintf("%s:%s[:%s]")`:三者合计超长即被
**静默截断**,该条目在 ImportsGen 侧仍被当作合法条目解析出**错误的
local/mod/orig** ⇒ 跨模块类名/别名还原取到错名字(D16 之后即污染
Spec.SourceName 的来源名)。改为按精确长度分配,不存在上限。
"""
if local is None or mod is None:
return cur
llen: sizet = string.strlen(local)
mlen: sizet = string.strlen(mod)
use_orig: int = 0
olen: sizet = 0
if orig is not None and string.strcmp(orig, local) != 0:
use_orig = 1
olen = string.strlen(orig)
# local ':' mod [':' orig] '\0'
need: sizet = llen + 1 + mlen + 1
if use_orig != 0:
need += 1 + olen
entry: str = pool.alloc(need)
if entry is None:
return cur
string.strcpy(entry, local)
entry[llen] = ':'
string.strcpy(entry + llen + 1, mod)
if use_orig != 0:
entry[llen + 1 + mlen] = ':'
string.strcpy(entry + llen + 1 + mlen + 1, orig)
if cur is None:
return entry
old_len: sizet = string.strlen(cur)
entry_len: sizet = string.strlen(entry)
buf: str = pool.alloc(old_len + 1 + entry_len + 1)
if buf is not None:
string.strcpy(buf, cur)
buf[old_len] = ' '
string.strcpy(buf + old_len + 1, entry)
return buf
# ============================================================
@@ -2137,6 +2076,10 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
# 解析注解——根治跨上下文误报(如 t.CArray[str] 需要 't' 已导入)。
stub_imported_modules: str = None
stub_from_imports: str = None
# 记账统一走 StubMergerStore(单一真源,与翻译期 ImportsGenMixin.translate_import
# 同口径):含 `import X as Y → from_imports "Y:X"`(别名反查模块真名)与
# `import a.b.c → imported_modules "a"`(顶层包名)。
stub_ctx: StubMergerStore.ImportCtx* = StubMergerStore.AllocImportCtx(pool, None, None)
for si in range(n):
schild: ast.AST* = ch.get(si)
if schild is None:
@@ -2149,9 +2092,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
for sni in range(sn_n):
sal: ast.Alias* = ast.Alias*(simp.names.get(sni))
if sal is not None and sal.name is not None:
stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sal.name)
if sal.asname is not None:
stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sal.asname)
StubMergerStore.NotePlainImport(pool, stub_ctx, sal.name, sal.asname)
elif sk == ast.ASTKind.ImportFrom:
sifr: ast.ImportFrom* = ast.ImportFrom*(schild)
sifr_mod: str = sifr.module
@@ -2219,7 +2160,9 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod)
sifr_mod_q = qbuf_r
if sifr_mod is not None:
stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sifr_mod_q)
if stub_ctx is not None:
stub_ctx.ImportedModules = StubMergerStore.AppendImportedModule(
pool, stub_ctx.ImportedModules, sifr_mod_q)
# 方案A:包 __init__.vpi 的重导出类以包 sha1 注册别名 entry
# (ast.Attribute 定义在 ast.exprs 但归属 ast 包;查找端按包
# sha1 匹配,注册端按定义文件 sha1 → name+sha1 错位 fail-fast。
@@ -2283,22 +2226,31 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
for sifri in range(sifr_nn):
sial: ast.Alias* = ast.Alias*(sifr.names.get(sifri))
if sial is not None and sial.name is not None:
# '*' star import 不建 from 映射(lookup 有 star 回退)
slocal: str = sial.name
sorig: str = None
if sial.asname is not None:
slocal = sial.asname
sorig = sial.name
# `from X import *` 亦写 "*:mod"(与翻译期
# ImportsGenMixin.translate_from_import_names 同口径):
# lookup_from_import 的 star 回退正是靠这条 entry,旧实现
# 在此跳过 ⇒ stub 上下文里 star 回退恒失效(同一份约定
# 的两处 loop 又一处不一致)。
if stub_ctx is not None:
stub_ctx.FromImports = StubMergerStore.AppendFromImport(
pool, stub_ctx.FromImports, slocal, sifr_mod_q, sorig)
# 重导出类以包 sha1 注册(asname 时按别名注册);star 无
# 具体类名,不参与重导出(仅记账,见上)。
if string.strcmp(sial.name, "*") != 0:
slocal: str = sial.name
sorig: str = None
if sial.asname is not None:
slocal = sial.asname
sorig = sial.name
stub_from_imports = _stub_add_from(
pool, stub_from_imports, slocal, sifr_mod_q, sorig)
# 重导出类以包 sha1 注册(asname 时按别名注册)
if is_pkg_init_rt != 0 and sub_sha1_rt is not None:
_inject_reexport_class_declare(
pool, mod, sial.name, slocal,
sub_sha1_rt, src_sha1, trans)
# 用 stub 自己的 imports 替代当前模块的(stub 注解解析上下文正确性)
if stub_ctx is not None:
stub_imported_modules = stub_ctx.ImportedModules
stub_from_imports = stub_ctx.FromImports
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
if stub_imported_modules is not None:
@@ -2328,7 +2280,8 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
else:
src_mod_buf[sri_src] = ch_src
src_mod_buf[sr_len_src] = '\0'
imported_modules = _stub_add_imported(pool, imported_modules, src_mod_buf)
imported_modules = StubMergerStore.AppendImportedModule(
pool, imported_modules, src_mod_buf)
stdlib.free(src_mod_buf)
# 递归预注册 stub import 闭包的 CTypedef/CDefine:注解(如 codepage:
@@ -2780,14 +2733,6 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
flat_buf_c[p_len] = '_'
string.strcpy(flat_buf_c + p_len + 1, cls_name)
real_name = flat_buf_c
# [DBG-GEN] 一次性取证(用完删除)
_dg_v_isgen: int = 0
if string.strcmp(real_name, "Generator") == 0:
_dg_v_isgen = 1
_dg_v_s: str = "<null>"
if src_sha1 is not None:
_dg_v_s = src_sha1
stdio.printf("[DBG-GEN] vpi inject_class IN name=Generator src_sha1=%s\n", _dg_v_s)
# REPL(ReplStubLayoutMode,见文件末"REPL:includes 类完整布局与虚表恢复"):
# 本类若已由 .stub.ll 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地
# 登记成功)只标可见;父类未就绪 ⇒ 本遍不登记(收尾遍补齐);布局不可信 ⇒
@@ -2802,23 +2747,26 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
if se.State == _REPL_SS_OK:
GenApi.add_visible_struct(pool, real_name)
return 1
# 1. 已注册检查(含源模块编译时注册的 entry):仅标记可见,避免重复注册
# 1. 已注册检查(含源模块编译时注册的 entry):仅标记可见,避免重复注册。
# 例外:**前向声明占位**(IsForwardDecl != 0)不早退——必须走下方注册路径,
# 并用「占位 Ty 原地写入」把字段体填进**同一个 Ty 对象**(见下方 fill_fwd_c 段)。
# existing_c/fill_fwd_c 声明在 if 外:下方填充段要跨块读取。
existing_c: GenTypes.StructEntry* = None
fill_fwd_c: int = 0
if src_sha1 is not None and src_sha1[0] != '\0':
existing_c: GenTypes.StructEntry* = GenApi.find_struct_by_module(real_name, src_sha1)
existing_c = GenApi.find_struct_by_module(real_name, src_sha1)
if existing_c is not None and existing_c.Ty is not None:
GenApi.add_visible_struct(pool, real_name)
if _dg_v_isgen != 0:
stdio.printf("[DBG-GEN] vpi inject_class EXISTING name=Generator fcount=%d\n",
existing_c.FieldCount)
return 1
if existing_c.IsForwardDecl != 0:
fill_fwd_c = 1
else:
GenApi.add_visible_struct(pool, real_name)
return 1
# REnum 类(class X(t.REnum))跳过:布局 {i32 __tag, <payload>} 由
# ClassGenEnum._translate_renum_def 按变体计算;vpi 侧按普通 struct 注册
# 会得到错误布局(变体字段都在嵌套类里,直接 AnnAssign 扫描得 0 字段)。
# 留给源模块正式翻译注册。LLVMType 本就因 field_count==0 早退,此守卫把
# 该行为显式化,避免将来 REnum 带直接字段时被误注册为普通 struct。
if _vpi_class_is_renum(class_cd) != 0:
if _dg_v_isgen != 0:
stdio.printf("[DBG-GEN] vpi inject_class RENUM-SKIP name=Generator\n")
return 0
# 规则 5.5 的证据前置(**必须早于**下方字段注解解析):
# 本函数在**注入期**解析字段注解(GenApi.resolve_annotation_type →
@@ -2875,12 +2823,6 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
pool, m_aa_c.annotation, imported_modules, from_imports, trans, 0)
if field_ty_c is None:
# 字段类型解析失败:不注册该类(留给源模块编译注册),不 fatal
if _dg_v_isgen != 0:
_dg_v_fn: str = "<null>"
if m_tgt_c.id is not None:
_dg_v_fn = m_tgt_c.id
stdio.printf("[DBG-GEN] vpi inject_class FIELD-FAIL name=Generator field=%s\n",
_dg_v_fn)
return 0
if field_count < GenTypes.FIELD_MAX:
field_names_buf[field_count] = sizet(m_tgt_c.id)
@@ -2889,8 +2831,6 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
# 3. vpi 存根不含类字段(只序列化 class 名 + def 签名)→ field_count 恒为 0;注册空类会让源模块编译时 find_struct_by_module 命中空类
# → "结构体已注册但字段数为0" InternalCompilerError。故改为不注册:依赖模块已编译时命中真实类(上方已处理),未编译时由 _deps_pending 多遍收敛。
if field_count == 0:
if _dg_v_isgen != 0:
stdio.printf("[DBG-GEN] vpi inject_class EMPTY name=Generator\n")
return 0
# 4. 构建 ParamNode 链表
first_node_c: rawptr[llvmlite.ParamNode] = None
@@ -2935,6 +2875,22 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
CompilerFactory.factory.PermPool, struct_ty_c)
if st_copy_c is not None:
struct_ty_c = st_copy_c
# 5.5 前向声明占位命中 ⇒ **占位 Ty 原地写入**(与 ClassGenDef.translate_class_def
# 的 fill_fwd 段逐句同款):给已存在的类型对象 `*existing.Ty` 赋入刚构建的
# 字段体,并让 struct_ty_c 指向**同一个 Ty**。
# 这是"不新建条目"的唯一正确做法:若此处让 struct_ty_c 保持新对象,下方
# register_struct 按 **Ty 指针**去重就会 miss ⇒ 每一遍注入都追加一个新条目
# ⇒ StructTable 爆表(现场:table full, cannot register Translator)。
# 必须早于 module_add_named_type:避免同一名字先以占位名注册、又以新对象重复注册。
if fill_fwd_c != 0 and existing_c is not None and existing_c.Ty is not None:
fwd_name_c: str = llvmlite.get_struct_name(existing_c.Ty)
fwd_fields_c: rawptr[llvmlite.ParamNode] = llvmlite.get_struct_fields(struct_ty_c)
fwd_fc_c: int = llvmlite.get_struct_fcount(struct_ty_c)
*existing_c.Ty = llvmlite.LLVMType.Struct(fwd_fields_c, fwd_fc_c, fwd_name_c)
struct_ty_c = existing_c.Ty
type_name_c = fwd_name_c
existing_c.IsForwardDecl = 0
existing_c.FieldCount = 0
# 6. 注册到当前模块(输出类型定义行)+ 全局 StructTable + 可见性
llvmlite.module_add_named_type(mod, pool, struct_ty_c)
# B5:vpi 保留 @c.Attribute(t.attr.packed) 装饰器,stub 注入的跨模块
@@ -2962,9 +2918,6 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
af_ty_c: llvmlite.LLVMType* = (llvmlite.LLVMType*)(field_types_buf[afi_c])
GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
GenApi.add_visible_struct(pool, real_name)
if _dg_v_isgen != 0:
stdio.printf("[DBG-GEN] vpi inject_class REGISTERED name=Generator fcount=%d ty=%s\n",
field_count, type_name_c)
return 1
+135
View File
@@ -29,6 +29,141 @@ import lib.Stub.StubMerger as StubMerger
# StubMergerStore - 全局 SHA1 映射存储器(sha1→模块名/相对路径)读写
# ============================================================
# ============================================================
# 导入上下文记账(单一真源)
#
# 「AST 导入语句 → imported_modules / from_imports」的记账口径必须唯一。
# 翻译期(ImportsGenMixin.translate_import)与 vpi 注入期(VpiReader 顶层
# 遍历)此前各写一份:注入期漏掉 `import X as Y → from_imports "Y:X"`(别名
# 反查模块真名的唯一入口,见 LookupModNameByAlias)以及 `import a.b.c →
# imported_modules "a"`(顶层包名放行 is_module_imported)。前者使注解里的
# `TSpec.TypeSpec*` 在 _forward_declare_ptr_from_node 4a'/4b 全落空 → 退化
# 为 Ptr(i8)(现场:GenTypes.VarEntry.Spec.PtrLevel 属性访问失败);后者使
# 包限定解析在 stub 上下文里被误判"未导入"。
#
# 现统一到本模块,两侧只保留各自的特有副作用(SHA1 校验 / reexport 注册 /
# add_visible_struct),字符串记账一律走下面这组函数。ImportCtx 只承载两个
# 上下文串(Viper 无多返回值,用 pool 分配的小 struct 传出)。
# ============================================================
class ImportCtx:
"""导入上下文容器:两个空格分隔串(ImportedModules / FromImports)"""
ImportedModules: str
FromImports: str
# ============================================================
# AppendImportedModule - 追加模块名到空格分隔列表(纯记账,无索引副作用)
# ============================================================
def AppendImportedModule(pool: memhub.MemBuddy*, cur: str, name: str) -> str:
"""追加模块名到空格分隔列表(pool 分配),返回新列表"""
if name is None:
return cur
if cur is None:
nlen: sizet = string.strlen(name)
buf: str = pool.alloc(nlen + 1)
if buf is not None:
string.strcpy(buf, name)
return buf
old_len: sizet = string.strlen(cur)
name_len: sizet = string.strlen(name)
buf2: str = pool.alloc(old_len + 1 + name_len + 1)
if buf2 is not None:
string.strcpy(buf2, cur)
buf2[old_len] = ' '
string.strcpy(buf2 + old_len + 1, name)
return buf2
# ============================================================
# AppendFromImport - 追加 from-import 映射("local:mod[:orig]" 空格分隔)
#
# [D7·截断纪律] 旧实现固定 256 缓冲 + snprintf:三者合计超长即被静默截断,
# 该条目在查找侧仍被当作合法条目解析出错误的 local/mod/orig ⇒ 跨模块类名/
# 别名还原取到错名字(污染 Spec.SourceName)。此处按精确长度分配,无上限。
# ============================================================
def AppendFromImport(pool: memhub.MemBuddy*, cur: str,
local: str, mod: str, orig: str) -> str:
"""追加 from-import 映射(pool 分配),返回新列表"""
if local is None or mod is None:
return cur
llen: sizet = string.strlen(local)
mlen: sizet = string.strlen(mod)
use_orig: int = 0
olen: sizet = 0
if orig is not None and string.strcmp(orig, local) != 0:
use_orig = 1
olen = string.strlen(orig)
# local ':' mod [':' orig] '\0'
need: sizet = llen + 1 + mlen + 1
if use_orig != 0:
need += 1 + olen
entry: str = pool.alloc(need)
if entry is None:
return cur
string.strcpy(entry, local)
entry[llen] = ':'
string.strcpy(entry + llen + 1, mod)
if use_orig != 0:
entry[llen + 1 + mlen] = ':'
string.strcpy(entry + llen + 1 + mlen + 1, orig)
if cur is None:
return entry
old_len: sizet = string.strlen(cur)
entry_len: sizet = string.strlen(entry)
buf: str = pool.alloc(old_len + 1 + entry_len + 1)
if buf is not None:
string.strcpy(buf, cur)
buf[old_len] = ' '
string.strcpy(buf + old_len + 1, entry)
return buf
# ============================================================
# AllocImportCtx - 用两个既有上下文串构造记账容器
# ============================================================
def AllocImportCtx(pool: memhub.MemBuddy*,
imported_modules: str, from_imports: str) -> ImportCtx*:
"""构造导入上下文容器(pool 分配);两串可为 None"""
ctx: ImportCtx* = pool.alloc(ImportCtx.__sizeof__())
if ctx is None:
return None
ctx.ImportedModules = imported_modules
ctx.FromImports = from_imports
return ctx
# ============================================================
# NotePlainImport - 记账 `import X` / `import X as Y`(就地更新 ctx)
#
# 有别名:追加别名 `Y`,并写 from-import 映射 "Y:X"——这就是 `LookupModNameByAlias`
# 与 `_forward_declare_ptr_from_node` 4a' 反查模块真名的唯一来源,绝不可省。
# 无别名:`import a.b.c` 时把顶层包名 `a` 也追加(使 a.b.c 作为属性访问 base
# 时 is_module_imported("a") 放行)。
# ============================================================
def NotePlainImport(pool: memhub.MemBuddy*, ctx: ImportCtx*,
name: str, asname: str) -> int:
"""记账单条 import(就地更新 ctx),返回 0"""
if ctx is None or name is None:
return 1
ctx.ImportedModules = AppendImportedModule(pool, ctx.ImportedModules, name)
if asname is not None:
ctx.ImportedModules = AppendImportedModule(pool, ctx.ImportedModules, asname)
ctx.FromImports = AppendFromImport(pool, ctx.FromImports, asname, name, None)
else:
dot_ptr: str = string.strstr(name, ".")
if dot_ptr is not None:
pkg_len: sizet = sizet(uint64(dot_ptr) - uint64(name))
if pkg_len > 0 and pkg_len < 256:
pkg_buf: str = pool.alloc(pkg_len + 1)
if pkg_buf is not None:
string.strncpy(pkg_buf, name, pkg_len)
pkg_buf[pkg_len] = '\0'
ctx.ImportedModules = AppendImportedModule(
pool, ctx.ImportedModules, pkg_buf)
return 0
# ============================================================
# PopulateSha1MapStore - 遍历 ScanResult.Entries 构建 sha1→module_name 映射,填充全局存储器
#