fix(类型): str/bytes 不再作通用指针通配,补齐实参/赋值/返回三处隐式转换 fail-fast

规则:void*/t.CPtr/rawptr 允许隐式转为任意指针类型,str/bytes 不允许(即便两者在 IR 层同为 i8*)。此前 coerce_to_type 的规则 1(通用指针通配)会把指向结构体/类/数组/函数的指针静默 bitcast 到 str 目标(反向亦然)。

- ExprGenBase:新增 _annot_name_is_bare_str/_annot_is_bare_str(裸 str/bytes 判定,组合段如 str|t.CPtr 不受影响)、fail_if_str_annot_agg_ptr(值是指向聚合类型的指针则 fail-fast,消息 CastError)、fail_if_str_annot_ast_agg_ptr(AST 注解入口)。

- 返回路径 StmtGenCifMixin.translate_return:在类型转换分支之前用 trans.CurFuncRetAnnot 判定,避免被规则 1 静默 bitcast。

- 赋值路径 AssignGenMixin.translate_annassign:四个分支(global / nonlocal / mod_glob / 局部)在 coerce 前用 aa.annotation 判定。

- 同模块实参 CallGenMixin.translate_call:在参数循环前一次性确保本模块 .vpi 参数注解缓存就绪(同跨模块 CallGenLib 键式:模块级函数 cls=None、段下标==形参下标),循环内按 _vpi_param_annot_seg_is 判定裸 str/bytes。
This commit is contained in:
TermiNexus committed 2026-10-03 14:46:57 +08:00
1 parent 8af967db7f
commit 09ea7283f9
4 files changed
+101

No files matched your search

+10
View File
@@ -2287,6 +2287,9 @@ novirtual class AssignGenMixin:
if mod_alloca.Ty is not None:
target_ty = mod_alloca.Ty.Pointee
if target_ty is not None:
# 「str/bytes 不是通用指针通配」:目标注解为裸 str/bytes 而右值
# 是指向聚合类型(结构体/类/数组/函数)的指针 → fail-fast。
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile(global 声明)全局:初始化 store 带 volatile 限定符
vol_gaa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
@@ -2329,6 +2332,7 @@ novirtual class AssignGenMixin:
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_coerced: llvmlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node)
llvmlite.build_store(builder, rhs_coerced, nl_ptr)
# 清除赋值上下文(防残留):与主分支清空时机同款。四处(主分支 /
@@ -2395,6 +2399,9 @@ novirtual class AssignGenMixin:
if mod_glob.Ty is not None:
target_ty_mg = mod_glob.Ty.Pointee
if target_ty_mg is not None:
# 「str/bytes 不是通用指针通配」:同上(global 分支),
# 目标注解为裸 str/bytes 而右值是指向聚合类型的指针 → fail-fast。
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val_mg, aa.annotation, node)
rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node)
# t.CVolatile 模块级全局:初始化 store 带 volatile 限定符
vol_mga: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
@@ -2637,6 +2644,9 @@ novirtual class AssignGenMixin:
if alloca.Ty is not None:
target_ty = alloca.Ty.Pointee
if target_ty is not None:
# 「str/bytes 不是通用指针通配」:目标注解为裸 str/bytes 而右值
# 是指向聚合类型(结构体/类/数组/函数)的指针 → fail-fast。
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile 局部变量:标记 VarEntry 并让本次 store 带 volatile
vol_laa: int = 0
+23
View File
@@ -1066,6 +1066,17 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
mc_dst2: llvmlite.Value* = None
mc_src2: llvmlite.Value* = None
mc_num2: llvmlite.Value* = None
# 「str/bytes 不是通用指针通配」前置:同模块**裸名**函数的形参注解来自本
# 模块的 .vpi(与跨模块路径 CallGenLib._coerce_args_to_def_params 同源同
# 键式:模块级函数 cls=None、段下标 == 形参下标)。在参数循环前一次性确保
# 本模块 vpi 参数注解缓存就绪,循环内每参只做一次 O(1) 段等值判定。
# 仅裸名(cl.func 是 Name):模块限定调用走 _coerce_args_to_def_params 已覆盖。
same_mod_vpi: int = 0
if trans is not None and trans.ModuleSha1 is not None \
and cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
VpiReader._ensure_vpi_param_annot_loaded(trans.ModuleSha1, self.Gen.CallGenBase.TempDir)
same_mod_vpi = 1
for ai in range(can):
arg: ast.AST* = cargs.get(ai)
if arg is not None:
@@ -1123,6 +1134,18 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
# 修复: CDefine/i32 常量传给 i64 形参未提升,导致 call(i32) 与 define(i64) 不匹配
if fp is not None:
if fpty is not None:
# 「str/bytes 不是通用指针通配」:同模块裸名函数的定义侧
# 形参注解恰为裸 str/bytes,而实参是指向聚合类型(结构体/
# 类/数组/函数)的指针 → fail-fast(IR 层同为 i8*,但语义
# 不同)。与跨模块路径 CallGenLib._coerce_args_to_def_params
# 的同名检查同规则、同判据(段等值判定只做逐字节比较)。
sm_is_str: int = 0
if same_mod_vpi != 0:
if VpiReader._vpi_param_annot_seg_is(trans.ModuleSha1, None, func_name, ai, "str") != 0 \
or VpiReader._vpi_param_annot_seg_is(trans.ModuleSha1, None, func_name, ai, "bytes") != 0:
sm_is_str = 1
if sm_is_str != 0:
self.Gen.ExprGenBase.fail_if_str_annot_agg_ptr(arg_val, 1, arg)
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, fpty, arg, trans)
elif found_func.IsVarArg == 1:
# Win64 变参 8 字节槽位:<64 位整数按 i64 传(sext 符号扩展),
+63
View File
@@ -429,6 +429,69 @@ novirtual class ExprGenBase:
return 1
return 0
# ============================================================
# 「str / bytes 不是通用指针通配」——规则与统一 fail-fast
#
# 用户规则:`void*` / `t.CPtr` / `rawptr` 允许隐式转为任意指针类型;
# `str` / `bytes` 不允许——**即便它们在 LLVM 层同为 i8***(_is_universal_ptr_ty
# 只看"一级指针 + 8 位被指类型",无法区分 CPtr 与 str),所以 str-ness 只能由
# 调用方从**注解来源**给出:赋值侧 trans.CurAssignAnnot、返回侧
# trans.CurFuncRetAnnot、形参侧 vpi/函数签名注解段。
#
# 只认**裸名**:`str` / `bytes`。`str | t.CPtr` 的语义是 i8**(指针槽,
# 见 AssignGenMixin._is_ptr_element_annotation),不是字符串值本体,不适用。
# ============================================================
def _annot_name_is_bare_str(self, txt: str) -> int:
"""注解**文本**是否为裸 str / bytes;1=是 / 0=否"""
if txt is None or txt[0] == 0:
return 0
if string.strcmp(txt, "str") == 0 or string.strcmp(txt, "bytes") == 0:
return 1
return 0
def _annot_is_bare_str(self, annot: ast.AST*) -> int:
"""注解 AST 是否为裸 str / bytes(Name 形态);1=是 / 0=否"""
if annot is None or annot.kind() != ast.ASTKind.Name:
return 0
nm: ast.Name* = ast.Name*(annot)
if nm is None or nm.id is None:
return 0
return self._annot_name_is_bare_str(nm.id)
def fail_if_str_annot_agg_ptr(self, val: llvmlite.Value*,
annot_is_str: int,
node: ast.AST* = None) -> int:
"""注解侧是裸 str/bytes 而值是指向聚合类型的指针 → fail-fast(返回 1)
判据:val 是一级指针且被指类型是聚合(Struct/Array/Func)——即"结构体/
类/联合/数组/函数指针"。显式转换(如 t.CPtr(p))的产物 pointee 是 i8,
不触发本检查,故显式转换不受影响。
"""
if annot_is_str == 0 or val is None or val.Ty is None:
return 0
if self.is_ptr_type(val.Ty) == 0:
return 0
pointee: llvmlite.LLVMType* = val.Ty.Pointee
if pointee is None:
return 0
pk: int = pointee.kind()
if pk != llvmlite.LLVMType.Struct \
and pk != llvmlite.LLVMType.Array \
and pk != llvmlite.LLVMType.Func:
return 0
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
"str/bytes 不接受指向非基本类型(结构体/类/数组/函数)的指针:两者在 IR 层"
"同为 i8*,但语义不同(str/bytes 不是通用指针通配)。若确需传递指针,"
"请改用 t.CPtr/rawptr 形参,或显式转换(如 t.CPtr(p))", "CastError")
return 1
def fail_if_str_annot_ast_agg_ptr(self, val: llvmlite.Value*,
annot: ast.AST*,
node: ast.AST* = None) -> int:
"""注解 AST 形态的入口:注解为裸 str/bytes 且值是指向聚合类型的指针 → fail-fast
供注解来源是 AST 的三条路径复用:赋值侧 aa.annotation、返回侧
trans.CurFuncRetAnnot。形参侧注解来源是文本(vpi 段),直接调
fail_if_str_annot_agg_ptr 传 1 即可。
"""
return self.fail_if_str_annot_agg_ptr(val, self._annot_is_bare_str(annot), node)
# ============================================================
# _is_basic_scalar_ty - 判据:类型是否为基本类型(整数/浮点),1=是 / 0=否
#
# 非基本类型 = 结构体/类/联合/数组/函数等(LLVM 层 Struct/Array/Func)。
+5
View File
@@ -619,6 +619,11 @@ novirtual class StmtGenCifMixin:
llvmlite.build_ret_void(builder)
return 0
if ret_ty_rt is not None:
# 「str/bytes 不是通用指针通配」:函数返回注解为裸 str/bytes 而返回值是
# 指向聚合类型(结构体/类/数组/函数)的指针 → fail-fast。放在下面所有
# 类型转换分支之前:否则 val/ret 两侧同为 i8*(ptr→ptr)会被
# coerce_to_type 的规则 1(通用指针通配)静默 bitcast 放行。
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(val, trans.CurFuncRetAnnot, node)
val_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
ret_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(ret_ty_rt)
val_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(val.Ty)