feat(call): 跨模块实参加 str/bytes 形参的 fail-fast(str 不作通用指针通配),并加 vpi 注解段等值判定

This commit is contained in:
TermiNexus committed 2026-10-03 14:23:12 +08:00
1 parent feb29fa7a3
commit 98ee53340f
2 files changed
+63

No files matched your search

+41
View File
@@ -1056,6 +1056,47 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int:
return 1
# ============================================================
# _vpi_param_annot_seg_is - 形参注解段等值判定(供"str/bytes 不是通用指针通配"规则)
# ============================================================
def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str,
seg_idx: int, want: str) -> int:
"""vpi 定义侧第 seg_idx 个形参注解段是否**恰等于** want(1=是 / 0=否)
注解串格式(见 _lookup_vpi_param_annots):以 '@' 分隔的段序列,段 0 是
self 占位(空串)。本函数只做逐字节等值比较,不解析、不做模板类型形参替换
——调用方是跨模块调用的**热路径**(每个实参一次),不能在此分配/解析。
典型用途:判定定义侧形参注解是否为**裸** `str` / `bytes`(用于"str/bytes
不是通用指针通配"这条规则;`str | t.CPtr` 这类组合段不等值,不受影响)。
"""
if want is None or seg_idx < 0:
return 0
prm: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name)
if prm is None or prm[0] == '\0':
return 0
cur: sizet = 0
seg: int = 0
# 定位段起点:跳过 seg_idx 个 '@' 段
while seg < seg_idx:
while prm[cur] != '\0' and prm[cur] != '@':
cur += 1
if prm[cur] == '\0':
return 0
cur += 1
seg += 1
slen: sizet = 0
while prm[cur + slen] != '\0' and prm[cur + slen] != '@':
slen += 1
wlen: sizet = string.strlen(want)
if slen != wlen:
return 0
i: sizet
for i in range(slen):
if prm[cur + i] != want[i]:
return 0
return 1
# ============================================================
# _ensure_vpi_param_annot_loaded - 按需加载模块的 vpi stub 参数注解缓存
#
+22
View File
@@ -262,6 +262,28 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
arg_node: ast.AST* = None
if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs):
arg_node = cargs.get(arg_idx)
# 「str / bytes 不是通用指针通配」:`void*` / `t.CPtr` / `rawptr` 允许
# 隐式转为任意指针类型,但 `str` / `bytes` 不允许——即便它们在 LLVM
# 层同为 i8*。跨模块实参在这条路径上原本只做 bitcast、完全不过指针
# 转换校验,于是"把结构体/类/数组/函数指针传给 str 形参"会被静默放行
# ⇒ 后续把该指针当 C 字符串用时生成非法 IR(现场:把 Function* 传给
# `callee: str`,发射出 `call i32 @(i32 7)` 空符号名,直到 llc 才炸)。
# 判据:定义侧形参注解**恰为**裸 str/bytes(组合段如 `str|t.CPtr` 不
# 等值、不受影响),且实参是指向聚合类型(Struct/Array/Func)的指针。
if self.Gen.ExprGenBase.is_ptr_type(cur.Ty) != 0:
pointee_a: llvmlite.LLVMType* = cur.Ty.Pointee
if pointee_a is not None:
pk_a: int = pointee_a.kind()
if pk_a == llvmlite.LLVMType.Struct \
or pk_a == llvmlite.LLVMType.Array \
or pk_a == llvmlite.LLVMType.Func:
if VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "str") != 0 \
or VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "bytes") != 0:
self.Gen.TypeGenErr.fatal_error(arg_node,
"str/bytes 形参不接受指向非基本类型(结构体/类/数组/函数)的指针:"
"两者在 IR 层同为 i8*,但语义不同(str/bytes 不是通用指针通配)。"
"若确需传递指针,请改用 t.CPtr/rawptr 形参或显式转换(如 t.CPtr(p))",
"CastError")
node_v = self._coerce_arg_value_to_ty(pool, builder, cur, tgt_ty, arg_node, trans, tpl_src)
if new_head is None:
new_head = node_v