From 5fa5a9f37ce6e9e5e1665bbfd65a9fbdcf9a4d24 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 9 Oct 2026 14:55:52 +0800 Subject: [PATCH] =?UTF-8?q?test(lifetime):=20=E8=A1=A5=20P3=20=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E5=BD=A2=E6=80=81=20+=20P2=20=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=BA=A7/=E5=8F=96=E5=9C=B0=E5=9D=80=E7=9A=84=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=B7=A5=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 NewLifetimeAnnFieldEscapeTest:h.p: int* = &local → LifetimeError(P3 注解形态) - 新增 NewLifetimeWillFreeMethodTest:r.Release(p) 后再用 p → LifetimeError(方法级标注,形参位含 self 偏移) - 新增 NewLifetimeWillFreeAddrTest:stdlib.free(p) 后 &p → LifetimeError(取地址路径) - NewLifetimeTest.CaseAnnFieldStore:p.x: int = 42 必须真的写进字段(正向) - 注册进 SequentialRegressionTest / regression_test / run_lifetime_negative_tests + README --- NewLifetimeAnnFieldEscapeTest/App/main.vp | 30 ++++++++++++++++++++ NewLifetimeAnnFieldEscapeTest/project.json | 29 ++++++++++++++++++++ NewLifetimeTest/App/main.vp | 12 ++++++++ NewLifetimeWillFreeAddrTest/App/main.vp | 26 ++++++++++++++++++ NewLifetimeWillFreeAddrTest/project.json | 29 ++++++++++++++++++++ NewLifetimeWillFreeMethodTest/App/main.vp | 32 ++++++++++++++++++++++ NewLifetimeWillFreeMethodTest/project.json | 29 ++++++++++++++++++++ README.md | 5 +++- SequentialRegressionTest.py | 8 +++++- regression_test.py | 4 +-- run_lifetime_negative_tests.py | 2 +- 11 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 NewLifetimeAnnFieldEscapeTest/App/main.vp create mode 100644 NewLifetimeAnnFieldEscapeTest/project.json create mode 100644 NewLifetimeWillFreeAddrTest/App/main.vp create mode 100644 NewLifetimeWillFreeAddrTest/project.json create mode 100644 NewLifetimeWillFreeMethodTest/App/main.vp create mode 100644 NewLifetimeWillFreeMethodTest/project.json diff --git a/NewLifetimeAnnFieldEscapeTest/App/main.vp b/NewLifetimeAnnFieldEscapeTest/App/main.vp new file mode 100644 index 0000000..e909080 --- /dev/null +++ b/NewLifetimeAnnFieldEscapeTest/App/main.vp @@ -0,0 +1,30 @@ +from stdint import * + + +# ============================================================================= +# 负向工程(**应当编译失败**):P3 —— 注解赋值的**属性目标**同样受"长命左值"约束 +# +# §12-P3 的实例:`obj.f: T = <短命右值>`。与普通赋值 `obj.f = <短命右值>` 是**同一条规则**, +# 只是语法形态是 AnnAssign(带类型注解)。此前 translate_annassign 对非 Name 目标直接 return 0, +# 整条语句被**静默丢弃**(既不报错也不写出)——本工程同时守住"不静默"与"P3 必须拦截"两点。 +# +# 判据(见 AssignGenMixin._translate_lvalue_annassign): +# · 左值 h.p 的存储属于**堆对象**(h 由 `new heap hlife` 得到)→ 活得比本帧长; +# · 右值 &local 是**本帧栈地址**(LIFE_FRAME)→ 出帧即成悬垂指针。 +# 期望报错:LifetimeError。 +# +# 对照(必须放行):`local_struct.f: int* = &local2`(左值也在本帧,FRAME)→ 两者同命,合法; +# `h.p: int* = heap_ptr`(右值生命期不短于左值)→ 合法。 +# 注意 h 必须在**同一个函数**里 `new`,否则它作为形参进来时 Life=UNKNOWN(保守放行)。 +# ============================================================================= + + +novirtual class Holder: + p: int* + + +export def main() -> int: + h: Holder* = new heap hlife Holder() + local: int = 7 + h.p: int* = &local # ← 必须被拒:注解形态的属性赋值把本帧栈地址写进堆对象字段 + return 0 diff --git a/NewLifetimeAnnFieldEscapeTest/project.json b/NewLifetimeAnnFieldEscapeTest/project.json new file mode 100644 index 0000000..73f80d0 --- /dev/null +++ b/NewLifetimeAnnFieldEscapeTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeAnnFieldEscapeTest", + "version": "1.0.0", + "source_dir": "./App", + "build_dir": "./.tpv_build", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-relocation-model=pic"] + }, + "linker": { + "cmd": "clang++", + "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32"], + "output": "NewLifetimeAnnFieldEscapeTest.exe" + }, + "includes": [ + "../../includes" + ], + "target": { + "triple": "x86_64-pc-windows-gnu", + "datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "sha1_slice_level": 1, + "target": "llvm", + "strict_mode": true + } +} diff --git a/NewLifetimeTest/App/main.vp b/NewLifetimeTest/App/main.vp index 25ed16a..8cf28fb 100644 --- a/NewLifetimeTest/App/main.vp +++ b/NewLifetimeTest/App/main.vp @@ -106,6 +106,17 @@ def CaseStack() -> int: return 0 +# (11) P3 注解形态的属性目标:`p.x: int = 42` 必须**真的写出**。 +# 此前 translate_annassign 对非 Name 目标直接 return 0,整条语句被静默丢弃 +# (既不报错也不写),这里守"写出"这一半;"长命左值收短命右值"由 +# NewLifetimeAnnFieldEscapeTest 守另一半。 +def CaseAnnFieldStore() -> int: + p: Point* = new heap slife Point(1, 2) + p.x: int = 42 + if p.x != 42: return 1 + return 0 + + # (9) §3.6 析构调度:slife 释放时**先**调 `__del__`、**再** free(对齐 TPC MemoryOps)。 # 用一个计数全局量验证析构确实被调度,且每个释放点恰好一次。 dtor_hits: int = 0 @@ -227,6 +238,7 @@ export def main() -> int: fails = Check("CaseBranchOnly(0)", CaseBranchOnly(0), fails) fails = Check("CaseBranchOnly(1)", CaseBranchOnly(1), fails) fails = Check("CaseStack", CaseStack(), fails) + fails = Check("CaseAnnFieldStore", CaseAnnFieldStore(), fails) CaseVoidBare(1) CaseVoidBare(0) diff --git a/NewLifetimeWillFreeAddrTest/App/main.vp b/NewLifetimeWillFreeAddrTest/App/main.vp new file mode 100644 index 0000000..c16ebbc --- /dev/null +++ b/NewLifetimeWillFreeAddrTest/App/main.vp @@ -0,0 +1,26 @@ +from stdint import * +import stdlib + + +# ============================================================================= +# 负向工程(**应当编译失败**) +# +# P2 willFree —— **取地址**路径。变量被 @life(willFree(...)) 标注的调用消费后只是"值失效", +# 但 `&p` 同样是把指向已释放对象的指针交出去(后续解引用即 use-after-free), +# 必须与**读取**同一口径拒绝。 +# 检查点:ExprGenOps 的 AddrOf 分支(`_FreedCheck`),与 ExprGenName 的读取路径同源。 +# +# 本工程:`free(p)` 之后取 `&p`。 +# 期望报错:LifetimeError。 +# ============================================================================= + + +def Use(x: rawptr*) -> int: + return 0 + + +export def main() -> int: + p: rawptr = stdlib.malloc(8) + stdlib.free(p) + Use(&p) # ← 必须被拒:对已失效变量取地址 + return 0 diff --git a/NewLifetimeWillFreeAddrTest/project.json b/NewLifetimeWillFreeAddrTest/project.json new file mode 100644 index 0000000..81c5601 --- /dev/null +++ b/NewLifetimeWillFreeAddrTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeWillFreeAddrTest", + "version": "1.0.0", + "source_dir": "./App", + "build_dir": "./.tpv_build", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-relocation-model=pic"] + }, + "linker": { + "cmd": "clang++", + "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32"], + "output": "NewLifetimeWillFreeAddrTest.exe" + }, + "includes": [ + "../../includes" + ], + "target": { + "triple": "x86_64-pc-windows-gnu", + "datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "sha1_slice_level": 1, + "target": "llvm", + "strict_mode": true + } +} diff --git a/NewLifetimeWillFreeMethodTest/App/main.vp b/NewLifetimeWillFreeMethodTest/App/main.vp new file mode 100644 index 0000000..1d02061 --- /dev/null +++ b/NewLifetimeWillFreeMethodTest/App/main.vp @@ -0,0 +1,32 @@ +from stdint import * +import stdlib + + +# ============================================================================= +# 负向工程(**应当编译失败**) +# +# P2 willFree —— **方法级**标注路径。`includes/stdlib.vp` 的 `free` 上那处 `@life(willFree(p))` +# 走的是**模块级函数调用**;本工程覆盖另一条独立链路: +# · 注册:StructGenMixin._RegisterClassMethods → _RegisterFuncWillFree(键 "Class.method"); +# · 查询:CallGenCall._translate_method_call 的 wf_mask_mc = get_param_willfree_method(...); +# · 形参位映射:实参链含 self(bit 0),故用户第 ai 个实参对应形参位 (ai+1)。 +# +# 本工程:`r.Release(p)` 之后 p 即失效,再 `free(p)` 必须被拒(二次释放)。 +# 期望报错:LifetimeError。 +# ============================================================================= + + +novirtual class Resource: + id: int + + @life(willFree(p)) + def Release(self, p: rawptr) -> None: + stdlib.free(p) + + +export def main() -> int: + r: Resource* = new heap hlife Resource(1) + p: rawptr = stdlib.malloc(8) + r.Release(p) # ← 本调用后 p 失效(被调者带 @life(willFree(p))) + stdlib.free(p) # ← 必须被拒:p 已失效(二次释放) + return 0 diff --git a/NewLifetimeWillFreeMethodTest/project.json b/NewLifetimeWillFreeMethodTest/project.json new file mode 100644 index 0000000..42c7e48 --- /dev/null +++ b/NewLifetimeWillFreeMethodTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeWillFreeMethodTest", + "version": "1.0.0", + "source_dir": "./App", + "build_dir": "./.tpv_build", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-relocation-model=pic"] + }, + "linker": { + "cmd": "clang++", + "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32"], + "output": "NewLifetimeWillFreeMethodTest.exe" + }, + "includes": [ + "../../includes" + ], + "target": { + "triple": "x86_64-pc-windows-gnu", + "datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "sha1_slice_level": 1, + "target": "llvm", + "strict_mode": true + } +} diff --git a/README.md b/README.md index 9dc975b..641b8a3 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,10 @@ | NewLifetimeUseAfterFreeTest | [Test/NewLifetimeUseAfterFreeTest/](NewLifetimeUseAfterFreeTest/App/main.vp) | **编译失败**:P2 —— `free(p)` 之后读/传 `p` → `LifetimeError`(使用已释放变量) | | NewLifetimeWillFreeOkTest | [Test/NewLifetimeWillFreeOkTest/](NewLifetimeWillFreeOkTest/App/main.vp) | **编译成功 + 退出码 0**:P2 合法用法 —— 首次 `free` 本身合法;同名变量**重新赋值**会清除失效态,其后可再用、再 `free`(防误报) | | NewLifetimeWillFreeKwargTest | [Test/NewLifetimeWillFreeKwargTest/](NewLifetimeWillFreeKwargTest/App/main.vp) | **编译失败**:P2 **关键字实参**路径 —— `stdlib.free(p = p)` 之后再用 `p` → `LifetimeError` | +| NewLifetimeWillFreeMethodTest | [Test/NewLifetimeWillFreeMethodTest/](NewLifetimeWillFreeMethodTest/App/main.vp) | **编译失败**:P2 **方法级标注**路径 —— `r.Release(p)`(带 `@life(willFree(p))`)之后再用 `p` → `LifetimeError`(形参位含 `self` 偏移) | +| NewLifetimeWillFreeAddrTest | [Test/NewLifetimeWillFreeAddrTest/](NewLifetimeWillFreeAddrTest/App/main.vp) | **编译失败**:P2 **取地址**路径 —— `stdlib.free(p)` 之后取 `&p` → `LifetimeError`(与读取同一口径) | | NewLifetimeFieldEscapeTest | [Test/NewLifetimeFieldEscapeTest/](NewLifetimeFieldEscapeTest/App/main.vp) | **编译失败**:P3 —— `h.p = &local`(堆对象字段收下本帧栈地址)→ `LifetimeError` | +| NewLifetimeAnnFieldEscapeTest | [Test/NewLifetimeAnnFieldEscapeTest/](NewLifetimeAnnFieldEscapeTest/App/main.vp) | **编译失败**:P3 **注解形态** —— `h.p: int* = &local`(AnnAssign 属性目标,同一条规则;也守住"非 Name 目标不得被静默丢弃")→ `LifetimeError` | 单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持): @@ -163,7 +166,7 @@ python Test/run_lifetime_negative_tests.py --exe .\TransPyV\output\TransPyV.exe python Test/run_lifetime_negative_tests.py --exe .\TransPyV\output\TransPyV.exe --only EscapeMethodTest ``` -> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest` / `NewLifetimeFreeTest` / `NewLifetimeAsyncTest` / `NewLifetimeWillFreeTest` / `NewLifetimeUseAfterFreeTest` / `NewLifetimeWillFreeKwargTest` / `NewLifetimeFieldEscapeTest`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。 +> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest` / `NewLifetimeFreeTest` / `NewLifetimeAsyncTest` / `NewLifetimeWillFreeTest` / `NewLifetimeUseAfterFreeTest` / `NewLifetimeWillFreeKwargTest` / `NewLifetimeWillFreeMethodTest` / `NewLifetimeWillFreeAddrTest` / `NewLifetimeFieldEscapeTest` / `NewLifetimeAnnFieldEscapeTest`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。 负向项目在 `SequentialRegressionTest.py` 里是**反向判定**(`NEGATIVE_PROJECTS`):编译失败且输出含 `LifetimeError` 才算通过,且**不再进入运行阶段**;编译成功 = 漏检,直接终止。`regression_test.py --escape` 会**剔除**这些项目(该脚本要求"编译成功 + 能跑"),负向断言请用 `SequentialRegressionTest.py --escape` 或上面的 `run_lifetime_negative_tests.py`。 diff --git a/SequentialRegressionTest.py b/SequentialRegressionTest.py index 3775241..6ddb20a 100644 --- a/SequentialRegressionTest.py +++ b/SequentialRegressionTest.py @@ -41,7 +41,7 @@ OVERVIEW = os.path.join(TEST_DIR, "README.md") # NewLifetimeFreeTest / NewLifetimeAsyncTest / NewLifetimeWillFreeTest / # NewLifetimeUseAfterFreeTest(负向); # 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest -ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"] # 负向项目:**期望编译失败**且输出含指定关键字(反向判定,且不运行可执行文件)。 # 这些项目里的用例故意交出本帧栈地址(或跨模块摘要路径),必须被 M1–M2.3 的 @@ -61,8 +61,14 @@ NEGATIVE_PROJECTS = { "NewLifetimeUseAfterFreeTest": "LifetimeError", # P2:关键字实参路径(stdlib.free(p = p) 之后再用 p) "NewLifetimeWillFreeKwargTest": "LifetimeError", + # P2:方法级标注(r.Release(p) 之后再用 p;形参位含 self 偏移) + "NewLifetimeWillFreeMethodTest": "LifetimeError", + # P2:取地址路径(stdlib.free(p) 之后 &p) + "NewLifetimeWillFreeAddrTest": "LifetimeError", # P3:obj.f = &local(堆对象字段收下本帧栈地址) "NewLifetimeFieldEscapeTest": "LifetimeError", + # P3:obj.f: T = &local(注解形态的属性目标,同一条规则;防"非 Name 目标被静默丢弃") + "NewLifetimeAnnFieldEscapeTest": "LifetimeError", } # IR 断言:项目名 → 生成 IR 中**必须出现**的子串。 diff --git a/regression_test.py b/regression_test.py index 97abf57..38a43b7 100644 --- a/regression_test.py +++ b/regression_test.py @@ -90,12 +90,12 @@ def find_test_projects(): # NewLifetimeFreeTest / NewLifetimeAsyncTest / NewLifetimeWillFreeTest / # NewLifetimeUseAfterFreeTest(负向); # 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest -ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"] # 负向项目(**期望编译失败**,见 SequentialRegressionTest.NEGATIVE_PROJECTS): # 本脚本按"编译必须成功 + 能跑"判定,纳入会误报,故 --escape 时剔除; # 负向断言请用 SequentialRegressionTest(反向判定)或 run_lifetime_negative_tests.py。 -NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"} +NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"} def find_project_file(proj_dir): diff --git a/run_lifetime_negative_tests.py b/run_lifetime_negative_tests.py index f283c63..4043615 100644 --- a/run_lifetime_negative_tests.py +++ b/run_lifetime_negative_tests.py @@ -34,7 +34,7 @@ PROJECT_ROOT = os.path.dirname(TEST_DIR) # NewLifetimeAsyncTest:M3 §4.2(协程体里的 `new heap slife` → 释放点无法确定,fail-fast)。 # NewLifetimeWillFreeTest / NewLifetimeUseAfterFreeTest:P2(@life(willFree(p)) 标注驱动—— # 二次释放 / 释放后使用,对应正向工程 NewLifetimeWillFreeOkTest)。 -NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"] +NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"] EXPECT_KEYWORD = "LifetimeError" COMPILE_TIMEOUT = 300