fix(gen): willFree 失效槽表补控制流合流,修复分支内 free 泄漏导致的误报

失效表原为平坦单调全局表,无分支合流语义:if 分支体内 free(x) 会把失效态泄漏到合流之后,于是 fileio.vp 的 if ...: free(buf); return None 之后 return buf 误报 LifetimeError(1784eaf 修好 free 的 wf 位后才暴露)。新增 freed_capture/freed_restore/freed_join(pool 快照 + 求交),并在 if(结合 then/else 是否到达合流)与 while/for 的既有 life_* 同点位接入:只有所有到达合流的路径都失效才算失效。
This commit is contained in:
TermiNexus committed 2026-10-10 14:10:53 +08:00
1 parent f2cbb658e1
commit eea8111e61
3 files changed
+81

No files matched your search

+17
View File
@@ -719,6 +719,8 @@ novirtual class StmtGenCifMixin:
# 生命期汇合:进入分支前快照(else 分支需从该状态重新开始,见 §11 控制流 Meet)
life_snap0: sizet* = self.Gen.VarGen.life_capture(trans)
# willFree 失效槽表同点位快照(分支体内的 free 不得泄漏到合流之后)
freed_snap0: sizet* = self.Gen.VarGen.freed_capture(trans)
# 5. translate the then body; reset the merge-reachable flag first (nested control flow
# overwrites it, so the branch-tail check must not see a stale value from before this If).
@@ -744,12 +746,14 @@ novirtual class StmtGenCifMixin:
# then 末状态快照(供两端 Meet:先复位到 if 前状态,再与 then 末状态合并)
life_snap1: sizet* = self.Gen.VarGen.life_capture(trans)
freed_snap1: sizet* = self.Gen.VarGen.freed_capture(trans)
# 6. translate the else body (if any)
else_branches_merge: int = 0
if has_else == 1:
# 复位到 if 前状态,让 else 分支从同一基线出发(否则会看到 then 分支写入的 Life)
self.Gen.VarGen.life_restore(life_snap0)
self.Gen.VarGen.freed_restore(freed_snap0)
# as in then: reset the flag to avoid stale values from the then branch
trans._ctrl_merge_reachable = 1
mlirlite.position_at_end(builder, else_bb)
@@ -769,6 +773,19 @@ novirtual class StmtGenCifMixin:
if has_else == 1: self.Gen.VarGen.life_join(life_snap1)
else: self.Gen.VarGen.life_join(life_snap0)
# willFree 失效槽表合流:只有"到达合流的路径"参与(否则会把"分支体内 free 后 return"
# 的失效态错误地带到合流之后——fileio.vp 的 `if …: free(buf); return None` 即此现场)。
# 两分支都到达 → 求交(curr=else 末态 ∩ then 末态);
# 仅 then 到达 → 取 then 末态;
# 仅 else/无 else → 取 else 末态(无 else 时即 if 前状态)。
if has_else == 1:
if then_branches_merge != 0 and else_branches_merge != 0:
self.Gen.VarGen.freed_join(freed_snap1)
elif then_branches_merge != 0:
self.Gen.VarGen.freed_restore(freed_snap1)
else:
self.Gen.VarGen.freed_restore(freed_snap0)
# 7. merge reachability (used by tail-flow checks): without else cond_br always reaches
# merge; with else, any branch br reaching merge suffices; only all-return is unreachable.
trans._ctrl_merge_reachable = 1
+6
View File
@@ -66,15 +66,21 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
elif k == ast.ASTKind.While:
# 循环生命期汇合:循环可能 0 次执行,出口状态须与进入前状态 Meet(见 §11 控制流 Meet)
snap_wh: sizet* = self.Gen.VarGen.life_capture(trans)
# willFree 失效槽表:同点位快照,出口与进入前状态**求交**(循环体可能 0 次执行,
# 故体内 free 不得被当成"循环后已失效")
freed_snap_wh: sizet* = self.Gen.VarGen.freed_capture(trans)
rc_wh: int = self.Gen.StmtGenCif.translate_while(trans, node)
self.Gen.VarGen.life_join(snap_wh)
self.Gen.VarGen.freed_join(freed_snap_wh)
return rc_wh
elif k == ast.ASTKind.AugAssign: return self.Gen.AssignGen.translate_augassign(trans, node)
elif k == ast.ASTKind.For:
# 同上:for 的各迭代形态(range/指针/数组/容器/解糖)统一在此收口
snap_fr: sizet* = self.Gen.VarGen.life_capture(trans)
freed_snap_fr: sizet* = self.Gen.VarGen.freed_capture(trans)
rc_fr: int = self.Gen.StmtGenCif.translate_for(trans, node)
self.Gen.VarGen.life_join(snap_fr)
self.Gen.VarGen.freed_join(freed_snap_fr)
return rc_fr
elif k == ast.ASTKind.ClassDef: return self.Gen.ClassGenDef.translate_class_def(trans, node)
elif k == ast.ASTKind.Import: return self.Gen.ImportsGen.translate_import(trans, node)
+58
View File
@@ -367,6 +367,64 @@ novirtual class VarGenMixin:
if symtab is None or name is None: return 0
return self._FreedClearSlot(self.lookup_var(symtab, name))
# ---- P2 willFree:失效槽表的控制流汇合(与 life_capture/life_restore/life_join 同点位) ----
# 背景:失效表是"平坦、单调"的全局表(_FreedMark 只插入),原本无分支合流语义,
# 于是"分支体内 free(x)"会把失效态泄漏到合流之后 → `if c: free(p); return` 之后的
# `return p` 误报。这里给 if/while/for 补上快照/复位/求交,口径 = "只有所有到达合流的
# 路径都失效,才算失效"(求交),与 life_* 的 Meet 同点位、同生命周期(pool 快照)。
def freed_capture(self, trans: HT.Translator*) -> sizet*:
"""快照当前失效槽表:buf[0]=count,其后每项一个失效槽指针;失败返回 None"""
if trans is None or trans.Pool is None: return None
pool: memhub.MemBuddy* = trans.Pool
n: sizet = sizet(_g_freed_count)
buf: sizet* = sizet*(pool.alloc((n + 1) * 8))
if buf is None: return None
buf[0] = n
i: sizet = 0
while i < n:
buf[1 + i] = _g_freed_slots[i]
i += 1
return buf
def freed_restore(self, buf: sizet*) -> int:
"""把失效槽表复位到快照(分支出口 / 单侧到达合流时用);0=完成 / 1=无快照"""
global _g_freed_count
if buf is None: return 1
n: sizet = buf[0]
if n > 0:
if _g_freed_slots is None:
_g_freed_slots = sizet*(stdlib.malloc(_FREED_MAX * 8))
if _g_freed_slots is None: return 1
i: sizet = 0
while i < n:
_g_freed_slots[i] = buf[1 + i]
i += 1
_g_freed_count = int(n)
return 0
def freed_join(self, buf: sizet*) -> int:
"""合流:当前失效槽表与快照**求交**(只有两路都失效才算失效);0=完成 / 1=无快照"""
global _g_freed_count
if buf is None: return 1
n: sizet = buf[0]
out: int = 0
i: sizet = 0
while i < sizet(_g_freed_count):
if _g_freed_slots is None: break
s: sizet = _g_freed_slots[i]
j: sizet = 0
keep: int = 0
while j < n:
if buf[1 + j] == s: keep = 1
if keep != 0: break
j += 1
if keep != 0:
_g_freed_slots[out] = s
out += 1
i += 1
_g_freed_count = out
return 0
# _spec_of_var_alloca - build a TypeSpec from the alloca's value type (S3 dual write).
# alloca.Ty is Ptr(var_ty), so the pointee is the value type; None means failure or an unsupported type.
def _spec_of_var_alloca(self, alloca: mlirlite.Value*) -> TSpec.TypeSpec*: