fix(core): print 类型感知格式化,跨模块未导入 fast-fail,报错行号防御
This commit is contained in:
1 parent
557a40c662
commit
adff2f35c4
3 files changed
+158
-4
No files matched your search
@@ -199,9 +199,11 @@ def translate_expr_stmt(trans: HT.Translator | t.CPtr,
|
||||
if func_name is None:
|
||||
return 0
|
||||
|
||||
# printf / print 走特殊路径(print 映射到 printf)
|
||||
if string.strcmp(func_name, "printf") == 0 or string.strcmp(func_name, "print") == 0:
|
||||
# printf / print 走特殊路径(print 是类型感知格式化,不映射到 printf)
|
||||
if string.strcmp(func_name, "printf") == 0:
|
||||
trans.ExprCallH.HandlePrintfCall(cl)
|
||||
elif string.strcmp(func_name, "print") == 0:
|
||||
trans.ExprCallH.HandlePrintCall(cl)
|
||||
else:
|
||||
# 通用函数调用(返回值丢弃)
|
||||
trans.ExprCallH.HandleCall(call_node)
|
||||
|
||||
@@ -5541,6 +5541,22 @@ def translate_call(pool: memhub.MemBuddy | t.CPtr,
|
||||
if xmod_nm.id is not None:
|
||||
mod_sha1: str = _lookup_module_sha1(xmod_nm.id)
|
||||
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)
|
||||
reexport_src: str = HandlesImports.lookup_reexport(mod_sha1, func_name)
|
||||
if reexport_src is not None:
|
||||
@@ -5839,6 +5855,122 @@ def translate_printf_call(pool: memhub.MemBuddy | t.CPtr,
|
||||
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)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# stdarg 糖函数实现: va_arg / arg / va_start / va_end
|
||||
#
|
||||
@@ -6550,6 +6682,15 @@ class ExprCallHandle(HandlesBase.Mixin):
|
||||
self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module,
|
||||
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 - 工厂函数
|
||||
# ============================================================
|
||||
|
||||
@@ -836,9 +836,20 @@ def fatal_error(node: ast.AST | t.CPtr, msg: str,
|
||||
file_name: str = _g_current_file
|
||||
if file_name is None:
|
||||
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:
|
||||
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:
|
||||
stdio.printf(" %s\n", src_line)
|
||||
# 列范围标记:在源代码行下方用 ^ 标记出错范围
|
||||
|
||||
Reference in new issue
Block a user