feat(gen,static): static 符号禁止跨模块使用(定义侧登记 + 引用侧三处拦截)

语义 = C 的 internal linkage:static 符号(函数/变量)只在定义模块内可见,任何跨模块引用一律 fail-fast(NameError)。

- 登记表:CallGenBase 新增 StaticSha1Arr/StaticNameArr/StaticCount(PermPool 定长槽,与 CexportSha1Arr 同款)+ RegisterStaticSymbol/IsStaticSymbol(按 (符号名, 模块 SHA1) 精确配对,避免跨模块同名误判)。

- 定义侧登记:FuncGenDef 识别注解形态(-> t.CVoid | t.CStatic)与装饰器形态(@t.CStatic;裸 static def 已由解析层归一为装饰器)→ 登记函数名;TranslatorGenMixin.handle_module_level_var 识别注解形态(static x: int 已归一为 t.CStatic | int)→ 登记变量名。

- 引用侧拦截(三处,覆盖全部跨模块形态):ExprGenAttr.translate_attribute 入口统一拦截 mod.name(含 import 别名还原);CallGenMixin.translate_call 入口拦截 mod.fn(...) 调用(按 AST 形态一次覆盖 found_func/vpi/裸名兜底各分支);ImportsGenMixin.translate_import_from_names 拦截 from mod import name。

- 性能:三处检查均以 StaticCount > 0 快速短路——项目中无 static 符号时零开销(不触碰逐条线性扫描与 sha1 解析)。

- 同模块自引用(sha1 相同)与非模块基座(self.method / obj.field / Class.static_method)一律放行。
This commit is contained in:
TermiNexus committed 2026-09-27 12:58:51 +08:00
1 parent 424dbd7379
commit dd56580855
6 files changed
+178 -1

No files matched your search

+68
View File
@@ -63,6 +63,14 @@ novirtual class CallGenBase:
CexportFuncArr: bytes
CexportCount: int
# --- static 符号注册表(`static` 修饰符 / @t.CStatic / t.CStatic 标记的符号 →
# 定义模块 SHA1)。语义 = 禁止跨模块使用(C 的 internal linkage):定义侧登记,
# 跨模块引用侧(mod.name 取值 / mod.fn() 调用 / from mod import name)拦截报错。
# 与 CexportSha1Arr 同款 PermPool 定长槽(跨 mark/reset 存活)。
StaticSha1Arr: bytes
StaticNameArr: bytes
StaticCount: int
# --- LLVMIR 寄存器序号 ---
LLVMIRRegSeq: int
@@ -418,6 +426,66 @@ novirtual class CallGenBase:
return self._is_cexport_func_from_vpi(sha1, func_name)
# ============================================================
# RegisterStaticSymbol / IsStaticSymbol - static 符号注册表
#
# `static def f` / `static x: int` / `@t.CStatic` / `x: t.CStatic | T` 四种写法
# 在解析层已归一为同一套标记(t.CStatic),语义 = 禁止跨模块使用(C 的
# internal linkage)。定义侧翻译时登记 (符号名, 定义模块 SHA1);跨模块引用侧
# (mod.name 取值 / mod.fn() 调用 / from mod import name)用 IsStaticSymbol
# 判定并 fail-fast。
# ============================================================
def RegisterStaticSymbol(self, sha1: str, name: str) -> int:
"""登记 static 符号(定义模块 SHA1 + 符号名);1=已登记(含幂等重复)/ 0=失败"""
if self.Gen is None or sha1 is None or name is None:
return 0
if self.Gen.CallGenBase.StaticSha1Arr is None:
self.Gen.CallGenBase.StaticSha1Arr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 17)
self.Gen.CallGenBase.StaticNameArr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 64)
if self.Gen.CallGenBase.StaticSha1Arr is None or self.Gen.CallGenBase.StaticNameArr is None:
return 0
string.memset(self.Gen.CallGenBase.StaticSha1Arr, 0, _CEXPORT_MAX * 17)
string.memset(self.Gen.CallGenBase.StaticNameArr, 0, _CEXPORT_MAX * 64)
# 幂等:同 (sha1, name) 重复登记(跨遍重译 / 同文件多次进入)直接命中返回
for i in range(self.Gen.CallGenBase.StaticCount):
si: sizet = sizet(i) * 17
if string.strcmp(self.Gen.CallGenBase.StaticSha1Arr + si, sha1) == 0:
sn: sizet = sizet(i) * 64
if string.strcmp(self.Gen.CallGenBase.StaticNameArr + sn, name) == 0:
return 1
if self.Gen.CallGenBase.StaticCount < _CEXPORT_MAX:
idx: sizet = sizet(self.Gen.CallGenBase.StaticCount) * 17
# 槽位定长 17/64 字节,strcpy 无界会越界写相邻槽(stdlib 堆区)
string.strncpy(self.Gen.CallGenBase.StaticSha1Arr + idx, sha1, 16)
self.Gen.CallGenBase.StaticSha1Arr[idx + 16] = '\0'
idx2: sizet = sizet(self.Gen.CallGenBase.StaticCount) * 64
string.strncpy(self.Gen.CallGenBase.StaticNameArr + idx2, name, 63)
self.Gen.CallGenBase.StaticNameArr[idx2 + 63] = '\0'
self.Gen.CallGenBase.StaticCount += 1
return 1
return 0
def IsStaticSymbol(self, sha1: str, name: str) -> int:
"""(sha1, name) 是否为已登记的 static 符号(跨模块引用拦截判据)
与 is_cexport_func 的区别:cexport 是"导出裸名"(跨模块**允许**),
static 是"禁止跨模块"(跨模块**必须报错**);两者判据方向相反,
但同用 (sha1, 符号名) 精确配对,避免同名符号跨模块误判。
"""
if self.Gen is None or sha1 is None or name is None:
return 0
if self.Gen.CallGenBase.StaticSha1Arr is None:
return 0
for i in range(self.Gen.CallGenBase.StaticCount):
si: sizet = sizet(i) * 17
if string.strcmp(self.Gen.CallGenBase.StaticSha1Arr + si, sha1) == 0:
sn: sizet = sizet(i) * 64
if string.strcmp(self.Gen.CallGenBase.StaticNameArr + sn, name) == 0:
return 1
return 0
def _is_cexport_func_from_vpi(self, sha1: str, func_name: str) -> int:
"""vpi 返回注解含 t.CExport → 定义端用裸名(跨模块调用无 sha1 头)
+26
View File
@@ -53,6 +53,32 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
trans: HT.Translator* = None) -> llvmlite.Value*:
"""翻译函数调用,返回 call 结果 Value"""
cl: ast.Call* = ast.Call*(node)
# static 符号(禁止跨模块使用):模块限定**调用** `mod.fn(...)` 命中别的
# 模块登记的 static 函数即 fail-fast。放在入口处统一拦截——后续分派有
# 多条分支(found_func 命中 / 走 vpi 解析 / 走裸名兜底),只在某一分支
# 检查会漏;此处按 AST 形态一次覆盖全部调用路径。基座不是已导入模块
# (self.method / obj.method / Class.static_method)时放行。
if cl is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute \
and trans is not None and trans.ModuleSha1 is not None \
and trans._imported_modules is not None \
and self.Gen.CallGenBase.StaticCount > 0:
st_at: ast.Attribute* = ast.Attribute*(cl.func)
if st_at.value is not None and st_at.attr is not None:
st_mod: str = None
if st_at.value.kind() == ast.ASTKind.Name:
st_nm: ast.Name* = ast.Name*(st_at.value)
st_mod = st_nm.id
elif st_at.value.kind() == ast.ASTKind.Attribute:
st_mod = self.Gen.ExprGenAttr._attr_module_path_str(pool, st_at.value)
if st_mod is not None \
and self.Gen.ImportsGen.is_module_imported(trans._imported_modules, st_mod) != 0:
st_sha1: str = self.Gen.StructGen._resolve_module_sha1(st_mod, trans._from_imports)
if st_sha1 is not None \
and string.strncmp(st_sha1, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(st_sha1, st_at.attr) != 0:
self.Gen.TypeGenErr.fatal_error(cl,
f"'{st_mod}.{st_at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
if cl is None: return None
func_name: str = self.Gen.ExprGenBase.get_func_name(cl.func)
+42
View File
@@ -202,6 +202,16 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
# 检查是否是已导入模块
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_path_w) == 0:
return None
# static 符号(`static x: int` / @t.CStatic / x: t.CStatic|T):语义为
# 禁止跨模块使用(C 的 internal linkage),模块限定属性访问命中别的模块
# 的 static 符号即 fail-fast。同模块自引用(sha1 相同)放行。
mod_sha1_st: str = self._lookup_module_sha1(mod_path_w)
if mod_sha1_st is not None and trans.ModuleSha1 is not None \
and string.strncmp(mod_sha1_st, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(mod_sha1_st, at.attr) != 0:
self.Gen.TypeGenErr.fatal_error(at,
f"'{mod_path_w}.{at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# 在当前模块查找全局变量:优先按"目标模块 SHA1 前缀 + 属性名"精确匹配,
# 避免多模块同名全局(@<A>._mbuddy / @<B>._mbuddy 同时注入)时裸名后缀
# 匹配返回错误模块的全局(串扰)。目标 sha1 未知时退化裸名(后缀匹配兜底)
@@ -343,6 +353,38 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
VLogger.error(diag_buf_an, "ATTR")
return None
# static 符号(禁止跨模块使用)——统一入口拦截:
# 属性基座是已导入模块名(含 import 别名还原)、且 attr 命中该模块登记的
# static 符号(`static def f` / `static x: int` / @t.CStatic / x: t.CStatic|T)
# 时 fail-fast。放在所有分支之前,一次覆盖 `mod.fn`(函数取址)、
# `mod.var`(全局变量)等全部模块限定属性形态。同模块自引用(sha1 相同)
# 与非模块基座(obj.field / Class.field)放行。
if trans is not None and trans.ModuleSha1 is not None \
and trans._imported_modules is not None \
and self.Gen.CallGenBase.StaticCount > 0:
st_path: str = self._attr_module_path_str(pool, at.value)
if st_path is not None and trans._from_imports is not None:
orig_st: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, st_path, 0)
if orig_st is not None:
ol_st: sizet = 0
while orig_st[ol_st] != ' ' and orig_st[ol_st] != '\0' and orig_st[ol_st] != ':':
ol_st += 1
if ol_st > 0:
ob_st: str = pool.alloc(ol_st + 1)
if ob_st is not None:
string.strncpy(ob_st, orig_st, ol_st)
ob_st[ol_st] = '\0'
st_path = ob_st
if st_path is not None \
and self.Gen.ImportsGen.is_module_imported(trans._imported_modules, st_path) != 0:
st_sha1: str = self._lookup_module_sha1(st_path)
if st_sha1 is not None \
and string.strncmp(st_sha1, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(st_sha1, at.attr) != 0:
self.Gen.TypeGenErr.fatal_error(at,
f"'{st_path}.{at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# 魔法属性 __doc__:查询 docstring 登记表(预注册阶段从 AST 提取),
# 命中则当前模块内联生成字符串常量返回;未命中(无 docstring 的
# 函数/类/方法)返回 null 指针(i8*)。支持本模块与跨模块符号:
+20 -1
View File
@@ -147,7 +147,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
has_export = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExport", 0)
# 装饰器形式存储类标记(@t.CExport/@t.CExtern/@t.State/@t.CStatic)
# 对齐 TPC HandlesFunctions._extract_decorator_storage_flags;
# CExport/CExtern/State 影响函数名(导出裸名),CStatic 仅标记。
# CExport/CExtern/State 影响函数名(导出裸名),CStatic 不影响函数名,
# 但登记到 static 符号表(禁止跨模块使用,见下方 RegisterStaticSymbol)。
# 注意: 用 HandlesClassDef._has_decorator 委托而非 self._has_decorator
# (Mixin 是 @t.NoVTable 静态绑定,跨 Mixin 方法经 MRO 调用会生成
# 不存在的 FuncGenMixin._has_decorator → 链接 undefined symbol)
@@ -162,6 +163,17 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if has_extern != 0 or has_state != 0:
is_extern_decl = 1
# static(禁止跨模块使用):注解形式(`def f() -> t.CVoid | t.CStatic:`)
# 与装饰器形式(`@t.CStatic`)同源识别,命中即登记到 static 符号表。
# 裸 `static def f(...)` 已在解析层归一为 @t.CStatic 装饰器
# (parser.vp 的 _ParseStaticModified),此处无需额外判据。
has_static: int = 0
if fd.returns is not None:
has_static = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CStatic", 0)
if has_static == 0 and fd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CStatic") == 1:
has_static = 1
# 计算 mangled name
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0:
mangled_name: str = fd.name
@@ -211,6 +223,13 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None:
self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
# 登记 static 符号(禁止跨模块使用):定义侧登记 (符号名, 模块 SHA1),
# 跨模块引用侧(mod.name 取值 / mod.fn() 调用 / from mod import name)
# 经 IsStaticSymbol 拦截并 fail-fast。函数名用裸名 fd.name 登记——
# 跨模块引用侧的解析入口拿到的也是源码里的裸名(不含 sha1 前缀)。
if has_static != 0 and trans.ModuleSha1 is not None:
self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, fd.name)
# 添加参数
args_node: ast.Arguments* = fd.args
if args_node is not None:
+13
View File
@@ -957,6 +957,19 @@ novirtual class ImportsGenMixin:
fi_target = jb_t
trans._from_imports = self.add_from_import(trans.Pool, trans._from_imports,
local_name, fi_target, orig_name)
# static 符号(禁止跨模块使用):from mod import name 命中源
# 模块登记的 static 符号 → fail-fast(与 `mod.name` 取值 /
# `mod.fn()` 调用两处拦截同源,覆盖 from-import 形态)。
# 用 alias.name(源码原名)判表——登记侧也用裸名。
if trans.ModuleSha1 is not None and fi_target is not None \
and self.Gen.CallGenBase.StaticCount > 0:
fi_sha1_st: str = self.Gen.StructGen._resolve_module_sha1(fi_target, trans._from_imports)
if fi_sha1_st is not None \
and string.strncmp(fi_sha1_st, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(fi_sha1_st, alias.name) != 0:
self.Gen.TypeGenErr.fatal_error(node,
f"'{fi_target}.{alias.name}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# 命名空间隔离:from-import 的名称标记为可见结构体
self.Gen.StructGen.add_visible_struct(trans.Pool, local_name)
# 注册 re-export 映射:当前模块 re-export 了 local_name,
+9
View File
@@ -687,6 +687,15 @@ novirtual class TranslatorGenMixin:
nm: ast.Name* = ast.Name*(aa.target)
var_name = nm.id
annot_node = aa.annotation
# static 符号(禁止跨模块使用):模块级变量带 t.CStatic 标记
# (`static x: int = 0` 经解析层归一为 `x: t.CStatic | int`;亦覆盖
# 手写 `x: t.CStatic | int`)→ 登记 (变量名, 定义模块 SHA1)。
# 变量无装饰器通道(@t.CStatic 只作用于 def),故只看注解。
# 时机:本处已在 `_declare_only` 提前返回(L662)之后,预声明遍不会
# 走到,避免同一符号重复登记。
if annot_node is not None and var_name is not None and trans.ModuleSha1 is not None:
if self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CStatic", 0) != 0:
self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, var_name)
# t.Callable 模块级函数指针:登记 (变量名 → 返回类型) 签名。此路径原先
# 只创建全局变量、不登记签名,模块级 t.Callable 变量被间接调用时在
# CallGenLeafKit._translate_func_ptr_call 报"返回类型未注册(t.Callable