From aed57de44cd99ce07eb60389d30d7b3a39910e3c Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 8 Oct 2026 21:28:55 +0800 Subject: [PATCH] =?UTF-8?q?test(lifetime):=20=E6=96=B0=E5=A2=9E=20willFree?= =?UTF-8?q?=20=E6=AD=A3/=E8=B4=9F=E5=90=91=E5=B7=A5=E7=A8=8B=E5=B9=B6?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E5=88=B0=E5=9B=9E=E5=BD=92=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NewLifetimeWillFreeTest(二次 free)、NewLifetimeUseAfterFreeTest(释放后使用)为负向;NewLifetimeWillFreeOkTest 验证合法用法与重赋值清除失效态。同步更新 SequentialRegressionTest / regression_test / run_lifetime_negative_tests 与 README。 --- NewLifetimeUseAfterFreeTest/App/main.vp | 24 ++++++++++++++++++ NewLifetimeUseAfterFreeTest/project.json | 29 ++++++++++++++++++++++ NewLifetimeWillFreeOkTest/App/main.vp | 31 ++++++++++++++++++++++++ NewLifetimeWillFreeOkTest/project.json | 29 ++++++++++++++++++++++ NewLifetimeWillFreeTest/App/main.vp | 21 ++++++++++++++++ NewLifetimeWillFreeTest/project.json | 29 ++++++++++++++++++++++ README.md | 5 +++- SequentialRegressionTest.py | 11 ++++++--- regression_test.py | 10 +++++--- run_lifetime_negative_tests.py | 4 ++- 10 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 NewLifetimeUseAfterFreeTest/App/main.vp create mode 100644 NewLifetimeUseAfterFreeTest/project.json create mode 100644 NewLifetimeWillFreeOkTest/App/main.vp create mode 100644 NewLifetimeWillFreeOkTest/project.json create mode 100644 NewLifetimeWillFreeTest/App/main.vp create mode 100644 NewLifetimeWillFreeTest/project.json diff --git a/NewLifetimeUseAfterFreeTest/App/main.vp b/NewLifetimeUseAfterFreeTest/App/main.vp new file mode 100644 index 0000000..db1adeb --- /dev/null +++ b/NewLifetimeUseAfterFreeTest/App/main.vp @@ -0,0 +1,24 @@ +from stdint import * +import stdlib + + +# ============================================================================= +# 负向工程(**应当编译失败**,对照工程:NewLifetimeWillFreeOkTest) +# +# P2 willFree 标注驱动:`@life(willFree(p))` 标注的调用返回后,实参变量即失效; +# 其后对它做任何**读取**(取值、参与表达式、作为实参传递)都必须在编译期被拒。 +# +# 本工程:Use(p) 在 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) # ← 必须被拒:p 已失效(使用已释放变量) + return 0 diff --git a/NewLifetimeUseAfterFreeTest/project.json b/NewLifetimeUseAfterFreeTest/project.json new file mode 100644 index 0000000..715b076 --- /dev/null +++ b/NewLifetimeUseAfterFreeTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeUseAfterFreeTest", + "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": "NewLifetimeUseAfterFreeTest.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/NewLifetimeWillFreeOkTest/App/main.vp b/NewLifetimeWillFreeOkTest/App/main.vp new file mode 100644 index 0000000..cd2222e --- /dev/null +++ b/NewLifetimeWillFreeOkTest/App/main.vp @@ -0,0 +1,31 @@ +from stdint import * +import stdlib + + +# ============================================================================= +# 正向工程:P2 willFree 的合法用法(编译必须成功、运行退出码 0) +# +# 对照负向工程 NewLifetimeWillFreeTest(二次 free)/ NewLifetimeUseAfterFreeTest(用已释放)。 +# 本工程验证两件事: +# (a) free(p) 之后**不再使用** p 是合法的(包括首次 free 本身); +# (b) 对同名变量**重新赋值**会清除"已失效"状态,其后可正常使用并再次 free。 +# (b) 是防止误报的关键:编译器必须把失效态当成"变量当前值"的属性,而不是永久的变量属性。 +# ============================================================================= + + +def Use(x: rawptr) -> int: + return 0 + + +export def main() -> int: + p: rawptr = stdlib.malloc(8) + Use(p) + stdlib.free(p) # 合法:p 在此之后失效 + p = stdlib.malloc(16) # 重新赋值 → 重新有效(清除失效态) + Use(p) # 合法 + stdlib.free(p) # 合法:再次失效 + + q: rawptr = stdlib.malloc(24) + Use(q) + stdlib.free(q) + return 0 diff --git a/NewLifetimeWillFreeOkTest/project.json b/NewLifetimeWillFreeOkTest/project.json new file mode 100644 index 0000000..4778487 --- /dev/null +++ b/NewLifetimeWillFreeOkTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeWillFreeOkTest", + "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": "NewLifetimeWillFreeOkTest.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/NewLifetimeWillFreeTest/App/main.vp b/NewLifetimeWillFreeTest/App/main.vp new file mode 100644 index 0000000..df8b6ff --- /dev/null +++ b/NewLifetimeWillFreeTest/App/main.vp @@ -0,0 +1,21 @@ +from stdint import * +import stdlib + + +# ============================================================================= +# 负向工程(**应当编译失败**,对照工程:NewLifetimeWillFreeOkTest) +# +# P2 willFree 标注驱动:`includes/stdlib.vp` 的 free 带 `@life(willFree(p))`, +# 语义 = "该实参所指对象在本调用返回后失效"。编译器读到被调者的该标注后,把对应 +# **实参变量槽**标为"已失效",其后再读/传/再 free → LifetimeError(fail-fast)。 +# +# 本工程:第二次 free(p) 必须在编译期被拒(p 在第一次 free 后已失效)。 +# 期望报错:LifetimeError。 +# ============================================================================= + + +export def main() -> int: + p: rawptr = stdlib.malloc(8) + stdlib.free(p) + stdlib.free(p) # ← 必须被拒:p 已失效(二次释放) + return 0 diff --git a/NewLifetimeWillFreeTest/project.json b/NewLifetimeWillFreeTest/project.json new file mode 100644 index 0000000..e1da40a --- /dev/null +++ b/NewLifetimeWillFreeTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeWillFreeTest", + "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": "NewLifetimeWillFreeTest.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 b2876d0..4ca7b3b 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ | NewLifetimeTest | [Test/NewLifetimeTest/](NewLifetimeTest/App/main.vp) | 编译通过、退出码 0(**M3 owned slife RAII**:出口释放 / 循环内覆盖释放 / 别名只释放一次 / 嵌套函数登记隔离 / early return / void 裸 return / 仅分支里 new 时 `free(NULL)` / stack slife 不释放;**§3.6 析构调度**:出口释放恰好 1 次 `__del__`、循环 3 轮共 3 次)。**另含 IR 断言**:IR 中必须出现 `llvm.call @free` | | NewLifetimeFreeTest | [Test/NewLifetimeFreeTest/](NewLifetimeFreeTest/App/main.vp) | **编译失败**:对 `slife` 对象手动 `stdlib.free(p)` → `LifetimeError`(§4.3 判据5,编译器独占释放;手动 `del p` 同样被拒) | | NewLifetimeAsyncTest | [Test/NewLifetimeAsyncTest/](NewLifetimeAsyncTest/App/main.vp) | **编译失败**:协程体(`async`)里的 `new heap slife` → `LifetimeError`(§4.2:状态机拆分 body,释放点无法确定;`new stack` / `new … hlife` 不受限) | +| NewLifetimeWillFreeTest | [Test/NewLifetimeWillFreeTest/](NewLifetimeWillFreeTest/App/main.vp) | **编译失败**:P2 `@life(willFree(p))` 标注驱动 —— `free(p)` 之后再 `free(p)` → `LifetimeError`(二次释放) | +| NewLifetimeUseAfterFreeTest | [Test/NewLifetimeUseAfterFreeTest/](NewLifetimeUseAfterFreeTest/App/main.vp) | **编译失败**:P2 —— `free(p)` 之后读/传 `p` → `LifetimeError`(使用已释放变量) | +| NewLifetimeWillFreeOkTest | [Test/NewLifetimeWillFreeOkTest/](NewLifetimeWillFreeOkTest/App/main.vp) | **编译成功 + 退出码 0**:P2 合法用法 —— 首次 `free` 本身合法;同名变量**重新赋值**会清除失效态,其后可再用、再 `free`(防误报) | 单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持): @@ -158,7 +161,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`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。 +> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest` / `NewLifetimeFreeTest` / `NewLifetimeAsyncTest` / `NewLifetimeWillFreeTest` / `NewLifetimeUseAfterFreeTest`:断言**编译失败且输出含 `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 201c660..74fb916 100644 --- a/SequentialRegressionTest.py +++ b/SequentialRegressionTest.py @@ -36,10 +36,12 @@ OVERVIEW = os.path.join(TEST_DIR, "README.md") # ============================================================ # 单独运行支持:--escape / --only # ============================================================ -# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构): -# Test/ 下:NewTest、NewLifetimeTest(正向)、NewLifetimeFreeTest / NewLifetimeAsyncTest(负向); +# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构 + P2 willFree): +# Test/ 下:NewTest、NewLifetimeTest、NewLifetimeWillFreeOkTest(正向)、 +# NewLifetimeFreeTest / NewLifetimeAsyncTest / NewLifetimeWillFreeTest / +# NewLifetimeUseAfterFreeTest(负向); # 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest -ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest"] # 负向项目:**期望编译失败**且输出含指定关键字(反向判定,且不运行可执行文件)。 # 这些项目里的用例故意交出本帧栈地址(或跨模块摘要路径),必须被 M1–M2.3 的 @@ -54,6 +56,9 @@ NEGATIVE_PROJECTS = { "NewLifetimeFreeTest": "LifetimeError", # M3 §4.2:协程体(async)里的 `new heap slife` → 释放点无法确定,fail-fast "NewLifetimeAsyncTest": "LifetimeError", + # P2 willFree:`stdlib.free` 带 @life(willFree(p)),二次释放/释放后使用 → LifetimeError + "NewLifetimeWillFreeTest": "LifetimeError", + "NewLifetimeUseAfterFreeTest": "LifetimeError", } # IR 断言:项目名 → 生成 IR 中**必须出现**的子串。 diff --git a/regression_test.py b/regression_test.py index 08522d6..8adc6e6 100644 --- a/regression_test.py +++ b/regression_test.py @@ -85,15 +85,17 @@ def find_test_projects(): # ============================================================ # 单独运行支持:--escape / --only # ============================================================ -# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构): -# Test/ 下:NewTest、NewLifetimeTest(正向)、NewLifetimeFreeTest / NewLifetimeAsyncTest(负向); +# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构 + P2 willFree): +# Test/ 下:NewTest、NewLifetimeTest、NewLifetimeWillFreeOkTest(正向)、 +# NewLifetimeFreeTest / NewLifetimeAsyncTest / NewLifetimeWillFreeTest / +# NewLifetimeUseAfterFreeTest(负向); # 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest -ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest"] # 负向项目(**期望编译失败**,见 SequentialRegressionTest.NEGATIVE_PROJECTS): # 本脚本按"编译必须成功 + 能跑"判定,纳入会误报,故 --escape 时剔除; # 负向断言请用 SequentialRegressionTest(反向判定)或 run_lifetime_negative_tests.py。 -NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest"} +NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest"} def find_project_file(proj_dir): diff --git a/run_lifetime_negative_tests.py b/run_lifetime_negative_tests.py index 2e4a81e..4611dcc 100644 --- a/run_lifetime_negative_tests.py +++ b/run_lifetime_negative_tests.py @@ -32,7 +32,9 @@ PROJECT_ROOT = os.path.dirname(TEST_DIR) # 每个项目只保证"至少触发一条 LifetimeError"。 # NewLifetimeFreeTest:M3 §4.3 判据5(对 slife 对象手动 free)。 # NewLifetimeAsyncTest:M3 §4.2(协程体里的 `new heap slife` → 释放点无法确定,fail-fast)。 -NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest"] +# NewLifetimeWillFreeTest / NewLifetimeUseAfterFreeTest:P2(@life(willFree(p)) 标注驱动—— +# 二次释放 / 释放后使用,对应正向工程 NewLifetimeWillFreeOkTest)。 +NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest"] EXPECT_KEYWORD = "LifetimeError" COMPILE_TIMEOUT = 300