fix(tpc): 联合强转 (T|t.CPtr)(x) 及其属性访问失败改为 fail-fast(附判定依据)

类别:b 机制漏豁免(把静默 return None 改成响亮失败并带证据)。

现场:TransPyV lib/core/Gen/Expr/ExprGenBase.vp:360 的
v = (ast.Expr*(node)).value 报“赋值表达式 'value'(Attribute 形态)编译期
求值为空”,而同一函数前三条 (ast.Assign/AnnAssign/AugAssign*(node)).value
同款写法正常 ⇒ 无法从报错看出是哪一步丢了类型。

两处静默返回改为 fail-fast(均只对该强转形态生效,其余路径行为不变):
1. HandlesExprCall._HandleTypeUnionCastLlvm:目标类型串解析失败(原为
   _vlog().warning + return None)。新增 _DescribeUnionCastFailure 取证:
   MergeTypes 返回值/ToString()/异常、_ExtractTypeStrFromUnionBinOp 结果,
   以及联合中每个操作数在符号表与类登记表(Gen.structs / _struct_sha1_map /
   class_sha1_map)的命中情况 —— 直接指认“基座类名没被当成类”还是
   “CPtr 标记侧解析失败”。
2. HandlesExprAttr._handle_attr_call_result:(T|t.CPtr)(x).attr 的 5 个
   return None(基座求值为空 / 非指针 / find_struct_by_pointee 未命中 /
   结构体不透明 / 成员偏移取不到)改为 _FailFastAttrCallBase,报文含联合
   基类名与三家登记表命中、被指类型 elements、class_members 项数与目标成员偏移。

原因:(T|t.CPtr)(x) 只可能来自指针强转(parser 把 (T*)x / (T*)(x) / T*(x)
统一产成该 Call),必须有值产出;静默丢弃会让整条表达式被判为空,把
“类名解析失败 / 结构体不透明 / 成员偏移取不到”三种不同根因混成同一条报文。
This commit is contained in:
TermiNexus committed 2026-09-29 12:23:54 +08:00
1 parent 8263f6ec47
commit fd212b3ff8
2 files changed
+166 -13

No files matched your search

+86 -7
View File
@@ -951,25 +951,39 @@ class ExprAttrHandle(BaseHandle):
ObjVal = inner_val
if ObjVal is None:
ObjVal = self.HandleExprLlvm(Node.value)
if not ObjVal:
return None
# 联合类型转换 (X | t.CPtr)(arg) 的属性访问(治本修复):
# TPC 联合转换对非 CType 类(如 ast.Name / ast.Call)目标类型解析失败,
# 结果可能退回 Ptr(i8) 或错误类型,导致下方 find_struct_by_pointee 失败、
# 属性访问被静默丢弃——((ast.Name|t.CPtr)(ov)).id 编译后 store 0 次
# (with 语句 asname 未绑定、pool 未定义根因)。从联合 BinOp 提取真实
# 基础类并 bitcast 到 Ptr(类) 再访问成员。
if isinstance(Node.value, ast.Call) and isinstance(Node.value.func, ast.BinOp) \
and isinstance(Node.value.func.op, ast.BitOr):
union_cls: str | None = self._extract_union_base_class(Node.value.func)
if union_cls and union_cls in Gen.structs:
target_ty: Any = ir.PointerType(Gen.structs[union_cls])
#
# 该形态的每个 return None 都改成 fail-fast(见 _FailFastAttrCallBase):
# `(T | t.CPtr)(x)` 只可能来自指针强转(parser 把 `(T*)x` / `T*(x)` 统一
# 产成该 Call),返回 None 会让调用方只报"表达式编译期求值为空",看不出是
# 类名没被当成类、结构体不透明、还是成员偏移取不到(现场:TransPyV
# ExprGenBase.vp:360 `v = (ast.Expr*(node)).value`,而同一函数前三条
# `(ast.Assign/AnnAssign/AugAssign*(node)).value` 同款写法正常)。
_IsUnionCastBase: bool = (isinstance(Node.value, ast.Call)
and isinstance(Node.value.func, ast.BinOp)
and isinstance(Node.value.func.op, ast.BitOr))
_UnionCastCls: str | None = None
if _IsUnionCastBase:
_UnionCastCls = self._extract_union_base_class(Node.value.func)
if ObjVal is not None and _UnionCastCls and _UnionCastCls in Gen.structs:
target_ty: Any = ir.PointerType(Gen.structs[_UnionCastCls])
if isinstance(ObjVal.type, ir.PointerType):
if ObjVal.type != target_ty:
ObjVal = Gen.builder.bitcast(ObjVal, target_ty, name="cast_union_attr")
elif isinstance(ObjVal.type, ir.IntType):
ObjVal = Gen.builder.inttoptr(ObjVal, target_ty, name="int2ptr_union_attr")
if not ObjVal:
self._FailFastAttrCallBase("基座强转调用求值为空(HandleExprLlvm 返回 None)",
Node, Gen, _IsUnionCastBase, _UnionCastCls, None)
return None
if not isinstance(ObjVal.type, ir.PointerType):
self._FailFastAttrCallBase("基座强转结果不是指针类型",
Node, Gen, _IsUnionCastBase, _UnionCastCls, ObjVal)
return None
pointee: Any = ObjVal.type.pointee
if isinstance(pointee, ir.PointerType):
@@ -977,6 +991,8 @@ class ExprAttrHandle(BaseHandle):
pointee = ObjVal.type.pointee
found: Any = Gen.find_struct_by_pointee(pointee)
if not found:
self._FailFastAttrCallBase("被指结构体未在 structs 登记(find_struct_by_pointee 未命中)",
Node, Gen, _IsUnionCastBase, _UnionCastCls, ObjVal)
return None
CN: str
ST: Any
@@ -988,15 +1004,78 @@ class ExprAttrHandle(BaseHandle):
spec_val: Any = self._try_access_opaque_generic_member(Gen, ObjVal, st if isinstance(st, ir.IdentifiedStructType) else pointee, Node, CN)
if spec_val is not None:
return spec_val
self._FailFastAttrCallBase("结构体不透明(stub / .vpi 未加载出字段)",
Node, Gen, _IsUnionCastBase, _UnionCastCls, ObjVal)
return None
bitfield_offsets: Any = Gen.class_member_bitoffsets.get(CN, {})
if Node.attr in bitfield_offsets:
return self._load_bitfield_member(ObjVal, CN, Node.attr)
offset: int | None = Gen._get_member_offset(Node.attr, CN)
if offset is None:
self._FailFastAttrCallBase("成员偏移取不到(class_members / 继承链均未命中)",
Node, Gen, _IsUnionCastBase, _UnionCastCls, ObjVal)
return None
return self._gep_and_Load_member(Gen, ObjVal, offset, Node.attr, CN, st)
def _FailFastAttrCallBase(self, Stage: str, Node: ast.Attribute, Gen: Any,
IsUnionCastBase: bool, UnionCls: str | None,
ObjVal: Any) -> None:
"""`(T | t.CPtr)(x).attr` 属性访问失败时的 fail-fast(非该形态时静默返回,行为不变)。
`(T | t.CPtr)(x)` 只可能来自指针强转(parser 把 `(T*)x` / `(T*)(x)` / `T*(x)`
统一产成该 Call),其属性访问必须有值产出。此前一律 `return None`,调用方只能
报"赋值表达式 'value'(Attribute 形态)编译期求值为空",把"类名没被当成类 /
结构体不透明 / 成员偏移取不到"三种完全不同的根因混成一条报文
(现场:TransPyV ExprGenBase.vp:360 `v = (ast.Expr*(node)).value`)。
此处逐项列出判定依据,直接指认失败环节。
"""
if not IsUnionCastBase:
return
from lib.core.LLVMCG.FuncGen import FailFastError
_Parts: list[str] = [
f"属性 '{Node.attr}' 的基座是强转调用 '{self._ExprText(Node.value)}',"
f"失败环节:{Stage}",
f"联合基类名={UnionCls!r}"
f";短名 in structs={'是' if (UnionCls and UnionCls in Gen.structs) else '否'}"
f";structs 同短名键="
f"{[k for k in Gen.structs if UnionCls and k.split('.')[-1] == UnionCls][:6]}"
f";_struct_sha1_map={getattr(Gen, '_struct_sha1_map', {}).get(UnionCls)!r}"
f";class_sha1_map={getattr(Gen, 'class_sha1_map', {}).get(UnionCls)!r}",
]
if ObjVal is None:
_Parts.append("基座求值结果=None")
else:
_Parts.append(f"基座求值类型={ObjVal.type!r}")
if isinstance(ObjVal.type, ir.PointerType):
_Pt: Any = ObjVal.type.pointee
_Parts.append(f"被指类型={_Pt!r};elements="
f"{getattr(_Pt, 'elements', 'n/a')}")
_Found: Any = Gen.find_struct_by_pointee(_Pt)
_Parts.append(
f"find_struct_by_pointee={'命中 ' + str(_Found[0]) if _Found else '未命中'}")
if _Found:
_CN: str = _Found[0]
_Parts.append(
f"class_members[{_CN!r}]="
f"{[m[0] for m in Gen.class_members.get(_CN, [])][:12]}"
f"({len(Gen.class_members.get(_CN, []))} 项)"
f";成员 '{Node.attr}' 偏移={self._SafeOffset(Gen, Node.attr, _CN)!r}")
if UnionCls:
_Parts.append(
f"class_members[{UnionCls!r}]="
f"{[m[0] for m in Gen.class_members.get(UnionCls, [])][:12]}"
f"({len(Gen.class_members.get(UnionCls, []))} 项)"
f";class_parent={getattr(Gen, 'class_parent', {}).get(UnionCls)!r}")
raise FailFastError(";".join(_Parts))
@staticmethod
def _SafeOffset(Gen: Any, Attr: str, ClassName: str) -> Any:
"""取证用成员偏移查询:本身绝不能再抛异常掩盖原始故障。"""
try:
return Gen._get_member_offset(Attr, ClassName)
except Exception as _E:
return f"<查询异常 {type(_E).__name__}: {_E}>"
def _extract_union_base_class(self, NodeFunc: ast.BinOp) -> str | None:
"""从 (X | t.CPtr) 联合 BinOp 提取第一个非指针/非修饰符的类名
+80 -6
View File
@@ -2065,14 +2065,88 @@ class ExprCallHandle(BaseHandle):
)
target_type_str = self._ExtractTypeStrFromUnionBinOp(Node.func)
if not target_type_str:
# Fallback 也无法解析:记录详细诊断信息后返回 None
_vlog().warning(
f"_HandleTypeUnionCastLlvm: fallback 解析失败,无法确定目标类型 "
f"(Node.func={ast.dump(Node.func)})"
)
return None
# fail-fast(原为 _vlog().warning + return None):
# `(T | t.CPtr)(x)` 只可能来自指针强转——parser 把 `(T*)x` / `(T*)(x)` /
# `T*(x)`(含 `**`)统一产成 func = `T | t.CPtr` 的 Call(见
# vast/ExprParser._ConsumePtrSuffixCall、vast_c.c ConsumePtrSuffixCall)。
# 该形态必须有值产出;返回 None 会让调用方把整条表达式当"编译期求值为空"
# 丢掉(现场:TransPyV ExprGenBase.vp:360 `v = (ast.Expr*(node)).value`
# → TPC 报"赋值表达式 'value'(Attribute 形态)编译期求值为空",而同函数
# 前三条 `(ast.Assign/AnnAssign/AugAssign*(node)).value` 同款写法正常)。
# 故把"哪一步没解析出目标类型"逐项摊开,而不是静默丢弃整条表达式。
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(self._DescribeUnionCastFailure(
Node, Gen, type_info, MergeException, target_type_str))
return self._HandleTypeCastLlvm(Node.args[0], target_type_str)
def _DescribeUnionCastFailure(self, Node: ast.Call, Gen: Any, TypeInfo: Any,
MergeException: Any, TargetStr: Any) -> str:
"""联合类型强转目标类型解析失败时的判定依据(逐项列出,供 fail-fast 报文)。
只做取证,不做类型解析:把 MergeTypes / _ExtractTypeStrFromUnionBinOp 的
返回值、异常,以及联合中每个操作数在符号表与类登记表(Gen.structs /
_struct_sha1_map / class_sha1_map)里的命中情况全部列出,使"是基座类名
没被当成类"还是"CPtr 标记侧解析失败"一眼可分。
"""
_Flat: list = []
def _Collect(n: ast.AST) -> None:
if isinstance(n, ast.BinOp) and isinstance(n.op, ast.BitOr):
_Collect(n.left)
_Collect(n.right)
else:
_Flat.append(n)
_Collect(Node.func)
_Operands: list[str] = []
for _op in _Flat:
_short: str = ''
_full: str = ''
if isinstance(_op, ast.Attribute):
_short = _op.attr
_cur: ast.AST = _op.value
while isinstance(_cur, ast.Attribute):
_cur = _cur.value
_full = f"{_cur.id}.{_short}" if isinstance(_cur, ast.Name) else _short
elif isinstance(_op, ast.Name):
_short = _op.id
_full = _short
else:
_Operands.append(f"<{type(_op).__name__}>(非类型名形态)")
continue
_sym: Any = None
try:
_sym = self.Trans.SymbolTable.lookup(_short)
except Exception:
_sym = None
_verdict: str = '符号表未命中'
if _sym is not None:
_verdict = (f"IsStruct={getattr(_sym, 'IsStruct', None)}"
f" IsRenum={getattr(_sym, 'IsRenum', None)}"
f" IsEnum={getattr(_sym, 'IsEnum', None)}"
f" IsEnumMember={getattr(_sym, 'IsEnumMember', None)}"
f" IsTypedef={getattr(_sym, 'IsTypedef', None)}"
f" Name={getattr(_sym, 'Name', None)!r}")
_Operands.append(
f"{_full}({_verdict};in structs={_short in Gen.structs}"
f";structs 同短名键={[k for k in Gen.structs if k.split('.')[-1] == _short][:4]}"
f";_struct_sha1_map={getattr(Gen, '_struct_sha1_map', {}).get(_short)!r}"
f";class_sha1_map={getattr(Gen, 'class_sha1_map', {}).get(_short)!r})")
_ToStr: str = '<非 CTypeInfo>'
try:
if isinstance(TypeInfo, CTypeInfo):
_ToStr = repr(TypeInfo.ToString())
elif TypeInfo is None:
_ToStr = 'None'
except Exception as _E:
_ToStr = f"<ToString 异常 {type(_E).__name__}: {_E}>"
return (f"联合类型强转 '({ast.unparse(Node.func)})(...)' 无法确定目标类型,"
f"拒绝静默返回 None(会让整条表达式被判为『编译期求值为空』而丢弃):"
f"MergeTypes 返回={type(TypeInfo).__name__}(ToString()={_ToStr});"
f"_ExtractTypeStrFromUnionBinOp={TargetStr!r};"
f"MergeTypes 异常={MergeException!r};操作数判据:" + ";".join(_Operands))
def _ExtractTypeStrFromUnionBinOp(self, NodeFunc: ast.AST) -> str | None:
"""Fallback:从类型联合 BinOp 手动提取类型字符串。