278 lines
10 KiB
Plaintext
278 lines
10 KiB
Plaintext
from stdint import *
|
||
import win32.win32console
|
||
import stdio
|
||
|
||
|
||
# =============================================================================
|
||
# M3 owned slife:`new heap slife …` 的 RAII 自动释放(函数级出口统一 free)
|
||
# 设计见 Debugger/ownership-and-new.md §4.2(释放点)/ §4.3(硬约束)。
|
||
#
|
||
# 语义:`new heap slife` 得到 malloc 的对象,**由编译器独占释放**——在引入该绑定的
|
||
# 最内层作用域出口(函数的所有 return / 隐式收尾)`free` 它;赋值处若已有旧对象则
|
||
# 先"覆盖释放"(循环里反复 new 因此不累积);手动 free 被编译期拒绝(见 NewLifetimeFreeTest)。
|
||
#
|
||
# 本工程是**正向**工程:编译必须成功、运行必须退出码 0。每个 Case 返回 0 才算通过,
|
||
# main 累加失败数并在末尾打印结论。
|
||
#
|
||
# IR 断言:本工程**刻意不写任何用户级 free**(否则断言会被用户代码蒙混过关)。
|
||
# SequentialRegressionTest 的 IR_EXPECT 会检查生成的 IR 里出现 `llvm.call @free`
|
||
# (temp/**.ll 是 LLVM dialect 文本,调用写作 `llvm.call @free(%9) : (!llvm.ptr) -> ()`),
|
||
# 那只能来自 M3 的出口释放——缺释放(泄漏)是运行期看不出来的,必须直接断言 IR。
|
||
# =============================================================================
|
||
|
||
|
||
novirtual class Point:
|
||
x: int
|
||
y: int
|
||
|
||
|
||
# (1) 普通出口:字段在释放前可读,函数返回时 free(p)
|
||
def CaseBasic() -> int:
|
||
p: Point* = new heap slife Point(1, 2)
|
||
if p.x != 1: return 1
|
||
if p.y != 2: return 2
|
||
return 0
|
||
|
||
|
||
# (2) 循环内反复 new:靠"覆盖释放"不累积。
|
||
# 第二次赋值前必须先 free 旧值(缺了=泄漏,多了/漏判=double-free 崩)
|
||
def CaseLoop(n: int) -> int:
|
||
i: int = 0
|
||
while i < n:
|
||
p: Point* = new heap slife Point(i, i + 1)
|
||
if p.x != i: return 1
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
# (3) 别名:P4 后 `q = p` 是**所有权转移**(q 接管、p 交出)——出口只释放 q 恰好一次
|
||
# (p 的影子已置 0,出口对它 free(NULL))。释放两次会 abort。
|
||
def CaseAlias() -> int:
|
||
p: Point* = new heap slife Point(3, 4)
|
||
q: Point* = p
|
||
if q.x != 3: return 1
|
||
return 0
|
||
|
||
|
||
# (4) 真·嵌套函数:内层在编译期与外层共享同一张登记表,必须各自隔离——
|
||
# 内层出口只能释放内层对象(否则外层对象被提前/重复释放)
|
||
def CaseNestedDef() -> int:
|
||
a: Point* = new heap slife Point(5, 6)
|
||
|
||
def Inner() -> int:
|
||
b: Point* = new heap slife Point(7, 8)
|
||
return b.y
|
||
|
||
r: int = Inner()
|
||
if r != 8: return 1
|
||
if a.x != 5: return 2
|
||
return 0
|
||
|
||
|
||
# (5) 多出口:early return 的两条路径都要释放。
|
||
# 注意返回**错误码**而不是字段值:成功分支若 `return p.x`(=9),main 里的 `fails +=`
|
||
# 会把它当成失败数累加(现场踩过:19 = 9 + 10,看着像 19 个用例错了,其实全过)。
|
||
def CaseEarlyReturn(flag: int) -> int:
|
||
p: Point* = new heap slife Point(9, 10)
|
||
if flag != 0:
|
||
if p.x != 9: return 1
|
||
return 0
|
||
if p.y != 10: return 2
|
||
return 0
|
||
|
||
|
||
# (6) void 函数 + 裸 return:裸 return 与隐式收尾两条出口都要释放
|
||
def CaseVoidBare(flag: int) -> None:
|
||
p: Point* = new heap slife Point(11, 12)
|
||
if flag != 0:
|
||
return
|
||
stdio.printf("void-slife x=%d\n", p.x)
|
||
|
||
|
||
# (7) 只有分支里才 new:flag=0 时该语句运行期不执行,出口必须 free(NULL)。
|
||
# 缺"入口块置空"就会 free 栈垃圾 → 堆损坏/abort
|
||
def CaseBranchOnly(flag: int) -> int:
|
||
p: Point* = None
|
||
if flag != 0:
|
||
p = new heap slife Point(13, 14)
|
||
if p.x != 13: return 1
|
||
return 0
|
||
|
||
|
||
# (8) stack slife:帧内 alloca,随帧消失,**不得**释放(对栈地址 free 会 abort)
|
||
def CaseStack() -> int:
|
||
p: Point* = new stack slife Point(15, 16)
|
||
if p.x != 15: return 1
|
||
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
|
||
|
||
|
||
novirtual class DtorPoint:
|
||
x: int
|
||
|
||
def __del__(self) -> None:
|
||
global dtor_hits
|
||
dtor_hits += 1
|
||
|
||
|
||
# 出口释放 → 恰好 1 次 __del__
|
||
def CaseDtor() -> int:
|
||
p: DtorPoint* = new heap slife DtorPoint(3)
|
||
if p.x != 3: return 1
|
||
return 0
|
||
|
||
|
||
# 循环内反复 new → 2 次覆盖释放 + 出口 1 次 = 3 次 __del__(析构跟释放点一一对应)
|
||
def CaseDtorOverwrite() -> int:
|
||
i: int = 0
|
||
while i < 3:
|
||
p: DtorPoint* = new heap slife DtorPoint(i)
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
# (10) P1 异常/传播出口:raise 真正**离开函数**前必须释放(用 __del__ 计数验证恰好一次)。
|
||
# 对照点:被本函数 except 捕获的 raise 不得提前释放(对象还在用)——见 CaseRaiseCaught。
|
||
class MyError(Exception):
|
||
pass
|
||
|
||
|
||
def CaseRaiseExitCallee() -> int:
|
||
p: DtorPoint* = new heap slife DtorPoint(5)
|
||
if p.x != 5: return 1
|
||
raise MyError("boom") # 离开函数前:__del__ + free
|
||
|
||
|
||
def CaseRaiseExit() -> int:
|
||
before: int = dtor_hits
|
||
try:
|
||
CaseRaiseExitCallee()
|
||
except MyError:
|
||
pass
|
||
if dtor_hits != before + 1: return 1 # 多一次=double free,少一次=异常路径泄漏
|
||
return 0
|
||
|
||
|
||
# 对照:异常被**本函数**捕获时不得提前释放(对象在 except 体里仍可用),出口才释放。
|
||
# 注意:释放发生在**被调函数返回那一刻**,所以 dtor 计数必须在**调用方**检查——
|
||
# 在被调函数自己体内、return 之前检查(原写法)计数必然还没变,恒判失败(测试自身的 bug,
|
||
# 与 CaseRaiseExit 的差别正在于那边对象属于被调函数)。
|
||
def CaseRaiseCaughtInner() -> int:
|
||
ok: int = 0
|
||
try:
|
||
p: DtorPoint* = new heap slife DtorPoint(6)
|
||
raise MyError("caught")
|
||
except MyError:
|
||
# 对象必须**还活着**:若编译器在 raise 处就释放了,这里就是 use-after-free
|
||
if p.x == 6: ok = 1
|
||
if ok == 0: return 2
|
||
return 0
|
||
|
||
|
||
def CaseRaiseCaught() -> int:
|
||
before: int = dtor_hits
|
||
rc: int = CaseRaiseCaughtInner()
|
||
if rc != 0: return rc
|
||
if dtor_hits != before + 1: return 1 # 内部函数出口恰好释放一次
|
||
return 0
|
||
|
||
|
||
# P4 别名所有权转移:`q = p` 后 **q 接管生命周期**(影子标志从 p 移到 q)。
|
||
# 于是对 p 重新赋值不得释放对象(否则 q 立刻悬垂);出口只释放 q 恰好一次。
|
||
def CaseOwnershipTransfer() -> int:
|
||
before: int = dtor_hits
|
||
p: DtorPoint* = new heap slife DtorPoint(4)
|
||
q: DtorPoint* = p
|
||
p = None # P4:所有权已在 q,这一步**不得**触发析构
|
||
if dtor_hits != before: return 1 # 未转移时这里会析构一次(对象被提前释放)
|
||
if q.x != 4: return 2 # 对象必须仍然有效(值仍在)
|
||
return 0
|
||
|
||
|
||
# P4 自别名:`q = p; q = p` 不能把两个槽共持的对象释放掉
|
||
# (覆盖释放前做运行时判等,相同则跳过)。
|
||
def CaseSelfAliasTransfer() -> int:
|
||
before: int = dtor_hits
|
||
p: DtorPoint* = new heap slife DtorPoint(5)
|
||
q: DtorPoint* = p
|
||
q = p # 自别名重复赋值:不得析构
|
||
if dtor_hits != before: return 1
|
||
if q.x != 5: return 2
|
||
return 0
|
||
|
||
|
||
# 失败可诊断:每个 case 失败时打印名字与返回码(否则只有一个总数,无法定位是哪条挂了)
|
||
def Check(name: str, rc: int, fails: int) -> int:
|
||
if rc != 0:
|
||
stdio.printf(" FAIL %s: rc=%d\n", name, rc)
|
||
return fails + 1
|
||
return fails
|
||
|
||
|
||
export def main() -> int:
|
||
win32.win32console.SetConsoleCP(65001)
|
||
win32.win32console.SetConsoleOutputCP(65001)
|
||
|
||
fails: int = 0
|
||
fails = Check("CaseBasic", CaseBasic(), fails)
|
||
fails = Check("CaseLoop", CaseLoop(200), fails)
|
||
fails = Check("CaseAlias", CaseAlias(), fails)
|
||
fails = Check("CaseNestedDef", CaseNestedDef(), fails)
|
||
fails = Check("CaseEarlyReturn(1)", CaseEarlyReturn(1), fails)
|
||
fails = Check("CaseEarlyReturn(0)", CaseEarlyReturn(0), fails)
|
||
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)
|
||
|
||
# §3.6 析构调度:出口释放恰好 1 次;循环 3 轮(2 次覆盖 + 1 次出口)共 3 次
|
||
fails = Check("CaseDtor", CaseDtor(), fails)
|
||
if dtor_hits != 1:
|
||
stdio.printf(" FAIL dtor_hits after CaseDtor: %d (want 1)\n", dtor_hits)
|
||
fails += 1
|
||
fails = Check("CaseDtorOverwrite", CaseDtorOverwrite(), fails)
|
||
if dtor_hits != 4:
|
||
stdio.printf(" FAIL dtor_hits after CaseDtorOverwrite: %d (want 4)\n", dtor_hits)
|
||
fails += 1
|
||
|
||
# P1 异常出口:传播出去(离开函数)恰好 1 次;被本函数捕获时不得提前释放、出口 1 次
|
||
fails = Check("CaseRaiseExit", CaseRaiseExit(), fails)
|
||
fails = Check("CaseRaiseCaught", CaseRaiseCaught(), fails)
|
||
|
||
# P4 别名所有权转移:函数内对 p 改写不得析构;出口释放 q 恰好一次
|
||
before_ot: int = dtor_hits
|
||
fails = Check("CaseOwnershipTransfer", CaseOwnershipTransfer(), fails)
|
||
if dtor_hits != before_ot + 1:
|
||
stdio.printf(" FAIL dtor_hits after CaseOwnershipTransfer: %d (want %d)\n", dtor_hits, before_ot + 1)
|
||
fails += 1
|
||
|
||
# P4 自别名:重复 `q = p` 不得释放共持对象;出口仍是恰好一次
|
||
before_sa: int = dtor_hits
|
||
fails = Check("CaseSelfAliasTransfer", CaseSelfAliasTransfer(), fails)
|
||
if dtor_hits != before_sa + 1:
|
||
stdio.printf(" FAIL dtor_hits after CaseSelfAliasTransfer: %d (want %d)\n", dtor_hits, before_sa + 1)
|
||
fails += 1
|
||
|
||
if fails != 0:
|
||
stdio.printf("NewLifetimeTest FAILED, %d case(s) wrong\n", fails)
|
||
return 1
|
||
stdio.printf("NewLifetimeTest OK: owned-slife RAII cases passed\n")
|
||
return 0
|