feat(gen): P2 生命期标注 @life(willFree(...)) 驱动释放后使用与二次释放检查

标注面:@life(willFree(<形参名|0起序号>)),兼容叠加式 @willFree(...),可混用累加;序号按形参表下标、self/cls 计入。GenApi.compute_willfree_mask 解析标注并对非法标注 fail-fast。本文件表 StructGenMixin.WfTable + 跨模块 .vpi 行尾 wf=<mask>(Converter 写 / VpiReader 读 / CallGenBase 查)。调用点在实参全部翻译之后把对应实参变量槽置 VarEntry.Freed;其后的读取路径报 LifetimeError。重新赋值清除失效态。
This commit is contained in:
TermiNexus committed 2026-10-08 21:28:49 +08:00
1 parent 9ff5a3a03e
commit 97313e9e55
10 files changed
+379 -8

No files matched your search

+16 -6
View File
@@ -517,15 +517,25 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*,
# v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader
# 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。
# 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。
# P2 willFree:同行走 `# ... wf=<mask>`(第 k 位=第 k 个形参在本调用后失效),
# 纯声明式、不依赖逃逸扫描的 scratch,所以单独算、独立决定是否落盘。
wf_mask_c: int = GenApi.compute_willfree_mask(fn)
esc_v_c: int = 0
ret_v_c: int = 0
if _PyiEnsureEscScratch() != 0:
esc_box_c: sizet* = sizet*(uint64(_g_pyi_esc_scratch) + 520)
if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0:
if esc_box_c[0] != 0 or esc_box_c[1] != 0:
tmp_c: bytes = stdlib.malloc(64)
if tmp_c is not None:
viperlib.snprintf(tmp_c, 64, " # esc=%d ret=%d", int(esc_box_c[0]), int(esc_box_c[1]))
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
stdlib.free(tmp_c)
esc_v_c = int(esc_box_c[0])
ret_v_c = int(esc_box_c[1])
if esc_v_c != 0 or ret_v_c != 0 or wf_mask_c != 0:
tmp_c: bytes = stdlib.malloc(96)
if tmp_c is not None:
if esc_v_c != 0 or ret_v_c != 0:
if wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d wf=%d", esc_v_c, ret_v_c, wf_mask_c)
else: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d", esc_v_c, ret_v_c)
else: viperlib.snprintf(tmp_c, 96, " # wf=%d", wf_mask_c)
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
stdlib.free(tmp_c)
pos = _PyiEmit(buf, buf_size, pos, "\n")
return pos
+126
View File
@@ -116,6 +116,15 @@ define VPI_IDX_FNV_MASK = 4294967295
define VPI_ESC_MAX = 8192
define VPI_ESC_KEY_LEN = 224
# P2 跨模块 willFree 标注缓存(.vpi 行尾 `# ... wf=<mask>`,由 StubGen.Converter._PyiFunction 写出)。
# 键格式与逃逸摘要完全一致("{sha1}.{Class}.{method}" / "{sha1}.{func}");值 = 形参失效掩码
# (第 k 位 = 第 k 个形参在本调用后失效)。无标注的 def 不写条目,"查不到" 即 "无标注"。
define VPI_WF_MAX = 8192
define VPI_WF_KEY_LEN = 224
_g_vpi_wf_keys: bytes = None
_g_vpi_wf_vals: sizet* = None
_g_vpi_wf_count: int = 0
_g_vpi_esc_keys: bytes = None
_g_vpi_esc_vals: sizet* = None
_g_vpi_esc_count: int = 0
@@ -882,6 +891,7 @@ def _cache_vpi_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
_cache_vpi_escape_annots_text(vpi_buf, src_sha1)
_cache_vpi_willfree_annots_text(vpi_buf, src_sha1)
return 1
@@ -1000,6 +1010,122 @@ def _lookup_vpi_escape_packed(src_sha1: str, cls_name: str, method_name: str) ->
return res
# _cache_vpi_willfree_annots_text - extract the trailing `wf=<mask>` marker (P2 willFree).
# 与逃逸摘要同构:不依赖 AST,按行扫描;只在有 wf 标记的 def 上建条目。
def _cache_vpi_willfree_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
"""抽取 vpi 行尾 P2 willFree 标记(`# ... wf=<mask>`)进缓存;1=处理完成 / 0=缓冲区或分配失败"""
global _g_vpi_wf_keys, _g_vpi_wf_vals, _g_vpi_wf_count
if vpi_buf is None or src_sha1 is None: return 0
if _g_vpi_wf_keys is None:
_g_vpi_wf_keys = stdlib.malloc(VPI_WF_MAX * VPI_WF_KEY_LEN)
_g_vpi_wf_vals = sizet*(stdlib.malloc(VPI_WF_MAX * 8))
if _g_vpi_wf_keys is None or _g_vpi_wf_vals is None: return 0
string.memset(_g_vpi_wf_keys, 0, VPI_WF_MAX * VPI_WF_KEY_LEN)
string.memset(_g_vpi_wf_vals, 0, VPI_WF_MAX * 8)
sha1_len: sizet = string.strlen(src_sha1)
buf_len: sizet = string.strlen(vpi_buf)
cur_cls: bytes = None
pos: sizet = 0
while pos < buf_len:
ls: sizet = pos
le: sizet = pos
while le < buf_len and vpi_buf[le] != '\n': le += 1
i: sizet = ls
while i < le and vpi_buf[i] == ' ': i += 1
if i < le:
# class X: -> 记录当前类上下文(与方法键一致)
if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0:
cstart: sizet = i + 6
cend: sizet = cstart
while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1
if cend > cstart:
if cur_cls is not None: stdlib.free(cur_cls)
cur_cls = stdlib.malloc(cend - cstart + 1)
if cur_cls is not None:
string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart)
cur_cls[cend - cstart] = '\0'
# def name(...) ... # [esc=<mask> ret=<ret>] wf=<mask>
elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0:
# 模块级函数(无缩进)清掉类上下文,避免上一类泄漏
if i == ls and cur_cls is not None:
stdlib.free(cur_cls)
cur_cls = None
# 只在注释段里找 "wf=":避开参数名(如 def f(wf: int))
mark: sizet = le
hm: sizet = le
j: sizet = i
while j < le:
if vpi_buf[j] == '#':
hm = j
break
j += 1
if hm < le:
j2: sizet = hm + 1
while j2 + 3 <= le:
if vpi_buf[j2] == 'w' and vpi_buf[j2 + 1] == 'f' and vpi_buf[j2 + 2] == '=':
mark = j2
break
j2 += 1
if mark < le:
kp: sizet = mark + 3
wf_v: int = 0
while kp < le and vpi_buf[kp] >= '0' and vpi_buf[kp] <= '9':
wf_v = wf_v * 10 + (int(vpi_buf[kp]) - 48)
kp += 1
if wf_v != 0 and _g_vpi_wf_count < VPI_WF_MAX:
dstart: sizet = i + 4
dend: sizet = dstart
while dend < le and vpi_buf[dend] != '(': dend += 1
if dend > dstart:
mname_len: sizet = dend - dstart
cls_len_w: sizet = 0
if cur_cls is not None: cls_len_w = string.strlen(cur_cls)
key_len_w: sizet = sha1_len + 1 + cls_len_w + 1 + mname_len
if key_len_w < VPI_WF_KEY_LEN:
mname_buf_w: bytes = stdlib.malloc(mname_len + 1)
key_buf_w: bytes = stdlib.malloc(key_len_w + 1)
if mname_buf_w is not None and key_buf_w is not None:
string.strncpy(mname_buf_w, vpi_buf + dstart, mname_len)
mname_buf_w[mname_len] = '\0'
if cur_cls is not None: viperlib.snprintf(key_buf_w, key_len_w + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf_w)
else: viperlib.snprintf(key_buf_w, key_len_w + 1, "%s.%s", src_sha1, mname_buf_w)
string.strcpy(_g_vpi_wf_keys + sizet(_g_vpi_wf_count) * VPI_WF_KEY_LEN, key_buf_w)
_g_vpi_wf_vals[_g_vpi_wf_count] = sizet(wf_v)
_g_vpi_wf_count += 1
if mname_buf_w is not None: stdlib.free(mname_buf_w)
if key_buf_w is not None: stdlib.free(key_buf_w)
pos = le + 1
if cur_cls is not None: stdlib.free(cur_cls)
return 1
# _lookup_vpi_willfree - query the cross-module P2 willFree cache (key shape = escape summary).
def _lookup_vpi_willfree(src_sha1: str, cls_name: str, method_name: str) -> int:
"""查跨模块 willFree 形参掩码;0 = 无标注"""
if _g_vpi_wf_keys is None or _g_vpi_wf_count == 0: return 0
if src_sha1 is None or method_name is None: return 0
sha1_len: sizet = string.strlen(src_sha1)
cl_len: sizet = 0
if cls_name is not None: cl_len = string.strlen(cls_name)
me_len: sizet = string.strlen(method_name)
key_len: sizet = sha1_len + 1 + cl_len + 1 + me_len
kb: bytes = stdlib.malloc(key_len + 1)
if kb is None: return 0
if cls_name is not None: viperlib.snprintf(kb, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name)
else: viperlib.snprintf(kb, key_len + 1, "%s.%s", src_sha1, method_name)
res: int = 0
idx: int = 0
while idx < _g_vpi_wf_count:
ent: bytes = _g_vpi_wf_keys + sizet(idx) * VPI_WF_KEY_LEN
# 键以模块 sha1 打头:先比前 2 个十六进制字符,避免逐项 strcmp
if ent[0] == kb[0] and ent[1] == kb[1] and string.strcmp(ent, kb) == 0:
res = int(_g_vpi_wf_vals[idx])
break
idx += 1
stdlib.free(kb)
return res
# _vpi_param_annot_seg_is - equality check for a param-annotation segment (for the "str/bytes is not a generic pointer" rule).
def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str, seg_idx: int, want: str) -> int:
"""Check whether the definition-side param-annotation segment equals want exactly; 1=yes / 0=no."""