fix(gen): return 本帧存储改为声明感知 fail-fast(A2 方案 A)
A2 原本整体移除定义端 return <本帧地址> 的 fail-fast,导致无 @life(ret(...)) 声明的函数返回本帧存储时静默通过(调用点只拿到 LIFE_UNKNOWN,悬垂一路无声)。 现改为按被调者自己的 ret 声明判定: - ret(frame) / ret(borrows=<该形参>) → 放行; - ret(heap|static|borrows=none) → 报错(声明与返回值矛盾); - 无声明 → 报错(提示补 @life(ret(frame)) 或 ret(borrows=<param>))。 落点:AssignGenMixin.check_return_frame_decl(判据复用 life_of_expr,target_ty 传 返回类型,与移除前的口径一致);ret_kind 由 StructGenMixin.get_ret_kind_by_func 反查 EscFuncTable → EscDirTable(顶层函数/方法/嵌套 def 一次覆盖,无需猜类名), 当前 FunctionDef 取 StructGenMixin.NllCurFunc。 新检查是旧检查的严格子集 ⇒ 自举无回归。
This commit is contained in:
1 parent
6da8e2e095
commit
79d1c86982
4 files changed
+80
-5
No files matched your search
@@ -844,6 +844,55 @@ novirtual class AssignGenMixin:
|
||||
leak_l = self.Gen.StructGen.escape_leak_line(leak_func, leak_param)
|
||||
if leak_l > 0: leak_f = self.Gen.TypeGenResolve.get_current_file()
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local; using it at {where} hands out a dangling pointer once this frame returns. Copy the value, or allocate on the heap (`new heap hlife ...`).", "LifetimeError", 0, 0, leak_f, leak_l)
|
||||
return 0
|
||||
|
||||
# check_return_frame_decl - **定义端** `return <本帧存储>` 的**声明感知**检查(A2 / §14.3 第 6 条)。
|
||||
# A2 移除了"一律拦"的旧判据:定义端分不清"交出本帧"与"借出调用方的存储"(后者是
|
||||
# `return <借用形参>`,一律拦会误报)。但也不能一律放 —— 无声明的函数返回本帧存储会让调用点
|
||||
# 永远只拿到"未知"(悬垂指针一路静默)。判据收敛到被调者自己的 `@life(ret(...))`:
|
||||
# · `ret(frame)` → 放行(去向由调用点负责);
|
||||
# · `ret(borrows=<该形参>)` → 放行(借出的正是那个形参);
|
||||
# · 无声明 / `ret(heap|static|borrows=none)` → `LifetimeError`(提示补声明 / 改声明)。
|
||||
# 拿不到当前函数定义(非三条函数体翻译路径)时**不判**:宁可少报,也不误报。
|
||||
def check_return_frame_decl(self, trans: HT.Translator*, value: ast.AST*) -> int:
|
||||
"""返回值是本帧存储且当前函数的 ret 声明不允许 → LifetimeError;0=放行"""
|
||||
if trans is None or value is None: return 0
|
||||
fd: ast.AST* = self.Gen.StructGen.NllCurFunc
|
||||
if fd is None: return 0
|
||||
rv_ty: mlirlite.LLVMType* = None
|
||||
if trans._cur_func is not None: rv_ty = mlirlite.function_get_ret_ty(trans._cur_func)
|
||||
# target_ty 必须传返回类型:裸本帧数组名在**指针**返回类型下是数组退让(交出栈地址),
|
||||
# 在同型聚合返回类型下是按值拷贝(LIFE_VALUE,不在此列)。
|
||||
if self.life_of_expr(trans, value, rv_ty) != GenTypes.LIFE_FRAME: return 0
|
||||
rk: int = GenApi.get_ret_kind_by_func(fd)
|
||||
if rk == GenApi.RET_KIND_FRAME: return 0
|
||||
is_borrow_kind: int = 0
|
||||
pi: int = -1
|
||||
if rk >= GenApi.RET_KIND_BORROW_BASE:
|
||||
is_borrow_kind = 1
|
||||
pi = rk - GenApi.RET_KIND_BORROW_BASE
|
||||
# 只有"返回的正是被声明的那个形参"才算借出;返回别的本帧存储仍是悬垂
|
||||
if self.Gen.StructGen._ParamIndex(ast.FunctionDef*(fd), value) == pi: return 0
|
||||
# 报文里给出名字(&x / x),便于定位
|
||||
nm_txt: str = "expression"
|
||||
nm_pre: str = ""
|
||||
if value.kind() == ast.ASTKind.Name:
|
||||
n2: ast.Name* = ast.Name*(value)
|
||||
if n2.id is not None: nm_txt = n2.id
|
||||
elif value.kind() == ast.ASTKind.UnaryOp:
|
||||
u2: ast.UnaryOp* = ast.UnaryOp*(value)
|
||||
if u2.operand is not None and u2.operand.kind() == ast.ASTKind.Name:
|
||||
n3: ast.Name* = ast.Name*(u2.operand)
|
||||
if n3.id is not None:
|
||||
nm_txt = n3.id
|
||||
nm_pre = "&"
|
||||
if rk == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local and this function has no `@life(ret(...))` declaration, so callers cannot know the result's lifetime. Declare `@life(ret(frame))` (the result may point into this frame; callers must keep it in-frame) or `@life(ret(borrows=<param>))` (the result borrows a parameter), or return a value copy / a heap allocation (`new heap hlife ...`).", "LifetimeError")
|
||||
return 0
|
||||
if is_borrow_kind != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local but this function declares `@life(ret(borrows=<param>))` (parameter #{pi}), which only permits returning that parameter. Return that parameter, or declare `@life(ret(frame))`.", "LifetimeError")
|
||||
return 0
|
||||
self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local but this function is declared as non-frame (`@life(ret(heap))` / `ret(static)` / `ret(borrows=none)`); the declaration contradicts the returned value. Fix the declaration, or return a non-frame value.", "LifetimeError")
|
||||
return 0
|
||||
|
||||
# _SlifeDropOwned - 该槽已被登记为 slife 绑定时交还所有权:释放它当前拥有的对象、影子清 0。
|
||||
|
||||
@@ -574,6 +574,12 @@ def get_ret_kind_method(pool: memhub.MemBuddy*, class_name: str, method_name: st
|
||||
return 0
|
||||
|
||||
|
||||
def get_ret_kind_by_func(fd: ast.AST*) -> int:
|
||||
"""**定义端**按 FunctionDef* 取本函数的 `@life(ret(...))` 契约(顶层/方法/嵌套都覆盖);0 = 无声明"""
|
||||
if _g_gen is not None: return _g_gen.StructGen.get_ret_kind_by_func(fd)
|
||||
return 0
|
||||
|
||||
|
||||
def lookup_ret_kind_via_vpi(cls_sha1: str, class_name: str, func_name: str) -> int:
|
||||
"""跨模块给被调者的 `@life(ret(...))` 契约;0 = 无声明(含该模块未加载)"""
|
||||
if _g_gen is not None: return _g_gen.CallGenBase.lookup_ret_kind_via_vpi(cls_sha1, class_name, func_name)
|
||||
|
||||
@@ -434,11 +434,12 @@ novirtual class StmtGenCifMixin:
|
||||
# f-string escape (return f"..."): its buffer lives on this frame's stack and dies on
|
||||
# return; copy it out explicitly (GenApi.escape_fstring_copy) before returning it.
|
||||
GenApi.fail_if_fstring_escape(rt.value, "function return value")
|
||||
# A2(§14.3 第 6 条):此处**不再**对 `return &local` / `return arr`(指针位置的数组退让)
|
||||
# 直接 fail-fast。返回值去向改由**调用点**按被调者的 `@life(ret(...))` 契约判定(见
|
||||
# AssignGenMixin.life_of_expr 的 ret_kind 分支):定义端无法区分"交出本帧"与"借出调用方的
|
||||
# 存储",一律拦会误报 `return <借用形参>`。必须在 `ret_kind` 契约(同批)落地后移除,
|
||||
# 否则未声明的函数会静默漏检。
|
||||
# A2(§14.3 第 6 条,方案 A):定义端对 `return <本帧存储>` 的**声明感知** fail-fast。
|
||||
# A2 移除了"一律拦"的旧判据(定义端分不清"交出本帧"与"借出调用方的存储"),
|
||||
# 但也不能一律放:判据收敛到被调者自己的 `@life(ret(...))` —— `ret(frame)` 或
|
||||
# `ret(borrows=<该形参>)` 放行;无声明 / 声明为非帧 → LifetimeError。
|
||||
# 实现见 AssignGenMixin.check_return_frame_decl。
|
||||
self.Gen.AssignGen.check_return_frame_decl(trans, rt.value)
|
||||
# return a, b → pack an anonymous struct value (multi-return, return type tuple[T1,T2])
|
||||
if rt.value.kind() == ast.ASTKind.Tuple:
|
||||
rtp: ast.Tuple* = ast.Tuple*(rt.value)
|
||||
|
||||
@@ -573,6 +573,25 @@ novirtual class StructGenMixin:
|
||||
if idx < 0: return 0
|
||||
return int(self.EscDirTable[idx * 4 + 3])
|
||||
|
||||
# get_ret_kind_by_func - **定义端**(`return` 处)用:按 FunctionDef* 反查本函数的 ret_kind。
|
||||
# 不靠"类名拼接":EscFuncTable 里已有"名哈希 → FunctionDef*",反查拿到哈希后再去
|
||||
# EscDirTable 取 ret_kind 列 —— 顶层函数 / 方法 / 嵌套 def 三种键形式一次性覆盖,
|
||||
# 无需在翻译期猜测 Class 名。0 = 无声明(或该函数未被登记)。
|
||||
def get_ret_kind_by_func(self, fd: ast.AST*) -> int:
|
||||
"""当前函数体的 `@life(ret(...))` 编码;0 = 无声明"""
|
||||
if fd is None or self.EscFuncTable is None: return 0
|
||||
i: int = 0
|
||||
while i < self.EscFuncCount:
|
||||
if sizet(self.EscFuncTable[i * 2 + 1]) == sizet(fd):
|
||||
h: sizet = self.EscFuncTable[i * 2]
|
||||
j: int = 0
|
||||
while j < self.EscDirCount:
|
||||
if self.EscDirTable[j * 4] == h: return int(self.EscDirTable[j * 4 + 3])
|
||||
j += 1
|
||||
return 0
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
# get_ret_kind_method - 方法版 `@life(ret(...))`:查 "Class.method" 并沿 MRO 回退。
|
||||
def get_ret_kind_method(self, pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
|
||||
if method_name is None or class_name is None: return 0
|
||||
|
||||
Reference in new issue
Block a user