fix(core): print 类型感知格式化,跨模块未导入 fast-fail,报错行号防御

This commit is contained in:
TermiNexus committed 2026-08-07 22:36:16 +08:00
1 parent 557a40c662
commit adff2f35c4
3 files changed
+159 -5

No files matched your search

+4 -2
View File
@@ -199,9 +199,11 @@ def translate_expr_stmt(trans: HT.Translator | t.CPtr,
if func_name is None: if func_name is None:
return 0 return 0
# printf / print 走特殊路径(print 映射到 printf) # printf / print 走特殊路径(print 是类型感知格式化,不映射到 printf)
if string.strcmp(func_name, "printf") == 0 or string.strcmp(func_name, "print") == 0: if string.strcmp(func_name, "printf") == 0:
trans.ExprCallH.HandlePrintfCall(cl) trans.ExprCallH.HandlePrintfCall(cl)
elif string.strcmp(func_name, "print") == 0:
trans.ExprCallH.HandlePrintCall(cl)
else: else:
# 通用函数调用(返回值丢弃) # 通用函数调用(返回值丢弃)
trans.ExprCallH.HandleCall(call_node) trans.ExprCallH.HandleCall(call_node)
+142 -1
View File
@@ -5541,6 +5541,22 @@ def translate_call(pool: memhub.MemBuddy | t.CPtr,
if xmod_nm.id is not None: if xmod_nm.id is not None:
mod_sha1: str = _lookup_module_sha1(xmod_nm.id) mod_sha1: str = _lookup_module_sha1(xmod_nm.id)
if mod_sha1 is not None: if mod_sha1 is not None:
# fast-fail: 模块限定调用要求模块已被当前文件 import。
# 全局 SHA1 存储包含所有被扫描的 includes,未导入的模块
# (如 this.this() 但 import this 被注释)也会命中存储,
# 静默生成 @{sha1}.func 调用而非报错——违反"找不到 fast-fail"原则。
is_xmod_imported: int = 1
if trans is not None and trans._imported_modules is not None:
is_xmod_imported = HandlesImports.is_module_imported(
trans._imported_modules, xmod_nm.id)
if is_xmod_imported == 0:
err_ni: t.CChar | t.CPtr = pool.alloc(256)
if err_ni is not None:
viperlib.snprintf(err_ni, 256,
"跨模块调用 '%s.%s':模块 '%s' 未导入(请先 import %s)",
xmod_nm.id, func_name, xmod_nm.id, xmod_nm.id)
HandlesType.fatal_error(xmod_nm, err_ni)
HandlesType.fatal_error(xmod_nm, "跨模块调用:模块未导入")
# 检查是否是 re-export 函数(如 ast._lexer_init 实际定义在 ast.lexer) # 检查是否是 re-export 函数(如 ast._lexer_init 实际定义在 ast.lexer)
reexport_src: str = HandlesImports.lookup_reexport(mod_sha1, func_name) reexport_src: str = HandlesImports.lookup_reexport(mod_sha1, func_name)
if reexport_src is not None: if reexport_src is not None:
@@ -5836,7 +5852,123 @@ def translate_printf_call(pool: memhub.MemBuddy | t.CPtr,
# is_variadic=1: 生成带显式函数类型签名的 call(如 call i32 (i8*, ...) @printf), # is_variadic=1: 生成带显式函数类型签名的 call(如 call i32 (i8*, ...) @printf),
# 让 llc 识别为 variadic 调用,生成正确的 Win64 ABI 代码(浮点参数同时传 XMM 和整数寄存器) # 让 llc 识别为 variadic 调用,生成正确的 Win64 ABI 代码(浮点参数同时传 XMM 和整数寄存器)
llvmlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1) llvmlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1)
# ============================================================
# 翻译 print 调用 — 类型感知格式化 + 默认换行
#
# 与 printf 不同,print 不需要用户提供格式串:每个参数按 LLVM
# 类型自动选择格式(%s/%d/%f/0x%llx),结尾追加 "\n"(对齐 Python
# print 默认 end="\n",以及 TPC HandlesExprBuiltin._HandlePrintLlvm)。
#
# 类型分派规则:
# i8*(str/bytes) → %s
# 其他指针(struct*/int*) → 0x%llx(ptrtoint 到 i64)
# i1(bool) → %d(zext 到 i32)
# i8/i16/i32 → %d(sext 到 i32,C ABI 整数提升)
# i64 → %lld
# float/double → %f(float fpext 到 double,C ABI 提升)
# 无法识别的类型 → "(unknown)"(不传参)
# ============================================================
def translate_print_call(pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cl: ast.Call | t.CPtr,
trans: HT.Translator | t.CPtr = None):
"""翻译 print 调用(类型感知格式化 + 默认换行)"""
cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args
if cargs is None:
return
FMT_BUF_SIZE: t.CSizeT = 256
fmt_buf: bytes = stdlib.malloc(FMT_BUF_SIZE)
if fmt_buf is None:
return
fmt_buf[0] = '\0'
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
dbl_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool)
can: t.CSizeT = cargs.__len__()
arg_head: llvmlite.Value | t.CPtr = None
arg_tail: llvmlite.Value | t.CPtr = None
arg_count: int = 1 # 格式串本身是第 1 个 printf 参数
for ai in range(can):
arg: ast.AST | t.CPtr = cargs.get(ai)
if arg is None:
continue
arg_val: llvmlite.Value | t.CPtr = HandlesExpr.translate_value(
builder, pool, mod, arg, None, 0, trans)
if arg_val is None or arg_val.Ty is None:
# 参数翻译失败 → "(null)"(与 TPC 一致,不传参)
if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16:
viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "(null)")
continue
token: t.CChar | t.CPtr = None
chain_arg: int = 1 # 1=该 token 消耗一个参数,0=纯文本
arg_bits: int = HandlesExpr.get_llvm_type_bits(arg_val.Ty)
arg_fbits: int = HandlesExpr.get_llvm_float_bits(arg_val.Ty)
if HandlesExpr.is_ptr_type(arg_val.Ty) != 0:
# i8*(str/bytes)→ %s;其他指针 → 0x%llx
pointee: llvmlite.LLVMType | t.CPtr = arg_val.Ty.Pointee
if pointee is not None and HandlesExpr.get_llvm_type_bits(pointee) == 8:
token = "%s"
else:
token = "0x%llx"
arg_val = llvmlite.build_ptrtoint(builder, arg_val, i64_ty)
elif arg_bits != 0:
if arg_bits == 1:
token = "%d"
arg_val = llvmlite.build_zext(builder, arg_val, i32_ty)
elif arg_bits == 8:
token = "%d"
arg_val = llvmlite.build_sext(builder, arg_val, i32_ty)
elif arg_bits == 16:
token = "%d"
arg_val = llvmlite.build_sext(builder, arg_val, i32_ty)
elif arg_bits == 32:
token = "%d"
else:
token = "%lld"
elif arg_fbits != 0:
token = "%f"
if arg_fbits < 64:
arg_val = llvmlite.build_fpext(builder, arg_val, dbl_ty)
else:
# 结构体值/数组/函数等 → "(unknown)",不传参
token = "(unknown)"
chain_arg = 0
# 追加格式 token
if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16:
viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "%s", token)
# 链接参数
if chain_arg != 0:
arg_val.Next = None
if arg_tail is None:
arg_head = arg_val
else:
arg_tail.Next = arg_val
arg_tail = arg_val
arg_count += 1
# 追加默认换行(Python print 默认 end="\n")
if string.strlen(fmt_buf) < FMT_BUF_SIZE - 2:
viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 2, "\n")
fmt_ptr: llvmlite.Value | t.CPtr = HandlesExpr.create_global_string(
builder, pool, mod, fmt_buf, trans)
stdlib.free(fmt_buf)
if fmt_ptr is None:
return
fmt_ptr.Next = arg_head
# is_variadic=1: 带显式函数类型签名(C ABI 变参调用)
llvmlite.build_call(builder, "printf", fmt_ptr, arg_count, i32_ty, 1)
# ============================================================ # ============================================================
@@ -6550,6 +6682,15 @@ class ExprCallHandle(HandlesBase.Mixin):
self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module, self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module,
cl, self.Trans) cl, self.Trans)
# ============================================================
# HandlePrintCall - 翻译 print 调用
# ============================================================
def HandlePrintCall(self, cl: ast.Call | t.CPtr):
"""翻译 print 调用(类型感知格式化 + 默认换行)"""
translate_print_call(
self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module,
cl, self.Trans)
# ============================================================ # ============================================================
# NewExprCallHandle - 工厂函数 # NewExprCallHandle - 工厂函数
# ============================================================ # ============================================================
+13 -2
View File
@@ -836,9 +836,20 @@ def fatal_error(node: ast.AST | t.CPtr, msg: str,
file_name: str = _g_current_file file_name: str = _g_current_file
if file_name is None: if file_name is None:
file_name = "<unknown>" file_name = "<unknown>"
stdio.printf(' File "%s", line %d\n', file_name, lineno) # 行号合理性防御:先读取该行源码,验证 node.lineno 是否落在当前文件内。
# 若读不到(文件不存在或行号超出范围),说明文件/行号归属不可信
# (如 AST 树被内存复用/串扰后跨文件报错),打印警告而非误导性行号。
src_line: str = None
line_ok: int = 1
if lineno > 0 and string.strcmp(file_name, "<unknown>") != 0: if lineno > 0 and string.strcmp(file_name, "<unknown>") != 0:
src_line: str = _read_source_line(file_name, lineno) src_line = _read_source_line(file_name, lineno)
if src_line is None:
line_ok = 0
if line_ok == 0:
stdio.printf(' File "%s", line %d (!! 无法读取该行源码:文件不存在或行号超出范围,错误可能来自被复用的 AST 树,请以错误消息为准)\n',
file_name, lineno)
else:
stdio.printf(' File "%s", line %d\n', file_name, lineno)
if src_line is not None: if src_line is not None:
stdio.printf(" %s\n", src_line) stdio.printf(" %s\n", src_line)
# 列范围标记:在源代码行下方用 ^ 标记出错范围 # 列范围标记:在源代码行下方用 ^ 标记出错范围