diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 3acb895..3b45fdc 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -864,14 +864,27 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: f.close() if bytes_read > 0: if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0 - _cache_vpi_return_annots_text(vpi_buf, src_sha1) - # v3:同一次加载里把行尾逃逸摘要标记也收进缓存(两者共享"本模块是否已加载"的判定) - _cache_vpi_escape_annots_text(vpi_buf, src_sha1) + # 单一入口:一次喂齐 ret / param / 逃逸摘要(见 _cache_vpi_annots_text 注释) + _cache_vpi_annots_text(vpi_buf, src_sha1) stdlib.free(vpi_buf) stdlib.free(vpi_path) return 1 +# _cache_vpi_annots_text - 所有"从 vpi 文本解析"的缓存**单一入口**。 +# 任何新增的文本缓存都加在这里,不要再散落到各加载点——漏喂一处就会**静默退化** +# (现场:逃逸摘要只在 _ensure_vpi_ret_annot_loaded 里喂,而 stub 注入加载点没喂, +# 前者开头又有"本模块 ret 注解已缓存即 return 1"的短路,于是跨模块逃逸查询永久查空, +# EscapeXmodTest 漏检)。 +def _cache_vpi_annots_text(vpi_buf: bytes, src_sha1: str) -> int: + """一次性把 vpi 文本派生出的所有缓存喂齐;1=完成 / 0=入参非法""" + if vpi_buf is None or src_sha1 is None: return 0 + _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) + return 1 + + # _cache_vpi_escape_annots_text - extract the trailing `# esc= ret=` markers. # v3 跨模块逃逸摘要:不依赖 AST,按行扫描(与 _cache_vpi_return_annots_text 同构)。 def _cache_vpi_escape_annots_text(vpi_buf: bytes, src_sha1: str) -> int: @@ -1799,15 +1812,9 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v # Cache method return annotations from vpi_buf text into stdlib (no AST tree: deep nodes # may be corrupted later, but the raw vpi_buf text stays valid until the function ends). - _cache_vpi_return_annots_text(vpi_buf, src_sha1) - # Likewise cache param annotations (tree param lists may be truncated; - # _inject_*_declare uses this cache to complete declare param counts). - _cache_vpi_param_annots_text(vpi_buf, src_sha1) - # v3 逃逸摘要必须**在所有 vpi 加载点**都喂一份:_ensure_vpi_ret_annot_loaded 开头有 - # "本模块 ret 注解已缓存 → 直接 return 1" 的短路,若某模块先经本函数(stub 注入) - # 加载、而这里没喂逃逸缓存,后续跨模块逃逸查询就会永久命中不到(现场:EscapeXmodTest - # 漏检——box.vpi 里 `# esc=2` 明明写对了,查询侧却查空)。 - _cache_vpi_escape_annots_text(vpi_buf, src_sha1) + # 单一入口:ret / param / 逃逸摘要一次喂齐(见 _cache_vpi_annots_text 注释)。 + # 本函数是 stub 注入路径的加载点,历史上这里漏喂逃逸摘要 → 跨模块漏检。 + _cache_vpi_annots_text(vpi_buf, src_sha1) # 3. walk top-level AST nodes ch: list[ast.AST*]* = tree.children diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 9e18b03..28a7b40 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -637,6 +637,16 @@ novirtual class AssignGenMixin: fnm: ast.Name* = ast.Name*(cl.func) if fnm.id is not None and string.strcmp(fnm.id, "malloc") == 0: return GenTypes.LIFE_HEAP return GenTypes.LIFE_UNKNOWN + if k == ast.ASTKind.New: + # M2.4 `new`:生命期由描述符决定(§2.3 组合矩阵) + # stack → slife:帧生命期(等价既有 `&T(...)`) + # heap/memgr slife → 同样按帧生命期:作用域结束即释放(RAII),逃逸出去必然悬垂 + # hlife(含 heap/memgr 的缺省)→ 堆/池生命期,可自由传递 + nn: ast.New* = ast.New*(node) + if nn is None: return GenTypes.LIFE_UNKNOWN + if nn.Life == ast.NEW_LIFE_SLIFE: return GenTypes.LIFE_FRAME + if nn.Life == ast.NEW_LIFE_DEFAULT and nn.Source == ast.NEW_SRC_STACK: return GenTypes.LIFE_FRAME + return GenTypes.LIFE_HEAP return GenTypes.LIFE_UNKNOWN # propagate_life - 把 RHS 的生命期写到赋值目标(仅 Name 目标;全局目标走汇点检查)。