diff --git a/lib/core/Handles/HandlesAssign.py b/lib/core/Handles/HandlesAssign.py index a3b2124..59b9014 100644 --- a/lib/core/Handles/HandlesAssign.py +++ b/lib/core/Handles/HandlesAssign.py @@ -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): diff --git a/lib/core/Handles/HandlesReturn.py b/lib/core/Handles/HandlesReturn.py index e4fa596..57b5ced 100644 --- a/lib/core/Handles/HandlesReturn.py +++ b/lib/core/Handles/HandlesReturn.py @@ -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 diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index e3a5f99..808fd42 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -19,6 +19,14 @@ class FailFastError(Exception): """ +class LifetimeError(FailFastError): + """生命周期/逃逸类编译错误(如栈地址逃逸:把本帧局部/形参的地址写到帧外)。 + + 继承 FailFastError:既有 `isinstance(..., FailFastError)` 的捕获/上抛语义全部保持不变, + 仅让错误类型名独立出来(与 TransPyV 的 err_type "LifetimeError" 同口径)。 + """ + + class MemberLookupFailFast(KeyError): """成员偏移查找失败的 fail-fast 异常(**继承 KeyError** 以保持既有兜底点可用)。 diff --git a/lib/core/LLVMCG/MemoryOps.py b/lib/core/LLVMCG/MemoryOps.py index 1340f4e..7d5b685 100644 --- a/lib/core/LLVMCG/MemoryOps.py +++ b/lib/core/LLVMCG/MemoryOps.py @@ -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 取最低位