fix(tpc): 属性读取返回 None 时登记「确切原因」,5 个 return None 点不再同义

类别:a(编译器缺陷 —— fail-fast 报文缺根因区分,5 个不同故障同报"求值为空")

现象:`self.Gen.ExprGen.translate_value(builder, pool, mod, ie.orelse, ...)`
(ExprGenOps.vp:97)报「调用实参编译期求值为空」,而同函数同对象的 `ie.test`
(:43)、`ie.body`(:78)求值正常。三个字段在 `IfExp` 布局里是**连续三槽**
(stub: %"…IfExp" = { i8*, AST*, MemBuddy*, i32×4, list*, AST*(test),
AST*(body), AST*(orelse) },共 11 元素),只有最后那个失败 ⇒ 必然是"偏移越出
body 元素数"这一类,但报文只说"求值为空",无法与"类名未解析 / 基座求值为空 /
结构体未匹配 / stub 无 body"区分。

改动(纯取证,不改变任何判定与 IR):
1. HandlesBase 新增 `_NoteAttrNone(Gen, ClassName, AttrName, Reason)`:把
   `(类名, 字段名, 原因)` 记到 `Gen._LastAttrNone`。
2. HandlesExprAttr 的 5 个 return None 点全部登记:
   - `_gep_and_Load_member`:偏移为 None;**偏移越界**(附 offset / body 元素数 /
     st 版元素数 / 成员表项数)。
   - `_handle_attr_name`:类名未解析;基座自身求值为空;结构体未匹配(附 ObjVal
     类型);stub 无 body;槽位越界(附 offset / pointee 元素数 / st 元素数)。
3. `_DescribeAttrBase` 追加三项事实:`struct body 元素数`、`字段 X 计算偏移`
   (`_get_member_offset`,异常则报异常类型)、以及与被报节点 (类名, 字段名)
   一致时回显 `读取侧原因=...`(不一致不回显,避免嵌套读取的旧记录冒充)。

依据:字段名在成员表里能找到却没读到值,只可能是"偏移 ≥ body 元素数"
(`_get_member_offset` 找不到字段时已 fail-fast,不会返回 None);因此
`偏移/body元素数/成员表项数` 三个数字是判定该情况的唯一依据,必须一次报全。
This commit is contained in:
TermiNexus committed 2026-09-29 17:46:58 +08:00
1 parent 0392ccdea0
commit 935a5b372b
2 files changed
+75 -2

No files matched your search

+48 -2
View File
@@ -555,6 +555,21 @@ class BaseHandle:
return f"Subscript({BaseHandle._ExprShape(Node.value, Depth + 1)})"
return type(Node).__name__
def _NoteAttrNone(self, Gen: Any, ClassName: str | None, AttrName: str, Reason: str) -> None:
"""记录「属性读取返回 None」的**确切原因**(供 fail-fast 报文取证,纯记账)。
`x.attr` 求值为 None 在 `ExprAttrHandle` 里有 5 个不同来源(类名未解析 /
基座求值为空 / 结构体未匹配 / stub 无 body / 槽位越界),各自根因完全不同;
下游(_RequireArgValue / AnnAssign)只看到"求值为空",无法区分 ⇒ 报错点与
真因相隔数层。此处把 `(类名, 字段名, 原因)` 记到 Gen,由 `_DescribeAttrBase`
在报文里回显;回显时要求 `(类名, 字段名)` 与被报节点一致,避免嵌套读取留下的
旧记录误导(不改变任何判定与 IR)。
"""
try:
Gen._LastAttrNone = (ClassName or '', AttrName or '', Reason)
except Exception: # noqa: BLE001
pass
def _DescribeAttrBase(self, Gen: Any, Node: ast.Attribute) -> str:
"""属性读返回 None 时的**基址事实**(供 fail-fast 报文取证,不改变任何判定)。
@@ -568,6 +583,7 @@ class BaseHandle:
"""
_base: ast.AST = Node.value
_parts: list[str] = [f"基座形态={BaseHandle._ExprShape(_base)}"]
_vc_head: Any = None
if isinstance(_base, ast.Name):
_bn: str = _base.id
_vc: str | None = None
@@ -579,7 +595,8 @@ class BaseHandle:
_alloc = (getattr(Gen, 'variables', None) or {}).get(_bn)
except Exception: # noqa: BLE001
pass
_parts.append(f"'{_bn}' 的 var_struct_class={_vc!r}")
_parts.append(f"'{_bn}' 的 var_struct_class={_vc!r}")
_vc_head = _vc
_parts.append(f"alloca 类型={getattr(_alloc, 'type', None)}")
_parts.append("该名在 Gen.structs="
+ ('是' if _vc and _vc in Gen.structs else ('否' if _vc else '—')))
@@ -588,6 +605,20 @@ class BaseHandle:
if _vc and _vc in Gen.class_members else '无该键'))
if _vc and _vc in Gen.class_members:
_parts.append(f"字段名单={[m[0] for m in Gen.class_members[_vc]][:12]}")
# struct body 元素数 与 该字段的**计算偏移**:字段名在成员表里能找到、
# 却没读到值,只可能是"偏移 ≥ body 元素数"(`_gep_and_Load_member` 的
# 越界 return None)。这两个数字是判定该情况的唯一依据,必须一并报出。
_st_attr: Any = Gen.structs.get(_vc) if _vc else None
if _st_attr is not None:
_nelem: Any = (len(_st_attr.elements)
if getattr(_st_attr, 'elements', None) is not None else 'opaque')
_parts.append(f"struct body 元素数={_nelem}")
if _vc:
try:
_off: Any = Gen._get_member_offset(Node.attr, _vc)
except Exception as _e: # noqa: BLE001
_off = f"<取偏移抛 {type(_e).__name__}>"
_parts.append(f"字段 '{Node.attr}' 计算偏移={_off}")
elif (isinstance(_base, ast.Call) and isinstance(_base.func, ast.BinOp)
and isinstance(_base.func.op, ast.BitOr)):
_cls: str | None = None
@@ -595,13 +626,28 @@ class BaseHandle:
_cls = self.Trans.ExprAttrHandle._extract_union_base_class(_base.func)
except Exception: # noqa: BLE001
_cls = None
_parts.append(f"联合基类名={_cls!r}")
_parts.append(f"联合基类名={_cls!r}")
_vc_head = _cls
_parts.append(f"短名 in structs={'是' if _cls and _cls in Gen.structs else '否'}")
_parts.append("class_members="
+ (f"{len(Gen.class_members.get(_cls) or [])} 个字段"
if _cls in Gen.class_members else '无该键'))
_parts.append("注: 强转基座形态本应走 _FailFastAttrCallBase 详细报文,"
"此处仍是本条 ⇒ 那条 fail-fast 未触发")
# 回显读取侧的**确切失败原因**(`ExprAttrHandle` 各 return None 点登记)。
# 仅在 (类名, 字段名) 与被报节点一致时输出:嵌套读取留下的旧记录不得冒充本节点。
try:
_rec: Any = getattr(Gen, '_LastAttrNone', None)
except Exception: # noqa: BLE001
_rec = None
if isinstance(_rec, tuple) and len(_rec) >= 3:
_rec_cls, _rec_attr, _rec_reason = _rec[0], _rec[1], _rec[2]
_short = lambda s: (s.split('.')[-1] if s else s) # noqa: E731
_same: bool = bool(_rec_attr) and _rec_attr == Node.attr and (
not _rec_cls or not _vc_head or _rec_cls == _vc_head
or _short(_rec_cls) == _short(_vc_head))
if _same:
_parts.append(f"读取侧原因={_rec_reason}")
return ";".join(_parts)
@staticmethod
+27
View File
@@ -24,6 +24,8 @@ class ExprAttrHandle(BaseHandle):
def _gep_and_Load_member(self, Gen: LlvmCodeGenerator, ObjVal: ir.Value, offset: int | None, AttrName: str, ClassName: str, ST: ir.Type | None = None) -> ir.Value | None:
"""从结构体指针通过 GEP 取成员,处理 array/struct pointer/class_members 特殊情况"""
if offset is None:
self._NoteAttrNone(Gen, ClassName, AttrName,
f"偏移为 None(字段 '{AttrName}' 未进成员表)")
return None
if isinstance(ObjVal.type, ir.PointerType):
pointee: Any = ObjVal.type.pointee
@@ -32,6 +34,15 @@ class ExprAttrHandle(BaseHandle):
if ST is not None and isinstance(ST, ir.IdentifiedStructType) and ST.elements is not None and offset < len(ST.elements):
ObjVal = Gen.builder.bitcast(ObjVal, ir.PointerType(ST), name=f"cast_gep_{ClassName}")
else:
_st_len: Any = (len(ST.elements)
if isinstance(ST, ir.IdentifiedStructType) and ST.elements is not None
else 'None/opaque')
self._NoteAttrNone(
Gen, ClassName, AttrName,
f"偏移越界:字段 '{AttrName}' 需槽位 {offset},"
f"但 body 只有 {len(pointee.elements)} 个元素,"
f"stub 版结构体(st)也仅 {_st_len} 个 ⇒ 布局与成员表不同源"
f"(成员表 {len(Gen.class_members.get(ClassName, []))} 项)")
return None
MemberPtr: Any = Gen.builder.gep(ObjVal, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), offset)], name=AttrName)
if isinstance(MemberPtr.type, ir.PointerType) and isinstance(MemberPtr.type.pointee, ir.ArrayType):
@@ -505,6 +516,9 @@ class ExprAttrHandle(BaseHandle):
if getattr(Gen, 'global_struct_class', None) is not None:
Gen.global_struct_class[VarName] = _recovered
if not ClassName:
self._NoteAttrNone(Gen, None, AttrName,
f"类名未解析('{VarName}' 的 var_struct_class 为空且无法由 "
f"alloca/值类型恢复)")
return None
# 检查 property getter
@@ -569,6 +583,8 @@ class ExprAttrHandle(BaseHandle):
# 普通结构体成员访问
ObjVal = self.HandleExprLlvm(Node.value)
if not ObjVal:
self._NoteAttrNone(Gen, ClassName, AttrName,
f"基座 '{VarName}' 自身求值为空(类名={ClassName!r})")
return None
# char* → struct* cast
if self._is_char_pointer(ObjVal):
@@ -661,12 +677,18 @@ class ExprAttrHandle(BaseHandle):
name=f"cast_slot_{ClassName}")
struct_match = True
if not struct_match:
self._NoteAttrNone(
Gen, ClassName, AttrName,
f"结构体未匹配(ObjVal 类型={ObjVal.type},类名={ClassName!r};"
f"pointee 既非该类 struct,静态类名也未能 bitcast 兜底)")
return None
st: Any
ok: bool
st, ok = self._try_Load_struct_from_stub(Gen, ClassName)
if not ok:
self._NoteAttrNone(Gen, ClassName, AttrName,
f"stub 无 body(Gen.structs[{ClassName!r}] 存在但 elements 为空/opaque)")
return None
# bitfield 处理
@@ -702,6 +724,11 @@ class ExprAttrHandle(BaseHandle):
if st is not None and isinstance(st, ir.IdentifiedStructType) and st.elements is not None and offset < len(st.elements):
ObjVal = Gen.builder.bitcast(ObjVal, ir.PointerType(st), name=f"cast_load_{ClassName}")
else:
self._NoteAttrNone(
Gen, ClassName, AttrName,
f"槽位越界(_handle_attr_name 侧):字段 '{AttrName}' 需槽位 "
f"{offset},但 ObjVal 的 pointee 只有 {len(actual_elems)} 个元素,"
f"st 版结构体也不够({len(st.elements) if isinstance(st, ir.IdentifiedStructType) and st.elements is not None else 'None/opaque'})")
return None
result = self._gep_and_Load_member(Gen, ObjVal, offset, AttrName, ClassName, st)
return result