feat(diag): 逃逸类 LifetimeError 追加 File lifetime leak 帧,标出泄漏点

StructGenMixin: 新增 escape_leak_line——错误路径重扫被调者函数体,取指定形参的**首个逃逸点**行号;
_NoteLeak 在三处置位来源采集(字段/全局写、return 形参、传递闭包调用),新增 EscLeakWant/EscLeakLine。

Diag: DiagEntry 增加 LeakFileName/LeakLine + set_leak + emit_leak_frame(标签 File lifetime leak),
打印顺序 = 调用链 → 错误点 → 泄漏点 → 类别: 消息。

TypeGenErr.fatal_error / GenApi.fail_if_stack_addr_escape 增加 leak_file/leak_line 透传;
CallGenMixin 调用点按失败实参位取行号。**仅同模块**:跨模块 EscFuncTable 无 AST → 返回 0,不打该帧。
This commit is contained in:
TermiNexus committed 2026-10-09 19:32:54 +08:00
1 parent c98730f717
commit 2353dca8ec
6 files changed
+112 -13

No files matched your search

+32
View File
@@ -54,6 +54,10 @@ class DiagEntry:
FrameCount: int # 调用链帧数
FrameFiles: sizet* # 调用链每帧文件名(深拷贝)
FrameLines: sizet* # 调用链每帧行号(与 FrameFiles 同容量)
# 泄漏点(可选):被调者那边**造成逃逸的写入点**。只有逃逸类 LifetimeError 会有值;
# 打印成独立一帧 `File lifetime leak "<file>", line N`,与调用栈帧(`File "..."`)区分。
LeakFileName: str
LeakLine: int
Cap: int # 内部:FrameFiles/FrameLines 容量(不参与打印)
@@ -81,6 +85,8 @@ def new_entry(max_frames: int) -> DiagEntry*:
ent.Msg = None
ent.Hint = None
ent.FrameCount = 0
ent.LeakFileName = None
ent.LeakLine = 0
ent.Cap = max_frames
ent.FrameFiles = (sizet*)(stdlib.malloc(sizet(max_frames) * 8))
ent.FrameLines = (sizet*)(stdlib.malloc(sizet(max_frames) * 8))
@@ -98,6 +104,8 @@ def begin(ent: DiagEntry*, category: str, msg: str, hint: str = None) -> int:
ent.ColStart = 0
ent.ColEnd = 0
ent.FrameCount = 0
ent.LeakFileName = None
ent.LeakLine = 0
return 0
@@ -111,6 +119,14 @@ def set_current(ent: DiagEntry*, file_name: str, lineno: int, col_start: int, co
return 0
# set_leak - 设置"泄漏点"帧(File lifetime leak);仅逃逸类 LifetimeError 使用
def set_leak(ent: DiagEntry*, file_name: str, lineno: int) -> int:
if ent is None or lineno <= 0: return 0
ent.LeakFileName = dup_str(file_name)
ent.LeakLine = lineno
return 0
# add_frame - 追加调用链一帧(按"自外向内"顺序追加)
def add_frame(ent: DiagEntry*, file_name: str, lineno: int) -> int:
if ent is None or ent.FrameFiles is None or ent.FrameLines is None: return 0
@@ -137,6 +153,8 @@ def emit(ent: DiagEntry*, direction: int) -> int:
emit_frame(str(ent.FrameFiles[i_u]), int(ent.FrameLines[i_u]))
i_u += 1
emit_current(ent.FileName, ent.Line, ent.ColStart, ent.ColEnd)
# 泄漏点帧紧跟在"错误点"之后(往下再一行),再印类别: 消息
if ent.LeakLine > 0: emit_leak_frame(ent.LeakFileName, ent.LeakLine)
emit_category(ent.Category, ent.Msg)
return 0
@@ -145,6 +163,7 @@ def emit(ent: DiagEntry*, direction: int) -> int:
def free_entry(ent: DiagEntry*) -> int:
if ent is None: return 0
if ent.FileName is not None: stdlib.free(ent.FileName)
if ent.LeakFileName is not None: stdlib.free(ent.LeakFileName)
if ent.Category is not None: stdlib.free(ent.Category)
if ent.Msg is not None: stdlib.free(ent.Msg)
if ent.Hint is not None: stdlib.free(ent.Hint)
@@ -222,6 +241,19 @@ def emit_frame(file_name: str, lineno: int) -> int:
return 0
# emit_leak_frame - "泄漏点"帧:标签用 `File lifetime leak`,与调用栈帧(`File "..."`)区分
def emit_leak_frame(file_name: str, lineno: int) -> int:
if file_name is None: return 0
if lineno > 0:
stdio.printf(' File lifetime leak "%s", line %d\n', file_name, lineno)
src: str = read_source_line(file_name, lineno)
if src is not None:
stdio.printf(" %s\n", src)
stdlib.free(src)
else: stdio.printf(' File lifetime leak "%s"\n', file_name)
return 0
# emit_current - 当前帧(带 caret 列范围;行读不出时印警告)
def emit_current(file_name: str, lineno: int, col_start: int, col_end: int) -> int:
# Sanity defense: read the line first to verify lineno belongs to the current file.