From cd102989a863abe164902f9ffb35ddd1624eadcc Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 8 Oct 2026 22:47:40 +0800 Subject: [PATCH] =?UTF-8?q?test(lifetime):=20=E6=96=B0=E5=A2=9E=20P3=20?= =?UTF-8?q?=E8=B4=9F=E5=90=91=E5=B7=A5=E7=A8=8B=EF=BC=88=E5=A0=86=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=AD=97=E6=AE=B5=E6=94=B6=E4=B8=8B=E6=9C=AC=E5=B8=A7?= =?UTF-8?q?=E6=A0=88=E5=9C=B0=E5=9D=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NewLifetimeFieldEscapeTest:h.p = &local(h 由 new heap hlife 得到,字段活得比本帧长)必须报 LifetimeError;同步注册到 SequentialRegressionTest / regression_test / run_lifetime_negative_tests 与 README。 --- NewLifetimeFieldEscapeTest/App/main.vp | 28 ++++++++++++++++++++++++ NewLifetimeFieldEscapeTest/project.json | 29 +++++++++++++++++++++++++ README.md | 3 ++- SequentialRegressionTest.py | 4 +++- regression_test.py | 4 ++-- run_lifetime_negative_tests.py | 2 +- 6 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 NewLifetimeFieldEscapeTest/App/main.vp create mode 100644 NewLifetimeFieldEscapeTest/project.json diff --git a/NewLifetimeFieldEscapeTest/App/main.vp b/NewLifetimeFieldEscapeTest/App/main.vp new file mode 100644 index 0000000..dcff498 --- /dev/null +++ b/NewLifetimeFieldEscapeTest/App/main.vp @@ -0,0 +1,28 @@ +from stdint import * + + +# ============================================================================= +# 负向工程(**应当编译失败**):P3 —— 禁止把生命期更短的右值赋给生命期更长的左值 +# +# §12-P3 的一般规则:右值生命期比目标左值的存储更短 → 报 LifetimeError。 +# 本工程是规则点名的实例:`obj.f = &local` +# · 左值 h.p 的存储属于**堆对象**(h 由 `new heap hlife` 得到)→ 活得比本帧长; +# · 右值 &local 是**本帧栈地址**(LIFE_FRAME)→ 出帧即成悬垂指针。 +# 期望报错:LifetimeError。 +# +# 对照(必须放行,由 --escape 集里的既有正向工程保证不误报): +# · `local_struct.f = &local2`:左值也在本帧(LIFE_FRAME)→ 两者同命,合法; +# · `p = &local`:p 是本帧局部指针槽(LIFE_FRAME)→ 合法。 +# 注意 h 必须在**同一个函数**里 `new`,否则它作为形参进来时 Life=UNKNOWN(保守放行)。 +# ============================================================================= + + +novirtual class Holder: + p: int* + + +export def main() -> int: + h: Holder* = new heap hlife Holder() + local: int = 7 + h.p = &local # ← 必须被拒:把本帧栈地址写进堆对象字段 + return 0 diff --git a/NewLifetimeFieldEscapeTest/project.json b/NewLifetimeFieldEscapeTest/project.json new file mode 100644 index 0000000..621b22d --- /dev/null +++ b/NewLifetimeFieldEscapeTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeFieldEscapeTest", + "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": "NewLifetimeFieldEscapeTest.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 8e12b42..f933cdf 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ | 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` | +| NewLifetimeFieldEscapeTest | [Test/NewLifetimeFieldEscapeTest/](NewLifetimeFieldEscapeTest/App/main.vp) | **编译失败**:P3 —— `h.p = &local`(堆对象字段收下本帧栈地址)→ `LifetimeError` | 单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持): @@ -162,7 +163,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`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。 +> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest` / `NewLifetimeFreeTest` / `NewLifetimeAsyncTest` / `NewLifetimeWillFreeTest` / `NewLifetimeUseAfterFreeTest` / `NewLifetimeWillFreeKwargTest` / `NewLifetimeFieldEscapeTest`:断言**编译失败且输出含 `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 65e65c9..3775241 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"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"] # 负向项目:**期望编译失败**且输出含指定关键字(反向判定,且不运行可执行文件)。 # 这些项目里的用例故意交出本帧栈地址(或跨模块摘要路径),必须被 M1–M2.3 的 @@ -61,6 +61,8 @@ NEGATIVE_PROJECTS = { "NewLifetimeUseAfterFreeTest": "LifetimeError", # P2:关键字实参路径(stdlib.free(p = p) 之后再用 p) "NewLifetimeWillFreeKwargTest": "LifetimeError", + # P3:obj.f = &local(堆对象字段收下本帧栈地址) + "NewLifetimeFieldEscapeTest": "LifetimeError", } # IR 断言:项目名 → 生成 IR 中**必须出现**的子串。 diff --git a/regression_test.py b/regression_test.py index c36a4a2..97abf57 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"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"] # 负向项目(**期望编译失败**,见 SequentialRegressionTest.NEGATIVE_PROJECTS): # 本脚本按"编译必须成功 + 能跑"判定,纳入会误报,故 --escape 时剔除; # 负向断言请用 SequentialRegressionTest(反向判定)或 run_lifetime_negative_tests.py。 -NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest"} +NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"} def find_project_file(proj_dir): diff --git a/run_lifetime_negative_tests.py b/run_lifetime_negative_tests.py index 5e0d44c..f283c63 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"] +NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeFieldEscapeTest"] EXPECT_KEYWORD = "LifetimeError" COMPILE_TIMEOUT = 300