fix(callgen): 包子模块限定调用支持祖先包导入判定,杜绝同名函数劫持

main.vp IR 显示 intr.gdt.init() 解析成 @3b3cc38844ed7ad9.init(serial.vp)、
intr.idt.init() 解析成 @c418c6220a9a9d2a.init(su8250.vp,还带伪造 i16 实参)
——intr 包限定的跨模块调用因 is_module_imported('intr.gdt')=0 跳过校验,
find_func_in_module 后缀匹配劫持最先注册的同名 init → GDT/IDT 从未初始化,
CPU 一直用 OVMF 的 IDT(int 0x20 → OVMF #GP 0xEB6810C,定时中断永不到达)。

新增 ImportsGen.is_module_imported_or_known:已导入 或 全局已登记且任一祖先
包已导入(import intr → intr.gdt 可访问;this.this() 未 import this 仍 fast-fail)。
替换 CallGenMixin 模块限定解析 5 处门控(构造单层/多层、found_func 校验、
跨模块调用 import 检查)。
This commit is contained in:
TermiNexus committed 2026-08-26 18:56:37 +08:00
1 parent 23ef8c94fa
commit 53c72ff57a
2 files changed
+89 -20

No files matched your search

+4 -4
View File
@@ -242,7 +242,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
elif at_qc0.value is not None and at_qc0.value.kind() == ast.ASTKind.Attribute:
mod_path0 = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_qc0.value)
if mod_path0 is not None:
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_path0) != 0:
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_path0) != 0:
mod_sha1_0: str = self._lookup_module_sha1(mod_path0)
if mod_sha1_0 is not None:
gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(
@@ -260,7 +260,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
at_qc: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl.func)
if at_qc.value is not None and at_qc.value.kind() == ast.ASTKind.Name:
mod_nm_qc: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at_qc.value)
imp_ok: int = self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_nm_qc.id)
imp_ok: int = self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_nm_qc.id)
if imp_ok != 0:
# 模块限定构造 Module.Class(...):按模块名解析 SHA1 精确命中类。
# 不能用 trans.ModuleSha1(那是当前文件 sha1,类在 gfx 模块)——
@@ -622,7 +622,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
xm_mod_path = self.Gen.ExprGenAttr._attr_module_path_str(
pool, xm_at.value)
if xm_mod_path is not None:
if self.Gen.ImportsGen.is_module_imported(
if self.Gen.ImportsGen.is_module_imported_or_known(
trans._imported_modules, xm_mod_path) != 0:
mod_sha1_chk: str = self._lookup_module_sha1(xm_mod_path)
if mod_sha1_chk is None and trans._from_imports is not None:
@@ -1064,7 +1064,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
# 静默生成 @{sha1}.func 调用而非报错——违反"找不到 fast-fail"原则。
is_xmod_imported: int = 1
if trans is not None and trans._imported_modules is not None:
is_xmod_imported = self.Gen.ImportsGen.is_module_imported(
is_xmod_imported = self.Gen.ImportsGen.is_module_imported_or_known(
trans._imported_modules, xmod_path)
if is_xmod_imported == 0:
err_ni: str = pool.alloc(256)
+85 -16
View File
@@ -117,6 +117,45 @@ class ImportsGenMixin:
return 1
return 0
# ============================================================
# 检查模块是否已导入或全局已登记(包子模块限定解析用)
# ============================================================
def is_module_imported_or_known(self, imported_modules: str, name: str) -> int:
"""已导入或可访问的模块(包子模块限定解析用)。
包子模块场景:`import intr` 只注册包名 "intr",但 intr.gdt / intr.idt
等子模块由包 __init__.vp 的 from . import X 在全局注册——模块限定
调用/属性访问(intr.gdt.init())的 is_module_imported 校验必须放行,
否则解析退化到函数名后缀匹配劫持其他模块同名函数
(serial.init 劫持 intr.gdt.init → GDT/IDT 未初始化根因)。
放行条件(保持单层名 fast-fail 语义不变):
1. 名字已在 _imported_modules(直接 import);
2. 名字在 Sha1Store 全局已登记,且其任一祖先包名已导入
(import intr → intr.gdt 可访问;`this.this()` 但 import this
被注释 → this 未导入 → 仍 fast-fail 报"模块未导入")。
"""
if self.is_module_imported(imported_modules, name) != 0:
return 1
if name is None:
return 0
if StubMergerStore.LookupSha1ByModName(name) is None:
return 0
nl: t.CSizeT = string.strlen(name)
dot_pos: t.CSizeT = 0
while dot_pos < nl:
if name[dot_pos] == '.':
pfx: str = stdlib.malloc(dot_pos + 1)
if pfx is not None:
string.strncpy(pfx, name, dot_pos)
pfx[dot_pos] = '\0'
hit: int = self.is_module_imported(imported_modules, pfx)
stdlib.free(pfx)
if hit != 0:
return 1
dot_pos += 1
return 0
# ============================================================
# 添加 from-import 名称映射
# ============================================================
@@ -565,7 +604,7 @@ class ImportsGenMixin:
path_buf: bytes = stdlib.malloc(path_len + 1)
if path_buf is None:
return 0
# .vp 文件
# .vp 文件(Viper 源仅 .vp,不再回退 .py)
string.strncpy(path_buf, cur, sep_pos)
path_buf[sep_pos] = '\0'
string.strcat(path_buf, mod_name)
@@ -575,16 +614,6 @@ class ImportsGenMixin:
fex.close()
stdlib.free(path_buf)
return 1
# .py 文件(Python 模块可能无 .vp)
string.strncpy(path_buf, cur, sep_pos)
path_buf[sep_pos] = '\0'
string.strcat(path_buf, mod_name)
string.strcat(path_buf, ".py")
fex2: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R)
if not fex2.closed:
fex2.close()
stdlib.free(path_buf)
return 1
stdlib.free(path_buf)
return 0
@@ -670,7 +699,7 @@ class ImportsGenMixin:
else:
# 无别名时(import a.b.c),将顶层包名 "a" 也加入 _imported_modules,
# 使 a.b.c 作为属性访问的 base 被翻译时能通过 is_module_imported("a") 检查
dot_ptr: str = string.strstr(alias.name, '.')
dot_ptr: str = string.strstr(alias.name, ".")
if dot_ptr is not None:
pkg_len: t.CSizeT = t.CSizeT(t.CUInt64T(dot_ptr) - t.CUInt64T(alias.name))
if pkg_len > 0 and pkg_len < 256:
@@ -706,6 +735,29 @@ class ImportsGenMixin:
self._check_import_sha1(node, resolved)
trans._imported_modules = self.add_imported_module(
trans.Pool, trans._imported_modules, resolved)
# from . import Y(impf.module is None):resolved 是包名(vpsdk),
# 各 name 是包的子模块 vpsdk.Y。须把完整子模块名也加入
# _imported_modules,否则别名 "syscall" 精确匹配不到包名 "vpsdk"
# → is_module_imported 返回 0 → syscall._syscall1 / syscall.LOAD_SO
# 跨模块解析失败(vpsdk/dynlib.vp 根因)。
if impf.module is None:
names_m: list[ast.AST | t.CPtr] | t.CPtr = impf.names
if names_m is not None:
nn_m: t.CSizeT = len(names_m)
for ni_m in range(nn_m):
alias_m: ast.Alias | t.CPtr = (ast.Alias | t.CPtr)(names_m.get(ni_m))
if alias_m is not None and alias_m.name is not None:
rl_m: t.CSizeT = string.strlen(resolved)
rn_m: t.CSizeT = string.strlen(alias_m.name)
jb_m: str = trans.Pool.alloc(rl_m + rn_m + 2)
if jb_m is not None:
string.strcpy(jb_m, resolved)
jb_m[rl_m] = '.'
string.strcpy(jb_m + rl_m + 1, alias_m.name)
if trans._declare_only == 0:
self._check_import_sha1(node, jb_m)
trans._imported_modules = self.add_imported_module(
trans.Pool, trans._imported_modules, jb_m)
return 0
# ============================================================
@@ -737,15 +789,32 @@ class ImportsGenMixin:
if alias.asname is not None:
local_name = alias.asname
orig_name = alias.name
# from . import Y(impf.module is None):_resolve_relative_module
# 返回的是包名(如 vpsdk),但 Y 是包的子模块 vpsdk.Y
# (vpsdk/syscall.vp)。此前直接映射 local_name → 包名,
# 导致 syscall.LOAD_SO 跨模块 CDefine / syscall._syscall1
# 跨模块调用在包里找不到(vpsdk/dynlib.vp `from . import
# syscall as syscall` 根因)。
fi_target: str = resolved
if impf.module is None and resolved is not None:
fl_t: t.CSizeT = string.strlen(resolved)
fn_t: t.CSizeT = string.strlen(alias.name)
jb_t: str = trans.Pool.alloc(fl_t + fn_t + 2)
if jb_t is not None:
string.strcpy(jb_t, resolved)
jb_t[fl_t] = '.'
string.strcpy(jb_t + fl_t + 1, alias.name)
fi_target = jb_t
trans._from_imports = self.add_from_import(
trans.Pool, trans._from_imports,
local_name, resolved, orig_name)
local_name, fi_target, orig_name)
# 命名空间隔离:from-import 的名称标记为可见结构体
self.Gen.StructGen.add_visible_struct(trans.Pool, local_name)
# 注册 re-export 映射:当前模块 re-export 了 local_name,源模块为 resolved
# 使跨模块调用 module.func() 能解析到正确的源模块 SHA1
# 注册 re-export 映射:当前模块 re-export 了 local_name,
# 源模块为 fi_target(from . import Y 时是子模块名 vpsdk.Y,
# 非包名)。使跨模块调用 module.func() 能解析到正确 SHA1
if trans.ModuleSha1 is not None:
self.register_reexport(trans.ModuleSha1, local_name, resolved)
self.register_reexport(trans.ModuleSha1, local_name, fi_target)
return 0
def InitImportsGen(self) -> int: