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