diff --git a/NewLifetimeRetEscapeTest/App/main.vp b/NewLifetimeRetEscapeTest/App/main.vp new file mode 100644 index 0000000..eb3be86 --- /dev/null +++ b/NewLifetimeRetEscapeTest/App/main.vp @@ -0,0 +1,56 @@ +from stdint import * + + +# ============================================================================= +# 负向工程(**应当编译失败**):A2 `@life(ret(frame))` —— 去向由**调用点**判定 +# +# A2 移除了定义端对 `return <本帧地址>` 的 fail-fast:定义端无法区分"交出本帧" +# 与"借出调用方的存储"(后者是 `return <借用形参>`,一律拦会误报)。取而代之: +# 被调者用 `@life(ret(...))` 声明返回值生命期,**调用点**按声明判去向。 +# +# 本工程验:声明为 `frame` 的返回值被写进**长命存储**时必须被拒。 +# 期望报错:LifetimeError(`a longer-lived field/element`)。 +# +# 隔离性:FrameRet 的逃逸摘要为 0(`l` 不是形参,`return l` 不置任何逃逸位), +# 所以下面唯一的报错来源就是 `ret(frame)` → LIFE_FRAME。 +# +# 本文件只保留**一条**会报错的语句:编译在第一个错误处终止,多条会掩盖顺序。 +# 对照写法(`ret(borrows=x)` 的结果同样只在本帧)留作注释,见文末。 +# ============================================================================= + + +novirtual class Holder: + p: int* + + +@life(ret(frame)) +def FrameRet() -> int*: + l: int* = new stack int + return l + + +# 唯一用例:ret(frame) 的结果存进**堆对象**字段 → 左值长命、右值本帧 → LifetimeError +def CaseFrame() -> int: + h: Holder* = new heap hlife Holder() + h.p = FrameRet() # ❌ 期望 LifetimeError + return 0 + + +export def main() -> int: + CaseFrame() + return 0 + + +# ----------------------------------------------------------------------------- +# 对照用例(把 main 里的 CaseFrame() 换成本函数即可,一次只开一条): +# +# @life(noescape(x), ret(borrows=x)) +# def IdentBorrow(x: int*) -> int*: +# return x +# +# def CaseBorrow() -> int: +# h: Holder* = new heap hlife Holder() +# l: int* = new stack int +# h.p = IdentBorrow(l) # ❌ 期望 LifetimeError(结果生命期 = l 的 = 本帧) +# return 0 +# ----------------------------------------------------------------------------- diff --git a/NewLifetimeRetEscapeTest/project.json b/NewLifetimeRetEscapeTest/project.json new file mode 100644 index 0000000..f4526f4 --- /dev/null +++ b/NewLifetimeRetEscapeTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeRetEscapeTest", + "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": "NewLifetimeRetEscapeTest.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/NewLifetimeRetOkTest/App/main.vp b/NewLifetimeRetOkTest/App/main.vp new file mode 100644 index 0000000..9d6b7f0 --- /dev/null +++ b/NewLifetimeRetOkTest/App/main.vp @@ -0,0 +1,64 @@ +from stdint import * +import stdio +import retdep + + +# ============================================================================= +# 正向工程:A2 `@life(ret(...))` 契约**不得误报**(编译必须成功、运行退出码 0) +# +# 对照负向工程: +# · NewLifetimeRetEscapeTest —— 同模块 `ret(frame)` 的结果存进长命存储 → 必须报; +# · NewLifetimeRetXmodTest —— 跨模块 `ret(borrows=x)`(经 .vpi 的 escr=)→ 必须报。 +# +# 本工程覆盖: +# (1) 非帧契约(heap / static / borrows=none)→ 结果可存进**堆对象字段**(长命左值); +# (2) ret(frame) → 结果只在本帧内使用 → 合法(不是"无害",而是调用点按声明**放行**); +# (3) ret(borrows=x) → 结果的生命期 = 实参 x 的生命期(本帧实参 → 帧内使用合法); +# (4) 同模块(本文件契约表)与跨模块(retdep.vp,经 .vpi 的 escr=)两条取用路径都走一遍。 +# +# 注意:本文件**不得**把 `ret(frame)` / `ret(borrows=x)` 的结果写进堆对象字段 +# (`h.p = …`)——那是负向工程负责证明会被拒的写法。 +# ============================================================================= + + +novirtual class Holder: + p: int* + c: char* + + +# ---- 同模块契约(走本文件 EscDirTable,不经 .vpi)---- + +@life(ret(heap)) +def LocalHeap() -> int*: + return new heap hlife int + + +@life(ret(frame)) +def LocalFrame() -> int*: + l: int* = new stack int + return l + + +export def main() -> int: + h: Holder* = new heap hlife Holder() + + # (1) 非帧契约 → 存进堆对象字段(左值长命、右值非帧)→ 放行 + h.p = retdep.MkHeap() # 跨模块 ret(heap) + h.c = retdep.MkStatic() # 跨模块 ret(static) + h.p = retdep.Fresh() # 跨模块 ret(borrows=none) + h.p = LocalHeap() # 同模块 ret(heap) + + # (2) ret(frame):结果只在本帧内使用 → 放行 + pf: int* = retdep.FrameRet() # 跨模块 ret(frame) + pl: int* = LocalFrame() # 同模块 ret(frame) + + # (3) ret(borrows=x):结果生命期 = 实参生命期;实参在本帧 → 结果只在本帧用 → 放行 + base: int* = new stack int + pb: int* = retdep.IdentBorrow(base) + + if pf is None: return 1 + if pl is None: return 2 + if pb is None: return 3 + + stdio.printf("RetOkTest OK: @life(ret(...)) contracts accepted\n") + return 0 diff --git a/NewLifetimeRetOkTest/App/retdep.vp b/NewLifetimeRetOkTest/App/retdep.vp new file mode 100644 index 0000000..e0137eb --- /dev/null +++ b/NewLifetimeRetOkTest/App/retdep.vp @@ -0,0 +1,45 @@ +from stdint import * + + +# ============================================================================= +# A2(§14)`@life(ret(...))` 的被调方:五种返回值生命期契约各一条。 +# +# 调用点(main.vp)**跨模块**取用 → 本文件的契约只能经 .vpi 行尾 +# `# esc=… escn=… escr=…` 传递(本地契约表按文件重置,调用点必然查不到)。 +# 设计与编码见 Debugger/ownership-and-new.md §14.3 / §14.3.1(GenApi.RET_KIND_*)。 +# ============================================================================= + + +# ret(heap):自由生命期 → 结果可自由存进长命存储 +@life(ret(heap)) +def MkHeap() -> int*: + return new heap hlife int + + +# ret(static):指向静态存储(字符串常量) +@life(ret(static)) +def MkStatic() -> char*: + return "static-lit" + + +# ret(borrows=none):返回值**不借用任何形参** → 非本帧 +@life(ret(borrows=none)) +def Fresh() -> int*: + return new heap hlife int + + +# ret(frame):返回值可能指向**本帧**存储 → 去向由调用点负责 +# (A2 已移除定义端对 `return <本帧地址>` 的 fail-fast:定义端无法区分 +# "交出本帧"与"借出调用方的存储",一律拦会误报 `return <借用形参>`。) +@life(ret(frame)) +def FrameRet() -> int*: + l: int* = new stack int + return l + + +# ret(borrows=x):返回值借用形参 x —— 调用点把结果当 x 的生命期。 +# 配 `noescape(x)`:本函数只**借出** x、不把它存进长命存储,故形参 x 不算逃逸; +# 否则调用点会先在"实参逃逸"那条路径上拦下来,掩盖 ret 契约本身。 +@life(noescape(x), ret(borrows=x)) +def IdentBorrow(x: int*) -> int*: + return x diff --git a/NewLifetimeRetOkTest/project.json b/NewLifetimeRetOkTest/project.json new file mode 100644 index 0000000..a3f11e7 --- /dev/null +++ b/NewLifetimeRetOkTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeRetOkTest", + "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": "NewLifetimeRetOkTest.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/NewLifetimeRetXmodTest/App/main.vp b/NewLifetimeRetXmodTest/App/main.vp new file mode 100644 index 0000000..3565642 --- /dev/null +++ b/NewLifetimeRetXmodTest/App/main.vp @@ -0,0 +1,43 @@ +from stdint import * +import retdep2 + + +# ============================================================================= +# 负向工程(**应当编译失败**):A2 **跨模块** `@life(ret(...))` —— 契约经 .vpi 的 `escr=` +# +# 被调者定义在 retdep2.vp,契约(含 ret_kind)只能经对方 .vpi 行尾的 `escr=` 传来; +# 本文件里它的本地契约表必然为空 → 命中跨模块回退路径。 +# +# 本工程验:跨模块声明为 `borrows=x` 的返回值(生命期 = 实参 = 本帧)被写进长命存储 +# 时必须被拒。期望报错:LifetimeError(`a longer-lived field/element`)。 +# +# 隔离性:IdentBorrow 带 `@life(noescape(x))`,"实参逃逸"那条路径被清掉, +# 故唯一的报错来源是 .vpi 的 `escr=` → ret(borrows=x) → 结果 LIFE_FRAME。 +# ============================================================================= + + +novirtual class Holder: + p: int* + + +# 唯一用例:跨模块 ret(borrows=x) 的结果存进**堆对象**字段 → 左值长命、右值本帧 +def CaseBorrow() -> int: + h: Holder* = new heap hlife Holder() + l: int* = new stack int + h.p = retdep2.IdentBorrow(l) # ❌ 期望 LifetimeError + return 0 + + +export def main() -> int: + CaseBorrow() + return 0 + + +# ----------------------------------------------------------------------------- +# 对照用例(把 main 里的 CaseBorrow() 换成本函数即可,一次只开一条): +# +# def CaseFrame() -> int: +# h: Holder* = new heap hlife Holder() +# h.p = retdep2.FrameRet() # ❌ 期望 LifetimeError +# return 0 +# ----------------------------------------------------------------------------- diff --git a/NewLifetimeRetXmodTest/App/retdep2.vp b/NewLifetimeRetXmodTest/App/retdep2.vp new file mode 100644 index 0000000..4248d0a --- /dev/null +++ b/NewLifetimeRetXmodTest/App/retdep2.vp @@ -0,0 +1,24 @@ +from stdint import * + + +# ============================================================================= +# A2 跨模块负例的**被调方**:契约(逃逸掩码 + 返回值生命期)只能经本模块 .vpi +# 行尾 `# esc=… escn=… escr=…` 传到调用点 —— 本文件与 main.vp 不同模块, +# 调用点的本地契约表按文件重置、必然查不到,命中 +# CallGenBase.lookup_ret_kind_via_vpi → VpiReader 的 escr= 缓存这条回退路径。 +# ============================================================================= + + +# ret(borrows=x):返回值借用形参 x —— 调用点把结果当 x 的生命期。 +# 配 `noescape(x)`:本函数只**借出** x、不把它存进长命存储 → 形参 x 不算逃逸, +# 于是调用点上"实参逃逸"那条路径不会先报错,判据收敛到 ret 契约本身。 +@life(noescape(x), ret(borrows=x)) +def IdentBorrow(x: int*) -> int*: + return x + + +# 对照:`ret(frame)`(把 main 的用例换成 CaseFrame 时用)。返回值指向本帧存储。 +@life(ret(frame)) +def FrameRet() -> int*: + l: int* = new stack int + return l diff --git a/NewLifetimeRetXmodTest/project.json b/NewLifetimeRetXmodTest/project.json new file mode 100644 index 0000000..6f09c5e --- /dev/null +++ b/NewLifetimeRetXmodTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewLifetimeRetXmodTest", + "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": "NewLifetimeRetXmodTest.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 641b8a3..966bd84 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,9 @@ ## 一·附、生命期 / 逃逸(escape)相关测试 验证 M1–M3 的生命期检查(栈地址逃逸、借用传播、函数逃逸摘要)、`new` 描述符分配, -以及 `new heap slife` 的 RAII 自动释放(M3)。因历史原因前 4 个位于**仓库根**(不在 `Test/` 下): +`new heap slife` 的 RAII 自动释放(M3),以及 `@life(...)` 标注面(P2 `willFree`、 +§14 `escape/stores`、`noescape/borrows`、A2 `ret(...)`:返回值生命期契约,声明优先于推断)。 +因历史原因前 4 个位于**仓库根**(不在 `Test/` 下): | 项目 | 位置 | 期望结果 | |---|---|---| @@ -142,6 +144,9 @@ | 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` | +| NewLifetimeRetOkTest | [Test/NewLifetimeRetOkTest/](NewLifetimeRetOkTest/App/main.vp) | **编译成功 + 退出码 0**:A2(§14)`@life(ret(...))` 契约**不得误报** —— `ret(heap)`/`ret(static)`/`ret(borrows=none)` 的结果存进堆对象字段;`ret(frame)`/`ret(borrows=x)` 的结果只在本帧内使用;同模块(契约表)与跨模块(`retdep.vp`,经 `.vpi` 的 `escr=`)两条取用路径都走一遍 | +| NewLifetimeRetEscapeTest | [Test/NewLifetimeRetEscapeTest/](NewLifetimeRetEscapeTest/App/main.vp) | **编译失败**:A2 —— 同模块 `@life(ret(frame))` 的返回值被写进**堆对象字段**(长命左值)→ `LifetimeError`(A2 已移除定义端 `return <本帧地址>` 的 fail-fast,去向改由调用点按契约判定) | +| NewLifetimeRetXmodTest | [Test/NewLifetimeRetXmodTest/](NewLifetimeRetXmodTest/App/main.vp) | **编译失败**:A2 **跨模块** —— 被调者(`retdep2.vp`)声明 `@life(noescape(x), ret(borrows=x))`,结果(生命期 = 实参 = 本帧)写进堆对象字段 → `LifetimeError`(契约经对方 `.vpi` 的 `escr=` 传来) | 单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持): @@ -166,7 +171,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` / `NewLifetimeWillFreeMethodTest` / `NewLifetimeWillFreeAddrTest` / `NewLifetimeFieldEscapeTest` / `NewLifetimeAnnFieldEscapeTest`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。 +> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest` / `NewLifetimeFreeTest` / `NewLifetimeAsyncTest` / `NewLifetimeWillFreeTest` / `NewLifetimeUseAfterFreeTest` / `NewLifetimeWillFreeKwargTest` / `NewLifetimeWillFreeMethodTest` / `NewLifetimeWillFreeAddrTest` / `NewLifetimeFieldEscapeTest` / `NewLifetimeAnnFieldEscapeTest` / `NewLifetimeRetEscapeTest` / `NewLifetimeRetXmodTest`:断言**编译失败且输出含 `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 6ddb20a..1f624ac 100644 --- a/SequentialRegressionTest.py +++ b/SequentialRegressionTest.py @@ -36,12 +36,13 @@ OVERVIEW = os.path.join(TEST_DIR, "README.md") # ============================================================ # 单独运行支持:--escape / --only # ============================================================ -# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构 + P2 willFree): -# Test/ 下:NewTest、NewLifetimeTest、NewLifetimeWillFreeOkTest(正向)、 +# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构 + +# P2 willFree + A2 @life(ret(...))): +# Test/ 下:NewTest、NewLifetimeTest、NewLifetimeWillFreeOkTest、NewLifetimeRetOkTest(正向)、 # NewLifetimeFreeTest / NewLifetimeAsyncTest / NewLifetimeWillFreeTest / -# NewLifetimeUseAfterFreeTest(负向); +# NewLifetimeUseAfterFreeTest / NewLifetimeRetEscapeTest / NewLifetimeRetXmodTest(负向); # 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest -ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeRetOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest", "NewLifetimeRetEscapeTest", "NewLifetimeRetXmodTest"] # 负向项目:**期望编译失败**且输出含指定关键字(反向判定,且不运行可执行文件)。 # 这些项目里的用例故意交出本帧栈地址(或跨模块摘要路径),必须被 M1–M2.3 的 @@ -69,6 +70,10 @@ NEGATIVE_PROJECTS = { "NewLifetimeFieldEscapeTest": "LifetimeError", # P3:obj.f: T = &local(注解形态的属性目标,同一条规则;防"非 Name 目标被静默丢弃") "NewLifetimeAnnFieldEscapeTest": "LifetimeError", + # A2(§14):@life(ret(frame)) 的结果存进堆对象字段(同模块契约表 EscDirTable) + "NewLifetimeRetEscapeTest": "LifetimeError", + # A2(§14):@life(ret(borrows=x)) 的结果存进堆对象字段(跨模块,经 .vpi 的 escr=) + "NewLifetimeRetXmodTest": "LifetimeError", } # IR 断言:项目名 → 生成 IR 中**必须出现**的子串。 diff --git a/regression_test.py b/regression_test.py index 38a43b7..4752a80 100644 --- a/regression_test.py +++ b/regression_test.py @@ -85,17 +85,18 @@ def find_test_projects(): # ============================================================ # 单独运行支持:--escape / --only # ============================================================ -# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构 + P2 willFree): -# Test/ 下:NewTest、NewLifetimeTest、NewLifetimeWillFreeOkTest(正向)、 +# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII + §3.6 析构 + +# P2 willFree + A2 @life(ret(...))): +# Test/ 下:NewTest、NewLifetimeTest、NewLifetimeWillFreeOkTest、NewLifetimeRetOkTest(正向)、 # NewLifetimeFreeTest / NewLifetimeAsyncTest / NewLifetimeWillFreeTest / -# NewLifetimeUseAfterFreeTest(负向); +# NewLifetimeUseAfterFreeTest / NewLifetimeRetEscapeTest / NewLifetimeRetXmodTest(负向); # 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest -ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"] +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeWillFreeOkTest", "NewLifetimeRetOkTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest", "NewLifetimeRetEscapeTest", "NewLifetimeRetXmodTest"] # 负向项目(**期望编译失败**,见 SequentialRegressionTest.NEGATIVE_PROJECTS): # 本脚本按"编译必须成功 + 能跑"判定,纳入会误报,故 --escape 时剔除; # 负向断言请用 SequentialRegressionTest(反向判定)或 run_lifetime_negative_tests.py。 -NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"} +NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest", "NewLifetimeRetEscapeTest", "NewLifetimeRetXmodTest"} def find_project_file(proj_dir): diff --git a/run_lifetime_negative_tests.py b/run_lifetime_negative_tests.py index 4043615..aa8bee4 100644 --- a/run_lifetime_negative_tests.py +++ b/run_lifetime_negative_tests.py @@ -34,7 +34,9 @@ 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", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest"] +# NewLifetimeRetEscapeTest / NewLifetimeRetXmodTest:A2(§14)`@life(ret(...))`—— +# 声明为 frame / borrows=x 的返回值被写进长命存储(同模块契约表 / 跨模块 .vpi 的 escr=)。 +NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest", "NewLifetimeAsyncTest", "NewLifetimeWillFreeTest", "NewLifetimeUseAfterFreeTest", "NewLifetimeWillFreeKwargTest", "NewLifetimeWillFreeMethodTest", "NewLifetimeWillFreeAddrTest", "NewLifetimeFieldEscapeTest", "NewLifetimeAnnFieldEscapeTest", "NewLifetimeRetEscapeTest", "NewLifetimeRetXmodTest"] EXPECT_KEYWORD = "LifetimeError" COMPILE_TIMEOUT = 300