feat(gen): 栈地址逃逸检查(LifetimeError)、按值返回聚合、标量→聚合 fail-fast

- GenApi.fail_if_stack_addr_escape:禁止把本帧栈地址写出帧外(显式 &local,以及局部数组名退让到指针目标,如 return arr with '-> char*');裸聚合名 + 同型聚合目标是值拷贝,不在其列;err_type 记为 LifetimeError。 - StmtGenCifMixin.translate_return:按值返回聚合时,对被指类型与返回聚合类型同一的指针补 load(与多返回值 struct 的 ret 同形),修复 return 局部数组报 CastError。 - ExprGenBase.coerce_to_type:聚合目标不接受标量(旧行为落成 store i32 0, ptr,只写首元素字节并抬高对齐),改为编译期 fail-fast。
This commit is contained in:
TermiNexus committed 2026-10-08 12:10:13 +08:00
1 parent 2ab6342d3c
commit 7070bc1608
4 files changed
+83 -2

No files matched your search

+36
View File
@@ -457,6 +457,42 @@ def escape_fstring_copy(src: str) -> str:
return out
# Stack-escape rule: the address of a stack object must not outlive the current frame.
# Two forms hand out this frame's stack address:
# (a) `&name` — the explicit address of a local variable / parameter;
# (b) `name` — a bare local ARRAY used where a pointer is expected (array decay,
# e.g. `def f() -> char*: ...; return arr`).
# A bare name with a matching aggregate target is a value copy, never an escape.
def fail_if_stack_addr_escape(node: ast.AST*, where: str, trans: HT.Translator*, target_ty: mlirlite.LLVMType* = None) -> int:
"""Fail fast when the address of a stack object escapes the current frame."""
if node is None or trans is None or _g_gen is None: return 0
nm_sa: str = None
if node.kind() == ast.ASTKind.UnaryOp:
uo_sa: ast.UnaryOp* = ast.UnaryOp*(node)
if uo_sa.op == ast.OpKind.AddrOf and uo_sa.operand is not None and uo_sa.operand.kind() == ast.ASTKind.Name:
nm0_sa: ast.Name* = ast.Name*(uo_sa.operand)
if nm0_sa.id is not None: nm_sa = nm0_sa.id
elif node.kind() == ast.ASTKind.Name and target_ty is not None:
# 数组退让(array decay):本帧局部数组名出现在指针目标位置 ⇒ 交出首元素地址(栈地址)
if _g_gen.ExprGenBase.is_ptr_type(target_ty) != 0:
nm1_sa: ast.Name* = ast.Name*(node)
if nm1_sa.id is not None:
v1_sa: mlirlite.Value* = _g_gen.VarGen.lookup_var(trans.SymTab, nm1_sa.id)
if v1_sa is not None and v1_sa.Ty is not None and _g_gen.ExprGenBase.is_ptr_type(v1_sa.Ty) != 0:
inner1_sa: mlirlite.LLVMType* = v1_sa.Ty.Pointee
if inner1_sa is not None and inner1_sa.kind() == mlirlite.LLVMType.Array: nm_sa = nm1_sa.id
if nm_sa is None: return 0
# Nonlocal captures are heap-promoted and may outlive the frame.
if is_nonlocal_name(trans, nm_sa) != 0: return 0
# Only a local alloca of this frame is stack storage; module globals / function names
# do not resolve in the local scope and are allowed to escape.
if _g_gen.VarGen.lookup_var(trans.SymTab, nm_sa) is None: return 0
_g_gen.TypeGenErr.fatal_error(node, f"stack address escapes: `{nm_sa}` refers to storage of the current frame (local variable/array or parameter); using it at {where} hands out a stack address, which becomes invalid once the function returns (dangling pointer). Return the value itself (a value copy) or allocate on the heap (`new heap hlife ...`).", "LifetimeError")
return 0
# ---------------- create_wrapper_main (delegates to TranslatorGenMixin.create_wrapper_main) ----------------
def create_wrapper_main(trans: HT.Translator*, tree: ast.AST*, i32_ty: mlirlite.LLVMType*) -> int:
"""Delegate to gen.create_wrapper_main"""