feat(core): 栈地址逃逸检查(LifetimeError)与标量→聚合 fail-fast

- MemoryOps._IsFrameStackAddr 判据 + HandlesReturn/HandlesAssign 汇点挂钩:禁止栈地址逃逸(&local 与数组退让);新增 LifetimeError(FailFastError) 承载该错误类。 - MemoryOps._coerce_value:标量(整数/浮点)不得初始化/赋值给聚合类型,改为 FailFastError(旧行为让 _store 抛 TypeError,非 strict 下被静默吞掉,聚合未初始化)。
This commit is contained in:
TermiNexus committed 2026-10-08 12:10:24 +08:00
1 parent 75aae26881
commit 5a0eecc22f
4 files changed
+81 -1

No files matched your search

+17
View File
@@ -372,6 +372,15 @@ class AssignHandle(BaseHandle):
GVar.initializer = ir.Constant(_gv_type, None) if isinstance(_gv_type, ir.PointerType) else ir.Constant(_gv_type, 0)
else:
GVar = Gen.module.globals[VarName]
# 栈地址逃逸:`G = &local`,或 `G = arr`(G 为指针时的数组退让)——禁止把本帧栈
# 地址写入模块级全局(函数返回后悬垂);目标为同型聚合时按值拷贝,不在此列。
TgtTy1: Any = GVar.type.pointee if isinstance(GVar.type, ir.PointerType) else None
if Gen._IsFrameStackAddr(Node.value, Gen, TgtTy1):
from lib.core.LLVMCG.FuncGen import LifetimeError
raise LifetimeError(
f"栈地址逃逸:不能把本帧栈上存储的地址写入模块级全局变量 {VarName},"
f"函数返回后该指针悬垂;请返回值本身(按值拷贝),或改用堆分配"
)
Gen._store(Value, GVar)
Gen.variables[VarName] = GVar
if isinstance(Value.type, ir.PointerType) and isinstance(Value.type.pointee, (ir.LiteralStructType, ir.IdentifiedStructType)):
@@ -441,6 +450,14 @@ class AssignHandle(BaseHandle):
IsMainScope: bool = CurrentFuncName == 'main' or CurrentFuncName.endswith('.main')
if VarName in Gen.module.globals and (VarName in Gen.global_vars or IsMainScope):
GVar: ir.GlobalVariable = Gen.module.globals[VarName]
# 栈地址逃逸:main 作用域下的全局写入同口径(含 `G = arr` 的数组退让)。
TgtTy2: Any = GVar.type.pointee if isinstance(GVar.type, ir.PointerType) else None
if Gen._IsFrameStackAddr(Node.value, Gen, TgtTy2):
from lib.core.LLVMCG.FuncGen import LifetimeError
raise LifetimeError(
f"栈地址逃逸:不能把本帧栈上存储的地址写入模块级全局变量 {VarName},"
f"函数返回后该指针悬垂;请返回值本身(按值拷贝),或改用堆分配"
)
Gen._store(Value, GVar, Node=Node.value)
Gen.variables[VarName] = GVar
elif _rhs_addr_form and isinstance(Value.type, ir.PointerType) and isinstance(Value.type.pointee, ir.PointerType):
+11 -1
View File
@@ -1,5 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from lib.core.translator import Translator
from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo
@@ -136,6 +136,16 @@ class ReturnHandle(BaseHandle):
Gen.emit_return(result, Node.value)
return
if Node.value:
# 栈地址逃逸:`return &local`(显式取址)与 `return arr`(返回类型为指针时的数组退让,
# 如 `-> char*`)都交出本帧栈地址;`return arr` 且返回类型为同型聚合时按值拷贝,不在此列。
RetTy: Any = (Gen.func.ftype.return_type
if getattr(Gen, 'func', None) is not None and getattr(Gen.func, 'ftype', None) is not None else None)
if Gen._IsFrameStackAddr(Node.value, Gen, RetTy):
from lib.core.LLVMCG.FuncGen import LifetimeError
raise LifetimeError(
f"栈地址逃逸:return 交出了本帧栈上存储的地址(局部变量/数组或形参),函数返回后即悬垂;"
f"请返回值本身(按值拷贝),或改用堆分配"
)
# 本函数返回类型即"使用处宽度":先取回返回类型再翻译值,供 CDefine 常量
# 按该位宽物化(更宽 ⇒ 升宽物化;更窄 ⇒ fail-fast)。
ret_type: ir.Type | None = getattr(Gen.func, 'ftype', None) and Gen.func.ftype.return_type if getattr(Gen, 'func', None) else None
+8
View File
@@ -19,6 +19,14 @@ class FailFastError(Exception):
"""
class LifetimeError(FailFastError):
"""生命周期/逃逸类编译错误(如栈地址逃逸:把本帧局部/形参的地址写到帧外)。
继承 FailFastError:既有 `isinstance(..., FailFastError)` 的捕获/上抛语义全部保持不变,
仅让错误类型名独立出来(与 TransPyV 的 err_type "LifetimeError" 同口径)。
"""
class MemberLookupFailFast(KeyError):
"""成员偏移查找失败的 fail-fast 异常(**继承 KeyError** 以保持既有兜底点可用)。
+45
View File
@@ -388,6 +388,37 @@ class MemoryOpsMixin:
return Node.value
return Node
@staticmethod
def _IsFrameStackAddr(Node: Any, Gen: Any, TargetType: Any = None) -> bool:
"""AST 判据:Node 是否交出「当前帧栈上存储的地址」。
两种形态(与 TransPyV fail_if_stack_addr_escape 同口径):
(a) `&local`:UnaryOp(AddrOf, Name),且该 Name 是本帧局部 alloca(含形参);
(b) 数组退让:裸本帧**数组**名出现在**指针**目标位置(如 `return arr` with `-> char*`),
交出的是首元素地址,同属栈地址。
模块级全局在静态存储、函数在代码段,取址可以逃逸,故排除。裸聚合名 + 同型聚合目标
是按值拷贝,不是逃逸,故不在 (b) 判据内。
"""
Vars: Any = getattr(Gen, 'variables', None)
if isinstance(Node, ast.UnaryOp) and isinstance(Node.op, ast.AddrOf):
Inner: Any = Node.operand
if not isinstance(Inner, ast.Name) or Inner.id is None:
return False
Var: Any = Vars.get(Inner.id) if Vars is not None else None
if isinstance(Var, ir.AllocaInstr):
return True
Module: Any = getattr(Gen, 'module', None)
if Module is not None and Inner.id in getattr(Module, 'globals', {}):
return False
# 全局/函数之外的已知名(如未登记的形参)保守判为栈地址
return True
if isinstance(Node, ast.Name) and Node.id is not None and isinstance(TargetType, ir.PointerType):
Var2: Any = Vars.get(Node.id) if Vars is not None else None
if (isinstance(Var2, ir.AllocaInstr) and isinstance(Var2.type, ir.PointerType)
and isinstance(Var2.type.pointee, ir.ArrayType)):
return True
return False
@staticmethod
def _IsSameAggregate(A: Any, B: Any) -> bool:
"""两个 LLVM 聚合类型是否指同一类型(对象同一,或 Identified 结构体 IR 名相同)。"""
@@ -606,6 +637,20 @@ class MemoryOpsMixin:
if isinstance(target_type, ir.IdentifiedStructType) and target_type.name in self.typedef_int_widths:
if isinstance(val.type, ir.IntType):
return val
# 聚合目标(数组/结构体)不接受标量(整数/浮点)初始化或赋值:标量→聚合无
# 转换语义,旧行为让本方法返回 None,_store 再抛 TypeError,而该 TypeError 在
# 非 strict 模式被静默吞掉 → 聚合完全未初始化(历史现场:`l: [10]char = 0`)。
# 直接 fail-fast(FailFastError 不被 TypeError 捕获,故能穿透调用点的 try)并
# 提示正确写法。typedef 伪整数已在上方短路,不受影响。
if (isinstance(target_type, (ir.ArrayType, ir.LiteralStructType, ir.IdentifiedStructType))
and isinstance(val.type, (ir.IntType, ir.FloatType, ir.DoubleType))):
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"标量(整数/浮点)不能初始化或赋值给聚合类型 {target_type}:"
f"旧行为只写首元素字节或整体不初始化,聚合其余部分保持未初始化;"
f"聚合零初始化请写 None,填充内容请用列表字面量 [...] 或逐元素赋值"
f"{self._DescribeValueNode(Node)}"
)
if isinstance(target_type, ir.IntType) and isinstance(val.type, ir.IntType):
if target_type.width < val.type.width:
# 目标 i1:真值归一化(与 bool(x)/if 条件同一判据),不是 trunc 取最低位