类别:a(编译器缺陷)
现象(早前现场,本轮定位):
TypeError: [fast-fail] len(mcc_pre.children) 分派 'list[….AST*].__len__' 失败:
类='MatchCase' 偏移=6 接收者类型=i32
判据(磁盘实证):
· MatchCase 的 body 在 temp 与**全部** output stub 中都是 **10 槽**
(vtable + AST 的 7 字段 + pattern/guard)⇒ `children` 的正确偏移 = base(1) + 表内下标(6) = **7**。
· 观测到 6 ⇒ base 被算成 0 ⇒ `_member_base` 的 `_Delta = 槽数 - 表长` 为 0,
即**表长 = 10**(9 个不同字段 + 1 条重名条目),而不是 9。
· 重名条目是**产物**:源码层面的字段名遮蔽已由 HandlesClassDef 的
"字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 表里的重名只可能来自"展平残留
(半截祖先块,已由 2e7bac18 在插入点剥除)/ 同一字段被两条收集路径各登记一次"。
修复(与 `_get_member_offset` 配对路径已有的"重名条目不占槽位"同口径,全线统一):
· `_member_base` 的长度校准改用**去重名数**(`_Delta` 不再被重名条目冲掉 ⇒ base 正确);
· 新增 `_MemberSlotIndex(Members, FieldName)`:取"首个同名的**去重**下标"
(重名条目不计槽位),并把 `_get_member_offset` 中**全部 12 处** "base + 表内下标"
站点改为调用它(含继承链回退、REnum 变体搜索、stub 重载后、_PreRegisterClassMembers
之后等分支);"最后回退"里的 `_num_members_final` 同步改为去重计数。
· 对无重名的表,取值与改前**逐字相同**(去重下标 == 原始下标)。
核验:py_compile + compileall 全 lib 通过;离线冒烟(真实方法、未绑定实例调用):
含 1 条重复 'children' 的表上 `children -> 6`、`guard -> 8`、缺名 -> -1;
去重名数 9 ⇒ `_Delta = 10 - 9 = 1` ⇒ base=1 ⇒ `children` 偏移 7(与 body 10 槽一致)。
未放宽任何 fail-fast。
1219 lines
83 KiB
Python
1219 lines
83 KiB
Python
from __future__ import annotations
|
||
from typing import Any
|
||
import vast as ast
|
||
import os
|
||
import re
|
||
import sys
|
||
import llvmlite.ir as ir
|
||
from lib.core.Handles.HandlesBase import CTypeInfo
|
||
from lib.core.VLogger import get_logger as _vlog
|
||
from lib.constants.config import mode as _config_mode
|
||
|
||
|
||
class FailFastError(Exception):
|
||
"""专用 fail-fast 异常:编译器主动抛出的编译错误(不可隐式转换/不可恢复)。
|
||
|
||
刻意与 llvmlite 自身抛出的 TypeError/ValueError 区分开 —— 后者属于"此处可以尝试
|
||
简化回退"的类型调整失败,本异常则是必须上抛、禁止被兜底 `except` 吞掉的信号
|
||
(例如 `_adjust_args` 的 L120/L181 两条 fail-fast)。
|
||
"""
|
||
|
||
|
||
class MemberLookupFailFast(KeyError):
|
||
"""成员偏移查找失败的 fail-fast 异常(**继承 KeyError** 以保持既有兜底点可用)。
|
||
|
||
为什么继承 KeyError:本仓有两处 `except KeyError` 把 `_get_member_offset` 的
|
||
失败当作"该字段类型未知"继续回查(HandlesExprAttr 的字段类型三级回退),改成
|
||
其它异常类型会把这些兜底点击穿。
|
||
|
||
为什么重写 `__str__`:KeyError 的默认 `__str__` 是 `repr(消息)` —— 会在报文两端
|
||
加引号、并把换行/制表转义成字面 `\\n`,多行 fail-fast 报文(含"源代码 (文件, 行号):
|
||
行号 | 源码")因此彻底不可读。这里原样返回裸消息,与 FailFastError 的观感一致。
|
||
"""
|
||
|
||
def __str__(self) -> str:
|
||
if self.args and isinstance(self.args[0], str):
|
||
return self.args[0]
|
||
return super().__str__()
|
||
|
||
|
||
class FuncGenMixin:
|
||
|
||
def setup_from_symbol_table(self, SymbolTable: Any) -> None:
|
||
self.SymbolTable: Any = SymbolTable
|
||
for name, info in SymbolTable.items():
|
||
if isinstance(info, CTypeInfo):
|
||
# CTypeInfo 对象:struct/union 由 StructGen 处理,此处只处理 dict 格式
|
||
continue
|
||
if not isinstance(info, dict):
|
||
if hasattr(info, 'get'):
|
||
info = dict(info) if hasattr(info, '__iter__') else {}
|
||
else:
|
||
continue
|
||
TypeKind: str = info.get('type', '')
|
||
if TypeKind == 'struct':
|
||
ClassName: str = name
|
||
self.class_methods[ClassName] = []
|
||
self.class_members[ClassName] = []
|
||
members: dict[str, Any] = info.get('members', {})
|
||
if members:
|
||
for MemberName, MemberInfo in members.items():
|
||
if isinstance(MemberInfo, dict):
|
||
MemberType: ir.Type = self._type_str_to_llvm(MemberInfo.get('type', 'int'), MemberInfo.get('IsPtr', False))
|
||
self.class_members[ClassName].append((MemberName, MemberType))
|
||
elif isinstance(MemberInfo, CTypeInfo):
|
||
MemberType: ir.Type = self._ctype_to_llvm(MemberInfo)
|
||
if MemberType is None:
|
||
MemberType = self._type_str_to_llvm(MemberInfo.ToString(), MemberInfo.IsPtr)
|
||
self.class_members[ClassName].append((MemberName, MemberType))
|
||
self._generate_structs()
|
||
self._create_Vtable_globals()
|
||
|
||
def register_method(self, ClassName: str, MethodName: str, IsVtableSlot: bool = True) -> None:
|
||
"""登记类方法。
|
||
|
||
IsVtableSlot:该方法是否占用虚表槽(class_methods 即 vtable 槽序唯一真源)。
|
||
默认 True 与既有语义逐字一致;False 用于方法级 `novirtual def`(⇒ 方法装饰器
|
||
@t.NoVTable)——此时只保证类的方法容器存在,**不**把它排进槽序,使 TPC 与
|
||
TPV(ClassGenBase._should_method_be_virtual 排除该项)的虚表布局逐槽相同。
|
||
"""
|
||
if ClassName not in self.class_methods:
|
||
self.class_methods[ClassName] = []
|
||
if ClassName not in self.class_members:
|
||
self.class_members[ClassName] = []
|
||
self._generate_structs()
|
||
self._create_Vtable_globals()
|
||
# 构造函数(__new__/__init__/__before_init__)不放入 vtable,
|
||
# 避免子类与基类 vtable 大小不一致导致虚分派索引偏移
|
||
_short_name: str = MethodName.split('.')[-1] if '.' in MethodName else MethodName
|
||
if _short_name in ('__new__', '__init__', '__before_init__'):
|
||
return
|
||
if not IsVtableSlot:
|
||
return
|
||
if MethodName not in self.class_methods[ClassName]:
|
||
self.class_methods[ClassName].append(MethodName)
|
||
|
||
def _apply_auto_addr(self, func_name: str, args: list[ir.Value]) -> list[ir.Value]:
|
||
"""对函数参数应用自动取地址逻辑(t.CNeedPtr / t.CAutoPtr)。
|
||
|
||
在调用前对标记为 auto-addr 的参数自动取地址:
|
||
- 'need' (t.CNeedPtr): 仅对非指针值取地址(alloca + store + get pointer)
|
||
- 'always' (t.CAutoPtr): 无条件取地址
|
||
"""
|
||
modes: list[str | None] | None = self._auto_addr_params.get(func_name)
|
||
if not modes:
|
||
return args
|
||
new_args: list[ir.Value] = list(args)
|
||
for i, mode in enumerate(modes):
|
||
if i >= len(new_args) or mode is None:
|
||
continue
|
||
arg: ir.Value = new_args[i]
|
||
is_ptr: bool = isinstance(arg.type, ir.PointerType)
|
||
if mode == 'always' or (mode == 'need' and not is_ptr):
|
||
# alloca + store + get pointer
|
||
slot: ir.Value = self.builder.alloca(arg.type)
|
||
self.builder.store(arg, slot)
|
||
new_args[i] = slot
|
||
return new_args
|
||
|
||
def _adjust_args(self, args: list[ir.Value], func: ir.Function) -> list[ir.Value]:
|
||
adjusted: list[ir.Value] = []
|
||
for i, (arg, param) in enumerate(zip(args, func.args)):
|
||
if arg.type != param.type:
|
||
if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
|
||
if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name):
|
||
# C 语言中数组参数退化为指针,不做 load 而是退化为元素指针
|
||
if isinstance(param.type, ir.ArrayType):
|
||
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||
elem_ptr: ir.Value = self.builder.gep(arg, [zero, zero], name=f"array_decay_arg_{i}")
|
||
adjusted.append(elem_ptr)
|
||
else:
|
||
adjusted.append(self._load(arg, name=f"Load_arg_{i}"))
|
||
continue
|
||
try:
|
||
if isinstance(arg.type, ir.IntType) and isinstance(param.type, ir.PointerType):
|
||
if isinstance(param.type.pointee, ir.IntType) and param.type.pointee.width == 8 and arg.type.width == 8:
|
||
# 当参数是 i8(字符值)而函数期望 i8*(字符串指针)时
|
||
# 为字符值分配内存,创建 null-terminated 字符串
|
||
tmp: ir.AllocaInstr = self._alloca(ir.ArrayType(ir.IntType(8), 2), name=f"char2str_arg_{i}")
|
||
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||
char_ptr: ir.Value = self.builder.gep(tmp, [zero, zero], name=f"char2str_ptr_{i}")
|
||
self._store(arg, char_ptr)
|
||
null_ptr: ir.Value = self.builder.gep(tmp, [zero, ir.Constant(ir.IntType(32), 1)], name=f"char2str_null_{i}")
|
||
self._store(ir.Constant(ir.IntType(8), 0), null_ptr)
|
||
adjusted.append(char_ptr)
|
||
else:
|
||
if isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType):
|
||
# 当参数是指针而函数期望整数时,使用 ptrtoint
|
||
adjusted.append(self.builder.ptrtoint(arg, param.type, name=f"ptrtoint_arg_{i}"))
|
||
else:
|
||
if arg.type.width < 32:
|
||
arg = self.builder.zext(arg, ir.IntType(32), name=f"zext_arg_{i}")
|
||
adjusted.append(self.builder.inttoptr(arg, param.type, name=f"inttoptr_arg_{i}"))
|
||
elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType):
|
||
# fail-fast:指针→整数隐式转换(跨类型层级,有损)禁止,
|
||
# 须显式转换(用户隐式转换规则:同层指针可转,指针→整数不允许)。
|
||
# 诊断增强(只在即将终止时拼串,零行为影响):把 callee 名、
|
||
# 实参/形参个数与**逐项类型**一并报出——只给单个类型对无法判断
|
||
# 「形参表整体错位一位」(如 self/pool 约定不一致)这类根因。
|
||
_diag: str = "; ".join(
|
||
f"[{k}] arg={a.type} param={p.type}"
|
||
for k, (a, p) in enumerate(zip(args, func.args)))
|
||
raise FailFastError(
|
||
f"调用参数类型不匹配: 指针类型 {arg.type} 不能隐式转换为"
|
||
f"整数参数 {param.type};如需转换请显式 cast"
|
||
f"(callee={func.name} 实参{len(args)}个/形参{len(func.args)}个;"
|
||
f"逐项: {_diag})")
|
||
elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.PointerType):
|
||
if isinstance(arg.type.pointee, ir.PointerType) and isinstance(param.type.pointee, ir.IntType):
|
||
Loaded: ir.Value = self._load(arg, name=f"Load_arg_{i}")
|
||
adjusted.append(Loaded)
|
||
elif isinstance(arg.type.pointee, ir.ArrayType) and isinstance(param.type.pointee, (ir.IntType, ir.FloatType, ir.DoubleType)):
|
||
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||
elem_ptr: ir.Value = self.builder.gep(arg, [zero, zero], name=f"array_decay_arg_{i}")
|
||
if elem_ptr.type != param.type:
|
||
adjusted.append(self.builder.bitcast(elem_ptr, param.type, name=f"cast_arg_{i}"))
|
||
else:
|
||
adjusted.append(elem_ptr)
|
||
elif isinstance(arg.type.pointee, ir.ArrayType) and isinstance(param.type.pointee, ir.PointerType):
|
||
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||
elem_ptr: ir.Value = self.builder.gep(arg, [zero, zero], name=f"array_decay_arg_{i}")
|
||
if elem_ptr.type != param.type:
|
||
adjusted.append(self.builder.bitcast(elem_ptr, param.type, name=f"cast_arg_{i}"))
|
||
else:
|
||
adjusted.append(elem_ptr)
|
||
else:
|
||
# 新增指针隐式转换规则①/②:通用指针通配放行;非通用
|
||
# T*→U* 仅在无损时放行,非基本/有损/层数不同 fail-fast。
|
||
Allowed: bool
|
||
Reason: str
|
||
Allowed, Reason = self._ClassifyPtrImplicitConvert(arg.type, param.type)
|
||
if not Allowed:
|
||
raise FailFastError(
|
||
f"指针隐式转换已禁止({Reason}):调用 {func.name} 第 {i} 个实参"
|
||
f" {arg.type} 不能隐式转为形参 {param.type};如需转换请显式 cast"
|
||
f"(如 t.CPtr(...)){self._DescribeValueNode()}")
|
||
adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}"))
|
||
else:
|
||
if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
|
||
if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name):
|
||
adjusted.append(self._load(arg, name=f"Load_arg_{i}"))
|
||
elif isinstance(arg.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
|
||
tmp: ir.AllocaInstr = self._alloca(param.type, name=f"temp_arg_{i}")
|
||
Loaded: ir.Value = self.builder.bitcast(arg, ir.PointerType(param.type), name=f"cast_arg_{i}")
|
||
Loaded_val: ir.Value = self._load(Loaded, name=f"Load_arg_{i}")
|
||
self._store(Loaded_val, tmp)
|
||
adjusted.append(self._load(tmp, name=f"reLoad_arg_{i}"))
|
||
else:
|
||
tmp: ir.AllocaInstr = self._alloca(param.type, name=f"temp_arg_{i}")
|
||
cast_ptr: ir.Value = self.builder.bitcast(arg, ir.PointerType(param.type), name=f"cast_arg_{i}")
|
||
Loaded_val: ir.Value = self._load(cast_ptr, name=f"Load_arg_{i}")
|
||
self._store(Loaded_val, tmp)
|
||
adjusted.append(self._load(tmp, name=f"reLoad_arg_{i}"))
|
||
elif isinstance(arg.type, ir.PointerType) and arg.type.pointee == param.type:
|
||
adjusted.append(self._load(arg, name=f"Load_arg_{i}"))
|
||
elif isinstance(param.type, ir.PointerType) and param.type.pointee == arg.type:
|
||
tmp: ir.AllocaInstr = self._alloca(arg.type, name=f"temp_arg_{i}")
|
||
self._store(arg, tmp)
|
||
adjusted.append(tmp)
|
||
elif isinstance(arg.type, ir.IntType) and isinstance(param.type, ir.IntType):
|
||
if arg.type.width < param.type.width:
|
||
# 整数扩展:使用 sext(符号扩展)以正确处理负数
|
||
adjusted.append(self.builder.sext(arg, param.type, name=f"sext_arg_{i}"))
|
||
else:
|
||
adjusted.append(self.builder.trunc(arg, param.type, name=f"trunc_arg_{i}"))
|
||
elif isinstance(arg.type, (ir.FloatType, ir.DoubleType)) and isinstance(param.type, (ir.FloatType, ir.DoubleType)):
|
||
if isinstance(arg.type, ir.DoubleType) and isinstance(param.type, ir.FloatType):
|
||
adjusted.append(self.builder.fptrunc(arg, param.type, name=f"fptrunc_arg_{i}"))
|
||
elif isinstance(arg.type, ir.FloatType) and isinstance(param.type, ir.DoubleType):
|
||
adjusted.append(self.builder.fpext(arg, param.type, name=f"fpext_arg_{i}"))
|
||
else:
|
||
adjusted.append(arg)
|
||
elif isinstance(arg.type, (ir.FloatType, ir.DoubleType)) and isinstance(param.type, ir.IntType):
|
||
# fail-fast:浮点→整数隐式转换(有损)禁止,须显式转换
|
||
# (用户隐式转换规则:浮点→整数不允许)。
|
||
raise FailFastError(
|
||
f"调用参数类型不匹配: 浮点类型 {arg.type} 不能隐式转换为"
|
||
f"整数参数 {param.type};如需转换请显式 cast")
|
||
elif isinstance(arg.type, ir.IntType) and isinstance(param.type, (ir.FloatType, ir.DoubleType)):
|
||
adjusted.append(self.builder.sitofp(arg, param.type, name=f"sitofp_arg_{i}"))
|
||
elif isinstance(arg.type, ir.IntType) and isinstance(param.type, ir.PointerType):
|
||
# 整数转指针:inttoptr
|
||
if arg.type.width < 64:
|
||
i64_val: ir.Value = self.builder.sext(arg, ir.IntType(64), name=f"sext_arg_{i}")
|
||
adjusted.append(self.builder.inttoptr(i64_val, param.type, name=f"inttoptr_arg_{i}"))
|
||
else:
|
||
adjusted.append(self.builder.inttoptr(arg, param.type, name=f"inttoptr_arg_{i}"))
|
||
elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType):
|
||
# 指针转整数:ptrtoint
|
||
i64_val: ir.Value = self.builder.ptrtoint(arg, ir.IntType(64), name=f"ptrtoint_arg_{i}")
|
||
if i64_val.type.width > param.type.width:
|
||
adjusted.append(self.builder.trunc(i64_val, param.type, name=f"trunc_arg_{i}"))
|
||
elif i64_val.type.width < param.type.width:
|
||
adjusted.append(self.builder.sext(i64_val, param.type, name=f"sext_arg_{i}"))
|
||
else:
|
||
adjusted.append(i64_val)
|
||
else:
|
||
adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}"))
|
||
# 只收口 llvmlite 自身"可回退"的类型调整失败(builder/IR 构造抛出的
|
||
# TypeError/ValueError);FailFastError 与其余异常一律上抛,
|
||
# 避免编译器主动抛出的真因被伪装成 llvmlite 的下游报文。
|
||
except (TypeError, ValueError): # 参数类型调整失败时使用简化回退逻辑
|
||
if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
|
||
if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name):
|
||
adjusted.append(self._load(arg, name=f"Load_arg_{i}"))
|
||
else:
|
||
adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}"))
|
||
elif isinstance(arg.type, ir.PointerType) and arg.type.pointee == param.type:
|
||
adjusted.append(self._load(arg, name=f"Load_arg_{i}"))
|
||
elif isinstance(param.type, ir.PointerType) and param.type.pointee == arg.type:
|
||
tmp: ir.AllocaInstr = self._alloca(arg.type, name=f"temp_arg_{i}")
|
||
self._store(arg, tmp)
|
||
adjusted.append(tmp)
|
||
elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.PointerType):
|
||
# 任意指针到指针的类型转换(如 struct* -> i8*)
|
||
adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}"))
|
||
else:
|
||
adjusted.append(arg)
|
||
else:
|
||
adjusted.append(arg)
|
||
while len(adjusted) < len(func.args):
|
||
missing_idx: int = len(adjusted)
|
||
param: ir.Argument = func.args[missing_idx]
|
||
param_type: ir.Type = param.type if hasattr(param, 'type') else param
|
||
if isinstance(param_type, ir.PointerType):
|
||
adjusted.append(ir.Constant(param_type, None))
|
||
elif isinstance(param_type, ir.IntType):
|
||
adjusted.append(ir.Constant(param_type, 0))
|
||
elif isinstance(param_type, (ir.FloatType, ir.DoubleType)):
|
||
adjusted.append(ir.Constant(param_type, 0.0))
|
||
else:
|
||
adjusted.append(ir.Constant(ir.IntType(32), 0))
|
||
if func.type.pointee.var_arg:
|
||
for i in range(len(func.args), len(args)):
|
||
arg: ir.Value = args[i]
|
||
adjusted.append(arg)
|
||
return adjusted
|
||
|
||
def _class_has_vtable(self, ClassName: str | None) -> bool:
|
||
"""类(或其任一祖先)是否带 vtable 槽。
|
||
|
||
与 StructGen.py:442-450 的父链传播同源——vtable 沿继承链携带,子类 struct 布局为
|
||
[vtable?] + [基类字段…] + [自身字段…]。只认 class_vtable /
|
||
_cross_module_vtable_classes(装饰器集合)会漏掉源码未写 @t.CVTable 的跨模块
|
||
AST 节点类(ast.Try / ast.Match / ast.ExceptHandler…):它们有方法、继承自
|
||
@t.CVTable 的 AST,struct 首元素确实是 vtable 指针,但 StructGen.py:415 在
|
||
"struct body 已从 stub 完整加载"时直接 continue,永不把它们登记进集合
|
||
(实测 TransPyV/temp/_shared_sym.json 的 class_vtable/cross_module_vtable 仅有
|
||
{AST, MemManager, Module})。此时 base 静默取 0 → 该类字段偏移整体前移 1
|
||
(ast.ExceptHandler.children 落到 end_col_offset,i32)。
|
||
"""
|
||
if not ClassName:
|
||
return False
|
||
_Seen: set[str] = set()
|
||
_Cur: str | None = ClassName
|
||
while _Cur and _Cur not in _Seen:
|
||
_Seen.add(_Cur)
|
||
if _Cur in self.class_vtable or _Cur in self._cross_module_vtable_classes:
|
||
return True
|
||
_Cur = self.class_parent.get(_Cur)
|
||
return False
|
||
|
||
def _NormalizeMemberClass(self, ClassName: str | None) -> str | None:
|
||
"""成员查询用的类键归一(短名 + 指针包装展开)。
|
||
|
||
- 统一短名解析(治本修复 2026-08-10):struct/class_members/class_vtable/
|
||
class_parent 均以短名注册(跨模块加载 HandlesImports L1604),而调用方
|
||
可能传入带 sha1 前缀的全名(如 "f47f88ae6545965a.Import")。
|
||
- rawptr[X] / t.CPtr[X] 指针包装归一:rawptr[X] 是内建裸指针类型
|
||
(X*),非真实类——接收者注解/类型名为 'rawptr[AST]' 时字段访问
|
||
(child.parent)应作用于内层 struct X,否则查 class_members 全 miss
|
||
→ fast-fail "属性 'parent' 在类 'rawptr[AST]' 中找不到(struct=opaque)"
|
||
(includes/ast/base.vp _set_parent_list bootstrap 根因)。
|
||
"""
|
||
if not ClassName:
|
||
return ClassName
|
||
_Cls: str = self._extract_short_name(ClassName)
|
||
if _Cls.startswith('rawptr[') or _Cls.startswith('t.CPtr['):
|
||
_lb: int = _Cls.find('[')
|
||
if _Cls.endswith(']') and _lb > 0:
|
||
_inner: str = _Cls[_lb + 1:-1].strip()
|
||
_short_inner: str = _inner.rsplit('.', 1)[-1] if '.' in _inner else _inner
|
||
_hit: str | None = None
|
||
if _short_inner in self.structs:
|
||
_hit = _short_inner
|
||
else:
|
||
for _k in self.structs:
|
||
if self._extract_short_name(_k) == _short_inner:
|
||
_hit = _k
|
||
break
|
||
if _hit:
|
||
_Cls = _hit
|
||
return _Cls
|
||
|
||
def _member_base(self, ClassName: str | None, StructHint: Any = None) -> int:
|
||
"""类 struct 的成员偏移基址(vtable 占位槽数)——**全仓唯一真源**。
|
||
|
||
`StructHint`(可选):调用方手里**实际要用于 GEP 的那个 struct 实例**
|
||
(通常就是 `ObjVal.type.pointee`)。同一类键上可能同时存在两个实例
|
||
(同名类 sha1 冲突的双注册,或一个已从 stub 加载完整、另一个仍 opaque),
|
||
而偏移基址必须与"实际 GEP 用的那份布局"一致 —— 用键上那份算、用另一份读,
|
||
会让整类字段偏移平移(现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*),
|
||
实参第 6 项被报"指针不能隐式转整数 i32")。传 hint 时以 hint 为准并**报出**
|
||
两侧差异(每类一次),不传时行为与改前逐字一致。
|
||
|
||
`_get_member_offset` 与所有"自算 base 再 GEP 字段"的消费点(构造/初始化、
|
||
字段默认值、继承方法 this 调整等)都必须走本方法,禁止各自直查
|
||
`class_vtable`/`_cross_module_vtable_classes`——旧实现只认这两个装饰器集合,
|
||
跨模块 AST 节点类(ast.Try/ast.Match/ast.ExceptHandler…)源码未写
|
||
@t.CVTable 且 StructGen 在"struct body 已从 stub 加载"时提前 continue,
|
||
永不把它们登记进集合,于是 base 只能靠"成员表长度 == struct 元素数 - 1"
|
||
这个长度启发式救回;成员表未展平/尚未加载时启发式失效 ⇒ base 静默取 0
|
||
⇒ 该类所有字段偏移整体前移 1(ast.ExceptHandler.children 落到
|
||
end_col_offset(i32),len() 分派拿到 i32 ⇒ Type of #1 arg mismatch)。
|
||
|
||
判定规则(与 `StructGen._generate_structs` 生成 struct 的规则同源):
|
||
- vtable 归属沿 class_parent 链判定(见 `_class_has_vtable`);
|
||
- 再用 struct 实际元素数校准:
|
||
delta(=struct 元素数 - 成员表长度) == 0 ⇒ 该 struct 无 vtable 槽 → base=0;
|
||
delta == 1 ⇒ 恰多一个槽:首元素为指针 / 无父类 / 继承链归属成立 → base=1;
|
||
delta >= 2 ⇒ 长度启发式失效,保留"继承链 vtable 归属"结果(不再静默取 0)。
|
||
|
||
查询前会调用 `HandlesImports.EnsureClassMembersFlattened`(幂等)把"停在自有
|
||
字段"的未展平表补齐——未展平表会让下列 delta 校准失真,也会让按表内下标
|
||
取字段的消费点漏掉继承前缀。
|
||
"""
|
||
_Cls: str | None = self._NormalizeMemberClass(ClassName)
|
||
if not _Cls:
|
||
return 0
|
||
# 查询前确保成员表已按父类展平(幂等、顺序无关)。
|
||
# 未展平的表(停在自有字段)会让本方法的 delta 校准失真,也会让所有
|
||
# "按成员表下标 base+i 取字段"的消费点漏掉继承前缀 —— 见
|
||
# HandlesImports.EnsureClassMembersFlattened(内部有纯内存前置判断与
|
||
# 重入/重复尝试保护,对已展平类零额外开销)。
|
||
_EnsureIH: Any = getattr(self, '_import_handler_ref', None)
|
||
if _EnsureIH is not None and hasattr(_EnsureIH, 'EnsureClassMembersFlattened'):
|
||
try:
|
||
_EnsureIH.EnsureClassMembersFlattened(_Cls, self)
|
||
except Exception as _e:
|
||
if _config_mode == "strict":
|
||
raise
|
||
_vlog().warning(f"补齐类成员展平失败: {_e}", "Exception")
|
||
_ByInheritVtable: bool = self._class_has_vtable(_Cls)
|
||
base: int = 1 if _ByInheritVtable else 0
|
||
# 布局来源:优先调用方**实际 GEP 用的那个实例**(StructHint)——偏移必须与
|
||
# 真正被索引的那份 body 同源。只在 hint 不可用时才回落到类键上的实例。
|
||
_RegBase: ir.IdentifiedStructType | None = self.structs.get(_Cls)
|
||
_StructBase: ir.IdentifiedStructType | None = _RegBase
|
||
if isinstance(StructHint, ir.IdentifiedStructType):
|
||
# 仅接受**同类**的 hint:接收者静态类型是基类、实际指向派生类实例时,
|
||
# hint 的布局属于派生类,拿它配 `class_members[_Cls]` 会算错 delta。
|
||
_HintShort: str = self._extract_short_name(getattr(StructHint, 'name', '') or '')
|
||
if _HintShort == _Cls:
|
||
_StructBase = StructHint
|
||
if _RegBase is not None and _RegBase is not StructHint:
|
||
# 同一类键上两个 struct 实例:报出事实(每类一次),避免"用一份算、
|
||
# 用另一份读"的静默偏移平移(本函数此处即该平移的判定源)。
|
||
_Rep: set = getattr(self, '_MemberBaseInstMismatch', None)
|
||
if _Rep is None:
|
||
_Rep = set()
|
||
self._MemberBaseInstMismatch = _Rep
|
||
if _Cls not in _Rep:
|
||
_Rep.add(_Cls)
|
||
_RegN: str = (f"{len(_RegBase.elements)} 元素"
|
||
if _RegBase.elements is not None else 'opaque')
|
||
_HintN: str = (f"{len(StructHint.elements)} 元素"
|
||
if StructHint.elements is not None else 'opaque')
|
||
_vlog().warning(
|
||
f"[成员偏移] 类 '{_Cls}' 存在两个 struct 实例:类键上 "
|
||
f"{_RegBase.name}({_RegN}) vs 取值处 {StructHint.name}({_HintN})"
|
||
f";偏移基址已按取值处实例计算(两者布局不同会让字段偏移平移)",
|
||
"MemberBase")
|
||
if (isinstance(_StructBase, ir.IdentifiedStructType)
|
||
and _StructBase.elements is not None and len(_StructBase.elements) > 0):
|
||
_NumElems: int = len(_StructBase.elements)
|
||
_MembersRaw: list = self.class_members.get(_Cls, [])
|
||
_NumMembers: int = len(_MembersRaw)
|
||
# 【重名条目不是布局槽位 ⇒ 长度校准按**去重名数**算(2026-09-30)】
|
||
# 与同文件 `_get_member_offset` 的配对段(重名条目跳过、不占槽位)以及
|
||
# StructGen 的"重名条目"分支同口径。依据:源码层面的字段名遮蔽已由
|
||
# HandlesClassDef 的"字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 成员表里的重名
|
||
# 只可能是**产物**(展平残留 / 同一字段被两条收集路径各登记一次),
|
||
# 不是合法槽位。
|
||
# 不这样算的后果(现场):`ast.MatchCase` 表 10 项(9 个字段 + 1 条重复)
|
||
# 而 body 恰 10 槽 ⇒ `_Delta == 0` ⇒ base 静默取 0 ⇒ 该类所有字段偏移整体
|
||
# 前移 1:`MatchCase.children` 表内下标 6 被当作槽 6(`end_col_offset`,i32)
|
||
# ⇒ `len(mc_c.children)` 分派 `list[AST*].__len__` 时收到 i32:
|
||
# "Type of #1 arg mismatch: %"…list[…]"* != i32"。
|
||
_UniqMembers: set = {n for n, _ in _MembersRaw}
|
||
if len(_UniqMembers) < _NumMembers:
|
||
_NumMembers = len(_UniqMembers)
|
||
_Delta: int = _NumElems - _NumMembers
|
||
_FirstElem: ir.Type = _StructBase.elements[0]
|
||
_IsVtablePtr: bool = (isinstance(_FirstElem, ir.PointerType)
|
||
and isinstance(_FirstElem.pointee, ir.IntType)
|
||
and _FirstElem.pointee.width == 8)
|
||
if _Delta == 0:
|
||
base = 0
|
||
elif _Delta == 1:
|
||
_HasParent: bool = self.class_parent.get(_Cls) is not None
|
||
base = 1 if (_IsVtablePtr or not _HasParent or _ByInheritVtable) else 0
|
||
# ---- 类型一致性校准(治本)----
|
||
# 上面的 delta 长度启发式只在"表与布局恰好差 vtable 一项"时成立:一旦成员表
|
||
# 比布局多/少**非 vtable** 项(跨模块类展平不全、同名类复用等),base 会被判错,
|
||
# 结果是**整个类所有字段偏移整体平移**。实测:`ast.Constant` 的
|
||
# struct = {i8*(vtable), AST*(parent), MemManager*(pool), i32(lineno), …}
|
||
# 而查询 `cnst.lineno` 得到的是 pool 那槽(`%MemManager*`)⇒ 调用点报
|
||
# "形参 lineno: i32 收到指针",掩盖了真因。
|
||
# 校准办法:对 b∈{0,1,2} 逐项比较"成员声明类型 vs elements[b+i] 类型",
|
||
# 取一致度最高者(完全相同 +2;同为指针但类型不同 +0)。正确对齐时
|
||
# 指针槽几乎全中,错位对齐只能靠 i32 撞运气,分数明显更低;平手保留启发式。
|
||
#
|
||
# 【只在长度不齐(_Delta != 0)时校准】_Delta == 0 表示"成员表项数 == body
|
||
# 槽数":定义端 body 由成员表生成、跨模块端 body(.stub.ll)与 .vpi 同源
|
||
# (同一声明顺序),长度相等即**逐位对齐**,base 必为 0。此时再用严格类型
|
||
# 比较挑 b 反而会误判——声明侧的联合类型(如 `list[str] | t.CPtr`)可能被记成
|
||
# 折叠后的 i8*,而 body 槽仍是具体指针(`%list[str]*`):b=0 丢掉这些匹配,
|
||
# 某个平移 b 却能靠指针槽互相撞分拿到更高分。
|
||
# 现场(2026-09-30):TypeGenMixin(41 成员 / 41 槽 / @t.NoVTable,_Delta=0)
|
||
# base 被抬到 2 ⇒ `SubTypeGen.Gen = self`(Gen 声明 Generator*、正确槽 0)
|
||
# 落到槽 40(TypeSpecArena*)⇒ "指针隐式转换已禁止(非基本类型(结构体/类/联合))"。
|
||
# _Delta != 0 的既有现场(ast.Constant 的 `cnst.lineno` 落到 pool 槽等)不受影响,
|
||
# 仍走校准。
|
||
_MembersCal: list = self.class_members.get(_Cls, [])
|
||
if _MembersCal and _NumElems > 0 and _Delta != 0:
|
||
_BestB: int = base
|
||
_BestScore: int = -1
|
||
_HeurScore: int = -1
|
||
_b: int
|
||
for _b in range(0, 3):
|
||
_Score: int = 0
|
||
_i2: int = 0
|
||
for _nm2, _mt2 in _MembersCal:
|
||
_j2: int = _b + _i2
|
||
if _j2 >= _NumElems:
|
||
break
|
||
_et2: ir.Type = _StructBase.elements[_j2]
|
||
if _mt2 is _et2 or str(_mt2) == str(_et2):
|
||
_Score += 2
|
||
_i2 += 1
|
||
if _b == base:
|
||
_HeurScore = _Score
|
||
if _Score > _BestScore:
|
||
_BestScore = _Score
|
||
_BestB = _b
|
||
# 仅当"明显更优"(至少多对齐 1 个成员)才覆盖启发式结果,避免抖动。
|
||
if _HeurScore >= 0 and _BestScore >= _HeurScore + 2:
|
||
base = _BestB
|
||
return base
|
||
|
||
def _RenumVariantFieldOwner(self, ClassName: str | None, field_name: str) -> str | None:
|
||
"""REnum **变体字段**的归属类键:主 REnum 不声明该字段时,返回**唯一**声明它的变体键。
|
||
|
||
判据(逐条可查,不是猜):
|
||
① 类短名按 `_IsRenumLayoutClass`(全仓唯一判据源)判为 REnum —— 只有 REnum
|
||
才有变体层;
|
||
② 变体类键固定为 `<主类短名>_<变体名>`,且成员表**首项恒为 `__tag`**
|
||
(唯一产源:HandlesClassDef._EmitREnumLlvm:1659-1673 与 HandlesImports
|
||
的 REnum 跨模块注册 :1975-2015);
|
||
③ 恰有**一个**变体声明该字段(多个变体同名字段 ⇒ 无法判定 ⇒ None,由调用方
|
||
照常 fail-fast);
|
||
④ 该变体键的 struct 已注册(布局可用)。
|
||
|
||
用变体键算偏移为什么正确:变体 struct 的 body 是**主 REnum 镜像**
|
||
`{i32 __tag, <max_variant_payload>}`,而变体成员表 `[__tag] + 载荷` 与主 REnum
|
||
槽位一一对应 ⇒ `base + 表内下标` 即真实槽位。
|
||
现场(2026-09-30):CallGenDispatch.vp:787 `len_val.Ty.Pointee` —— 'Pointee' 只属
|
||
变体键 'LLVMType_Ptr',主类 'LLVMType' 的成员表为空(HandlesClassDef:716)。
|
||
"""
|
||
if not ClassName or not field_name:
|
||
return None
|
||
_Short: str | None = self._extract_short_name(ClassName)
|
||
if not _Short:
|
||
return None
|
||
if not self._IsRenumLayoutClass(_Short):
|
||
return None
|
||
_Owner: str | None = None
|
||
_Prefix: str = f"{_Short}_"
|
||
for _Key, _Members in self.class_members.items():
|
||
if not _Key.startswith(_Prefix):
|
||
continue
|
||
_List: list = _Members or []
|
||
if not _List or _List[0][0] != '__tag':
|
||
continue
|
||
if not any(_n == field_name for _n, _ in _List):
|
||
continue
|
||
if _Key not in self.structs:
|
||
continue
|
||
if _Owner is not None:
|
||
return None
|
||
_Owner = _Key
|
||
return _Owner
|
||
|
||
def _MemberSlotIndex(self, Members: list, FieldName: str) -> int:
|
||
"""字段在成员表里的**槽位下标**(重名条目**不占槽位**;找不到返回 -1)。
|
||
|
||
与 `_get_member_offset` 的配对路径同口径:重名条目是产物,不是布局槽位
|
||
—— 源码层面的字段名遮蔽已由 HandlesClassDef 的"字段名遮蔽 fail-fast"拦下,
|
||
其余重名只可能来自"展平残留 / 同一字段被两条收集路径各登记一次"。
|
||
所有"按表内下标 + base 求偏移"的站点都必须走本方法,否则含重名条目的表会让
|
||
偏移整体偏斜:
|
||
· `ast.ExceptHandler` 表 10 项、'children' 出现两次 ⇒ 'name' 原始下标 9 ⇒ 偏移 10(越界);
|
||
· `ast.MatchCase` 表 10 项 / 去重 9(body 10 槽)⇒ `_Delta == 0` ⇒ base 变 0 ⇒
|
||
`children` 偏移 6 落到 `end_col_offset`(i32)。
|
||
重名条目在**首个同名(正本)之后**才出现,故按"首个同名的去重下标"取值。
|
||
"""
|
||
_Slot: int = 0
|
||
_Seen: set = set()
|
||
for _n, _ in (Members or []):
|
||
if _n in _Seen:
|
||
continue
|
||
_Seen.add(_n)
|
||
if _n == FieldName:
|
||
return _Slot
|
||
_Slot += 1
|
||
return -1
|
||
|
||
def _get_member_offset(self, field_name: str, ClassName: str | None = None,
|
||
StructHint: Any = None) -> int | None:
|
||
# base 判定用未归一的原始类名:_member_base 内部自行做同一套归一
|
||
# (_NormalizeMemberClass),二者结果逐字一致(避免二次归一在
|
||
# "rawptr[X] 展开命中 sha1 前缀 struct 键"这一角落改变取值)。
|
||
_MemberBaseKey: str | None = ClassName
|
||
# 治本修复:同名类 sha1 冲突检测。
|
||
# 当 class_sha1_map[ClassName] 的 sha1 与 Gen.structs[ClassName] 的 sha1 不一致时,
|
||
# class_members[ClassName] 可能存储了错误模块的同名类成员。
|
||
# 此处检测冲突,加载正确模块的 class_members 并以 sha1 前缀全名缓存。
|
||
_ResolvedClass: str = ClassName if ClassName else ''
|
||
if ClassName:
|
||
# 类键归一(短名 + rawptr[X]/t.CPtr[X] 展开)统一走 _NormalizeMemberClass,
|
||
# 与 _member_base 的 base 判定共用同一归一,禁止各写一份。
|
||
ClassName = self._NormalizeMemberClass(ClassName)
|
||
_ResolvedClass = ClassName
|
||
_ClassSha1Map: dict[str, str] = getattr(self, 'class_sha1_map', {})
|
||
_ExpectedSha1: str | None = _ClassSha1Map.get(ClassName)
|
||
if _ExpectedSha1:
|
||
_ExistingStruct = self.structs.get(ClassName)
|
||
if isinstance(_ExistingStruct, ir.IdentifiedStructType):
|
||
_ExistingSha1: str = self._extract_struct_sha1(_ExistingStruct)
|
||
if _ExistingSha1 and _ExistingSha1 != _ExpectedSha1:
|
||
# sha1 冲突:使用全名查找 class_members
|
||
_FullKey: str = f"{_ExpectedSha1}.{ClassName}"
|
||
if _FullKey in self.class_members and self.class_members[_FullKey]:
|
||
_ResolvedClass = _FullKey
|
||
else:
|
||
# 从正确 stub 加载 class_members
|
||
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||
_StubName: str = f"{_ExpectedSha1}.stub.ll"
|
||
# 临时保存旧值,清除短名条目以绕过 guard,加载后恢复
|
||
_OldMembers = self.class_members.get(ClassName)
|
||
self.class_members[ClassName] = []
|
||
try:
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(ClassName, _StubName, self)
|
||
except Exception:
|
||
pass
|
||
_NewMembers = self.class_members.get(ClassName)
|
||
if _NewMembers:
|
||
self.class_members[_FullKey] = _NewMembers
|
||
_ResolvedClass = _FullKey
|
||
# 恢复旧值(避免影响其他代码对短名的依赖)
|
||
if _OldMembers:
|
||
self.class_members[ClassName] = _OldMembers
|
||
else:
|
||
self.class_members.pop(ClassName, None)
|
||
# base(vtable 占位槽数):唯一真源 = _member_base(规则与文档见该方法)。
|
||
# 本函数在同一次调用内还会在下方多处重载成员表(懒加载 425-436、模块重载
|
||
# 482-508、字段预注册 558-578、force 重建 587-643),每次都可能改写
|
||
# _member_base 的三个输入(class_parent 父链、structs 布局、class_members
|
||
# 长度)。因此 base 与"即将返回的下标 i"必须取自**同一份最新已加载状态**:
|
||
# 所有 return 一律用 _BaseNow() 现算,禁止缓存早先算出的 base——缓存会让
|
||
# 偏移整体差 1(ast.ExceptHandler.children 落到 end_col_offset(i32),
|
||
# len 分派拿到 i32)。
|
||
def _BaseNow() -> int:
|
||
return self._member_base(_MemberBaseKey, StructHint)
|
||
# 预热(与原行为逐字一致):先触发一次 EnsureClassMembersFlattened,
|
||
# 再决定下面的懒加载。
|
||
_BaseNow()
|
||
if ClassName and ClassName not in self.class_members and self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||
# 跨模块 class_members 懒加载:struct 已从 stub 加载但 class_members
|
||
# 可能尚未从 .vpi 填充(vpi/stub 就绪时序不一致),主动按类 sha1 加载。
|
||
_Sha1sForLoad: dict[str, str] = getattr(self, 'class_sha1_map', {})
|
||
for _mod_name_lz, _mod_sha1_lz in _Sha1sForLoad.items():
|
||
try:
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(
|
||
ClassName, f"{_mod_sha1_lz}.stub.ll", self)
|
||
except Exception:
|
||
continue
|
||
if ClassName in self.class_members and len(self.class_members[ClassName]) > 0:
|
||
break
|
||
# 依赖补齐(上面的懒加载)后再确认一次展平(幂等);偏移基址由 _BaseNow()
|
||
# 在真正 return 处现算,不在此缓存。
|
||
_BaseNow()
|
||
# ---- REnum 变体字段:主 REnum 不声明该字段时,改用它**唯一**的变体键继续 ----
|
||
# 现场(2026-09-30):CallGenDispatch.vp:787 `len_val.Ty.Pointee` —— 'Pointee' 属
|
||
# LLVMType 的 REnum 变体键 'LLVMType_Ptr',主类 'LLVMType' 成员表为空 ⇒ 旧路径
|
||
# 直接 fast-fail "属性 'Pointee' 在类 'LLVMType' 中找不到" 终止整个编译。
|
||
# 判据与槽位正确性见 _RenumVariantFieldOwner(变体 struct body = 主 REnum 镜像)。
|
||
if field_name and ClassName:
|
||
_MainMembers: list = (self.class_members.get(_ResolvedClass)
|
||
or self.class_members.get(ClassName) or [])
|
||
if not any(_n == field_name for _n, _ in _MainMembers):
|
||
_VariantOwner: str | None = None
|
||
try:
|
||
_VariantOwner = self._RenumVariantFieldOwner(ClassName, field_name)
|
||
except Exception:
|
||
_VariantOwner = None
|
||
if _VariantOwner:
|
||
_RepRK: set = getattr(self, '_RenumVariantFieldReported', None)
|
||
if _RepRK is None:
|
||
_RepRK = set()
|
||
self._RenumVariantFieldReported = _RepRK
|
||
_RKey: tuple = (_VariantOwner, field_name)
|
||
if _RKey not in _RepRK:
|
||
_RepRK.add(_RKey)
|
||
_vlog().warning(
|
||
f"[REnum 变体字段] '{field_name}' 不在主 REnum 类 "
|
||
f"{_ResolvedClass!r} 中,按唯一变体类 '{_VariantOwner}' 取偏移"
|
||
f"(变体 struct body = 主 REnum 镜像,槽位一一对应)",
|
||
"MemberBase")
|
||
ClassName = _VariantOwner
|
||
_ResolvedClass = _VariantOwner
|
||
_MemberBaseKey = _VariantOwner
|
||
_BaseNow()
|
||
# ---- 布局真源优先(配对式偏移):偏移只认"运行时真正被 GEP 的那份 struct body" ----
|
||
# class_members 是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载),
|
||
# 而 struct body 一旦 set_body 即**永久冻结** ⇒ "base + 成员表下标"会随表漂移
|
||
# 整体平移(现场:Converter 模块读 `Alias.name` 取到槽 9(= asname),
|
||
# `import t` 无 as 子句 ⇒ asname=NULL ⇒ vpi 生成误报"import 别名 name 为空";
|
||
# 同族旧现场:ast.Constant.lineno 取到 pool 槽)。改为两条"只看布局"的路径:
|
||
# ① 建 body 时冻结下来的"成员名 → 槽位"映射(StructGen._generate_structs 挂载);
|
||
# ② 用**类型**把成员表逐项配对到 elements:类型不匹配的条目视为方法/占位/漂移项,
|
||
# 不占槽位;配对中类型相等的那个槽位即该字段的真实槽位。
|
||
# 表与布局自洽时两条路径给出与旧下标法**逐字相同**的结果(body 本按该表建);
|
||
# 都覆盖不到时落回旧下标法(对既有正确行为零变化)。
|
||
if field_name:
|
||
_OffStruct: Any = self.structs.get(ClassName)
|
||
if isinstance(StructHint, ir.IdentifiedStructType) and ClassName:
|
||
# 与 _member_base 同口径:只接受**同类**的 hint(接收者静态类型可能是基类,
|
||
# 实际指向派生类实例,其布局不属于本类)。
|
||
if self._extract_short_name(getattr(StructHint, 'name', '') or '') == ClassName:
|
||
_OffStruct = StructHint
|
||
if isinstance(_OffStruct, ir.IdentifiedStructType):
|
||
_OffEls: Any = _OffStruct.elements
|
||
_Frozen: Any = getattr(_OffStruct, '_tpc_member_slots', None)
|
||
# 冻结表**有效性**判定:表挂在 struct 实例上,而 body 会被"不完整即清除"
|
||
# (StructGen 预检 `existing_st.elements = None`)与 stub 加载
|
||
# (HandlesImports._TryLoadStructFromStub 的 set_body)**清空后重设**,
|
||
# 重设路径不更新本表 ⇒ 陈旧表把字段指到别的槽。现场:AnnAssign 在
|
||
# "自有 4 字段 + vtable"(5 槽:i8*,AST*,AST*,AST*,i32)时冻结
|
||
# {'value': 3},随后 body 被重设为 12 槽正确布局(value 在 10),
|
||
# 查询仍读槽 3(lineno, i32)⇒ `.kind()` 虚分派 GEP 的操作数是 i32。
|
||
# 判据 = 建表时记下的 **body 对象身份**(set_body 每次赋新 tuple):
|
||
# 与当前 elements 非同一对象即视为失效,落到按布局求槽的路径。
|
||
_FrozenFresh: bool = (isinstance(_Frozen, dict)
|
||
and getattr(_OffStruct, '_tpc_member_body', None) is _OffEls)
|
||
if _FrozenFresh and field_name in _Frozen:
|
||
_FrozenSlot: Any = _Frozen[field_name]
|
||
if isinstance(_FrozenSlot, int) and (not _OffEls or 0 <= _FrozenSlot < len(_OffEls)):
|
||
return _FrozenSlot
|
||
_OffMembers: list = self.class_members.get(_ResolvedClass) or []
|
||
_BitFields: Any = getattr(self, 'class_member_bitfields', None)
|
||
if _OffEls and _OffMembers and not (_BitFields and _BitFields.get(ClassName)):
|
||
# ---- 布局真源之二:两种对齐候选按"逐项类型可写入"**打分**取高者 ----
|
||
# 成员表可能是①展平表(含父类前缀)②仅自有字段(父类前缀未补齐 ⇒ 真身是
|
||
# body **尾部**)③残缺前缀(如仅 ['Gen'] 一项 ⇒ 真身在 body **头部**)。
|
||
# 只用"长度关系/父类槽数"来断言①③是猜,已被两轮现场打脸
|
||
# (Generator.vp:136 TypeGenMixin 被锚到槽 40;:164 TypeGenErr 因恰好
|
||
# 只有 1 个自有字段、`body-len(表) == 父类槽数` 巧合成立而被锚到槽 41)。
|
||
# 改为**比较**两个候选锚(前缀锚 `_BaseNow()` vs 尾部锚 `len(body)-len(表)`)
|
||
# 的逐项得分(判据与建 body/存储侧同一套 `_MemberSlotAccepts`):
|
||
# 只有尾部锚**严格更高分**才采用它,同分/更低一律保持原行为
|
||
# (配对 → 下标法),因此对既有正确布局零风险。
|
||
_ElemN: int = len(_OffEls)
|
||
_AnchorA: int = _BaseNow()
|
||
_AnchorB: int = _ElemN - len(_OffMembers)
|
||
_ScoreA: int = 0
|
||
_aA: int = 0
|
||
while _aA < len(_OffMembers):
|
||
if _AnchorA + _aA >= _ElemN:
|
||
break
|
||
_nA, _tA = _OffMembers[_aA]
|
||
if self._MemberSlotAccepts(_tA, _OffEls[_AnchorA + _aA]):
|
||
_ScoreA += 1
|
||
_aA += 1
|
||
_ScoreB: int = 0
|
||
if _AnchorB >= 0 and _AnchorB != _AnchorA and not self._IsRenumLayoutClass(_ResolvedClass):
|
||
_aB: int = 0
|
||
while _aB < len(_OffMembers):
|
||
if _AnchorB + _aB >= _ElemN:
|
||
break
|
||
_nB, _tB = _OffMembers[_aB]
|
||
if self._MemberSlotAccepts(_tB, _OffEls[_AnchorB + _aB]):
|
||
_ScoreB += 1
|
||
_aB += 1
|
||
if _ScoreB > _ScoreA and _ScoreB > 0:
|
||
_TIdx: int = -1
|
||
for _i5, (_n5, _) in enumerate(_OffMembers):
|
||
if _n5 == field_name:
|
||
_TIdx = _i5
|
||
break
|
||
if _TIdx >= 0:
|
||
# 每类只留一次痕(大类的每个字段都会命中本路径)
|
||
_TailKey: str = _ResolvedClass
|
||
_TailRep: set = getattr(self, '_MemberTailAlignedReported', None)
|
||
if _TailRep is None:
|
||
_TailRep = set()
|
||
self._MemberTailAlignedReported = _TailRep
|
||
if _TailKey not in _TailRep:
|
||
_TailRep.add(_TailKey)
|
||
_vlog().warning(
|
||
f"[成员偏移] 类 '{_ResolvedClass}' 的成员表({len(_OffMembers)} 项)"
|
||
f"在 body({_ElemN} 槽)上按**尾部对齐**得分更高"
|
||
f"(尾部锚 {_AnchorB} 得 {_ScoreB} vs 前缀锚 {_AnchorA} 得 {_ScoreA})"
|
||
f"⇒ 判定该表缺的是**父类前缀**:字段 '{field_name}' = "
|
||
f"{_AnchorB + _TIdx}",
|
||
"MemberBase")
|
||
return _AnchorB + _TIdx
|
||
_Cursor: int = _BaseNow()
|
||
_PairedNames: set = set()
|
||
for _nmP, _mtP in _OffMembers:
|
||
if _Cursor >= _ElemN:
|
||
break
|
||
# 重名条目不是布局槽位(表构造/重载产生的重复项)⇒ 不占槽位
|
||
if _nmP in _PairedNames:
|
||
continue
|
||
# None 成员在 body 里被物化为 i32(见 StructGen 建 body 分支),
|
||
# 配对类型必须同口径,否则该类字段会被整条跳过。
|
||
_mtEff: Any = _mtP if _mtP is not None else ir.IntType(32)
|
||
_etP: Any = _OffEls[_Cursor]
|
||
_PairOk: bool = (_mtEff is _etP or str(_mtEff) == str(_etP))
|
||
if (not _PairOk and isinstance(_mtEff, ir.PointerType)
|
||
and isinstance(_etP, ir.PointerType)):
|
||
# 联合类型字段在成员表里存成通用指针(如 AST.children =
|
||
# `list[rawptr[AST]] | t.CPtr` ⇒ i8*),而 body 槽是具体指针
|
||
# (`%"...list[...]"*`):严格串比较会在这条**断链**,其后的
|
||
# 所有条目都不再消费槽位(cursor 不动)⇒ 目标字段全部退化成
|
||
# "base + 表内下标"(现场:ExceptHandler.name 表内下标 9 ⇒
|
||
# 偏移 10,越界 fail-fast,而正确槽是 9)。判据与存储/建 body
|
||
# 侧同一套(_MemberSlotAccepts:两侧都是指针时按指针转换规则,
|
||
# i8* 通用指针放行);标量仍走严格串比较,不引入新的过配对。
|
||
_PairOk = self._MemberSlotAccepts(_mtEff, _etP)
|
||
if _PairOk:
|
||
_PairedNames.add(_nmP)
|
||
if _nmP == field_name:
|
||
# 与旧下标法不一致时留痕:这正是"成员表与 struct 布局
|
||
# 不自洽"的现场事实(每类每字段一次,便于定位漂移来源)。
|
||
_OldIdx: int = -1
|
||
for _i3, (_n3, _) in enumerate(_OffMembers):
|
||
if _n3 == field_name:
|
||
_OldIdx = _i3
|
||
break
|
||
if _OldIdx >= 0 and _BaseNow() + _OldIdx != _Cursor:
|
||
_RepKey: tuple = (_ResolvedClass, field_name)
|
||
_RepSet: set = getattr(self, '_MemberSlotDriftReported', None)
|
||
if _RepSet is None:
|
||
_RepSet = set()
|
||
self._MemberSlotDriftReported = _RepSet
|
||
if _RepKey not in _RepSet:
|
||
_RepSet.add(_RepKey)
|
||
_vlog().warning(
|
||
f"[成员偏移] 类 '{_ResolvedClass}' 的成员表与 struct "
|
||
f"布局不自洽:字段 '{field_name}' 下标法={_BaseNow() + _OldIdx}"
|
||
f"(成员表 {len(_OffMembers)} 项) vs 布局配对比={_Cursor}"
|
||
f"(struct {_ElemN} 槽)——已采用布局配对结果",
|
||
"MemberBase")
|
||
return _Cursor
|
||
_Cursor += 1
|
||
if _ResolvedClass and _ResolvedClass in self.class_members:
|
||
# 只有走到这里(布局配对未命中)才做一次重名检查:重名条目**不占布局槽**,
|
||
# 却会让 `base + 表内下标` 整体后移(现场:ExceptHandler 表 10 项、其中
|
||
# 'children' 出现两次 ⇒ 'name' 下标 9 ⇒ 偏移 10 越界)。此处把事实报出来
|
||
# (每类一次),并标注该返回值取自回退路径、可能失准。
|
||
_MemList: list = self.class_members[_ResolvedClass]
|
||
_SeenNm: set = set()
|
||
_DupNm: list[str] = []
|
||
for _nmC, _ in _MemList:
|
||
if _nmC in _SeenNm:
|
||
if _nmC not in _DupNm:
|
||
_DupNm.append(_nmC)
|
||
else:
|
||
_SeenNm.add(_nmC)
|
||
if _DupNm:
|
||
_DupRep: set = getattr(self, '_MemberDupNamesReported', None)
|
||
if _DupRep is None:
|
||
_DupRep = set()
|
||
self._MemberDupNamesReported = _DupRep
|
||
if _ResolvedClass not in _DupRep:
|
||
_DupRep.add(_ResolvedClass)
|
||
_vlog().warning(
|
||
f"[成员偏移] 类 '{_ResolvedClass}' 的成员表含**重名条目** "
|
||
f"{_DupNm!r}(表 {len(_MemList)} 项 / 去重 {len(_SeenNm)} 项)——"
|
||
f"重名项不是布局槽位,`base + 表内下标` 会因此整体后移;"
|
||
f"本次字段 '{field_name}' 走的是回退路径(未被 body 配对命中),"
|
||
f"返回值可能失准。重名来源通常是「同一字段被两条收集路径各登记一次"
|
||
f"(类内 AnnAssign 声明 + 方法体 self.x 赋值)」或继承前缀重复前置。",
|
||
"MemberBase")
|
||
# 回退取值也按"重名条目**不占槽位**"算(与上方配对路径同口径):直接取
|
||
# `base + 原始下标` 会把重名条目也算进槽位,整体后移(现场:ExceptHandler
|
||
# 表 10 项、'children' 出现两次 ⇒ 'name' 原始下标 9 ⇒ 偏移 10 越界)。
|
||
_SlotIdx: int = 0
|
||
_Counted: set = set()
|
||
for name, _ in _MemList:
|
||
if name in _Counted:
|
||
continue
|
||
_Counted.add(name)
|
||
if name == field_name:
|
||
return _BaseNow() + _SlotIdx
|
||
_SlotIdx += 1
|
||
# 继承链回退:当字段不在当前类的 class_members 中时(常见于跨模块导入
|
||
# 父类未加载,字段被填充为 __inherited_N 占位名),沿 class_parent 链向上查找。
|
||
# 子类 struct 布局为 [vtable?] + [基类字段...] + [子类字段...],
|
||
# 基类字段在子类 struct 中的起始偏移即为 base(子类),
|
||
# 因此祖先 class_members 中的索引 i 对应子类 struct 中的偏移 base + i。
|
||
if ClassName and field_name:
|
||
_parent: str | None = self.class_parent.get(ClassName)
|
||
_visited: set = {ClassName} if ClassName else set()
|
||
_cls_short: str = ClassName.split('.')[-1] if ClassName else ''
|
||
while _parent and _parent not in _visited:
|
||
_visited.add(_parent)
|
||
# 父类候选键:class_parent 只登记**短名**(含泛型基类时是模板名,
|
||
# 如 'GSListNode'),而 class_members 里真实键可能是**实例化名**
|
||
# ('GSListNode[GlobalVariable]',见 includes/llvmlite/__module.vp
|
||
# `class GlobalVariable(GSListNode[GlobalVariable])`)或模板名。
|
||
# 少这一步 ⇒ 泛型基类字段(Next/Prev 等)沿链永远查不到 → fast-fail
|
||
# ("属性 'Next' 在类 'GlobalVariable' 中找不到")。
|
||
_p_tmpl: str = _parent.split('[')[0]
|
||
_cand_keys: list = []
|
||
for _k in (_parent, _p_tmpl, f"{_p_tmpl}[{_cls_short}]" if _cls_short else _p_tmpl):
|
||
if _k and _k not in _cand_keys:
|
||
_cand_keys.append(_k)
|
||
# 跳过 __inherited_N 占位条目,只在真实字段名中查找
|
||
_hit_idx: int = -1
|
||
for _k in _cand_keys:
|
||
if _k in self.class_members:
|
||
_members: list = self.class_members[_k]
|
||
_has_placeholder: bool = any(n.startswith('__inherited_') for n, _ in _members)
|
||
if not _has_placeholder:
|
||
_hit_idx = self._MemberSlotIndex(_members, field_name)
|
||
if _hit_idx >= 0:
|
||
break
|
||
if _hit_idx >= 0:
|
||
# 基类字段位于子类 struct 的 base 起始处,索引 i 即基类内偏移
|
||
return _BaseNow() + _hit_idx
|
||
# 父类 class_members 未加载或含占位条目,尝试从 stub 加载真实字段
|
||
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||
sha1_map: dict[str, str] = getattr(self, 'ModuleSha1Map', {})
|
||
for _k in _cand_keys:
|
||
for mod_name, mod_sha1 in sha1_map.items():
|
||
stub_name: str = f"{mod_sha1}.stub.ll"
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(_k, stub_name, self)
|
||
if _k in self.class_members and not any(n.startswith('__inherited_') for n, _ in self.class_members[_k]):
|
||
_pIdx: int = self._MemberSlotIndex(self.class_members[_k], field_name)
|
||
if _pIdx >= 0:
|
||
return _BaseNow() + _pIdx
|
||
break
|
||
if _k in self.class_members and any(n == field_name for n, _ in self.class_members.get(_k, [])):
|
||
break
|
||
_parent = self.class_parent.get(_parent)
|
||
short_name: str | None = ClassName.split('.')[-1] if ClassName and '.' in ClassName else None
|
||
if short_name and short_name in self.class_members:
|
||
_sIdx: int = self._MemberSlotIndex(self.class_members[short_name], field_name)
|
||
if _sIdx >= 0:
|
||
return _BaseNow() + _sIdx
|
||
if ClassName and field_name:
|
||
if ClassName not in self.class_members or field_name not in [n for n, _ in self.class_members.get(ClassName, [])]:
|
||
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||
sha1_map: dict[str, str] = getattr(self, 'ModuleSha1Map', {})
|
||
for mod_name, mod_sha1 in sha1_map.items():
|
||
stub_name: str = f"{mod_sha1}.stub.ll"
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(ClassName, stub_name, self)
|
||
if ClassName in self.class_members and len(self.class_members[ClassName]) > 0:
|
||
_rIdx: int = self._MemberSlotIndex(self.class_members[ClassName], field_name)
|
||
if _rIdx >= 0:
|
||
return _BaseNow() + _rIdx
|
||
break
|
||
if short_name:
|
||
for mod_name, mod_sha1 in sha1_map.items():
|
||
stub_name: str = f"{mod_sha1}.stub.ll"
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(short_name, stub_name, self)
|
||
if short_name in self.class_members and len(self.class_members[short_name]) > 0:
|
||
_rIdx2: int = self._MemberSlotIndex(self.class_members[short_name], field_name)
|
||
if _rIdx2 >= 0:
|
||
return _BaseNow() + _rIdx2
|
||
break
|
||
if ClassName and field_name:
|
||
for cn_key in self.class_members:
|
||
if cn_key == ClassName or cn_key.endswith(f'.{ClassName}') or (short_name and (cn_key == short_name or cn_key.endswith(f'.{short_name}'))):
|
||
_cIdx: int = self._MemberSlotIndex(self.class_members[cn_key], field_name)
|
||
if _cIdx >= 0:
|
||
return _BaseNow() + _cIdx
|
||
# REnum 变体搜索: ClassName 是 REnum(如 MetaType),成员注册在变体结构
|
||
# (MetaType_Var, MetaType_Concrete, MetaType_App)中。变体与 REnum 主结构
|
||
# 共享 max_variant_struct 布局,偏移一致(__tag 在偏移 0,字段从偏移 1 开始)
|
||
if ClassName and field_name:
|
||
variant_prefix: str = f"{ClassName}_"
|
||
for cn_key in self.class_members:
|
||
if cn_key.startswith(variant_prefix):
|
||
_vIdx: int = self._MemberSlotIndex(self.class_members[cn_key], field_name)
|
||
if _vIdx >= 0:
|
||
return _vIdx
|
||
if short_name:
|
||
variant_prefix = f"{short_name}_"
|
||
for cn_key in self.class_members:
|
||
if cn_key.startswith(variant_prefix):
|
||
_vIdx2: int = self._MemberSlotIndex(self.class_members[cn_key], field_name)
|
||
if _vIdx2 >= 0:
|
||
return _vIdx2
|
||
if ClassName and ClassName in self.structs:
|
||
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
|
||
if isinstance(struct_type, ir.IdentifiedStructType) and struct_type.elements is not None and len(struct_type.elements) > 0:
|
||
for cn_key, members in self.class_members.items():
|
||
if cn_key == short_name or (short_name and cn_key.endswith(f'.{short_name}')):
|
||
_kIdx: int = self._MemberSlotIndex(members, field_name)
|
||
if 0 <= _kIdx < len(struct_type.elements):
|
||
return _kIdx
|
||
break
|
||
else:
|
||
if short_name:
|
||
for cn_key, members in self.class_members.items():
|
||
sn: str = cn_key.split('.')[-1] if '.' in cn_key else cn_key
|
||
if sn == short_name:
|
||
_kIdx2: int = self._MemberSlotIndex(members, field_name)
|
||
if 0 <= _kIdx2 < len(struct_type.elements):
|
||
return _kIdx2
|
||
break
|
||
if ClassName and field_name and ClassName not in self.class_members:
|
||
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||
for temp_dir_candidate in [getattr(self, '_temp_dir', None)]:
|
||
if temp_dir_candidate and os.path.isdir(temp_dir_candidate):
|
||
for pyi_file in os.listdir(temp_dir_candidate):
|
||
if pyi_file.endswith('.vpi') or pyi_file.endswith('.pyi'):
|
||
stub_name: str = pyi_file.replace('.vpi', '.stub.ll').replace('.pyi', '.stub.ll')
|
||
short_cn: str = short_name if short_name else ClassName
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(short_cn, stub_name, self)
|
||
if short_cn in self.class_members and len(self.class_members[short_cn]) > 0:
|
||
_scIdx: int = self._MemberSlotIndex(self.class_members[short_cn], field_name)
|
||
if _scIdx >= 0:
|
||
return _BaseNow() + _scIdx
|
||
break
|
||
if ClassName and ClassName in self.structs and (ClassName not in self.class_members or len(self.class_members.get(ClassName, [])) == 0):
|
||
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
|
||
if isinstance(struct_type, ir.IdentifiedStructType) and struct_type.elements is not None:
|
||
if self._Trans and hasattr(self._Trans, 'ClassHandler'):
|
||
class_handler: Any = self._Trans.ClassHandler
|
||
if self._current_tree:
|
||
for node in ast.iter_child_nodes(self._current_tree):
|
||
if isinstance(node, ast.ClassDef) and (node.name == ClassName or node.name == short_name):
|
||
if node.name not in self.class_members:
|
||
self.class_members[node.name] = []
|
||
if len(self.class_members[node.name]) == 0:
|
||
class_handler._PreRegisterClassMembers(node, self)
|
||
if ClassName in self.class_members:
|
||
_prIdx: int = self._MemberSlotIndex(self.class_members[ClassName], field_name)
|
||
if _prIdx >= 0:
|
||
return _BaseNow() + _prIdx
|
||
if short_name and short_name in self.class_members:
|
||
_prIdx2: int = self._MemberSlotIndex(self.class_members[short_name], field_name)
|
||
if _prIdx2 >= 0:
|
||
return _BaseNow() + _prIdx2
|
||
break
|
||
# 最后回退:当继承链回退失败但 struct body 已加载时,
|
||
# 从 struct elements 数和 class_members 数推断继承字段偏移。
|
||
# 继承字段在 struct 开头(base 之后),此 fallback 仅适用于单继承字段的情况
|
||
# (如 GSListNode[T] 的 Next 字段)。子类 struct 布局:
|
||
# [vtable?] + [基类字段...] + [子类字段...]
|
||
# 当 class_members 只含子类自身字段、基类 class_members 未加载时,
|
||
# 继承字段数 = struct_elements - class_members_count,
|
||
# 若该值 == 1,则所查字段(如 Next)的偏移 = base + 0。
|
||
if ClassName and field_name:
|
||
_st_final = self.structs.get(ClassName)
|
||
if isinstance(_st_final, ir.IdentifiedStructType) and _st_final.elements is not None:
|
||
_num_elements_final: int = len(_st_final.elements)
|
||
_members_final: list = self.class_members.get(ClassName, [])
|
||
# 与 _member_base 的长度校准同口径:重名条目**不是布局槽位** ⇒ 按去重名数算
|
||
_num_members_final: int = len({_Rn for _Rn, _ in _members_final})
|
||
if _num_elements_final > _num_members_final:
|
||
# base 是 vtable 占位(struct[0] = i8*),不是继承字段,必须扣除:
|
||
# 不扣 base 时,凡"struct 元素数恰好多 1"的类,差值其实只是 vtable
|
||
# 本身(class_members 不含 vtable),却会对任意字段名返回 base+0。
|
||
# 实例:ast.ImportFrom 的 level/module → 静默返回偏移 1(=parent,
|
||
# %"…AST"*)→ 调用点把 AST* 当 i32 传入 _resolve_relative_module
|
||
# → llvmlite "Type of #4 arg mismatch: i32 != %"…AST"*"。
|
||
# 成员表与 struct 不一致(少一个非 vtable 槽)时,先强制按定义模块
|
||
# 的 .vpi 重载一次成员表再按名查:_TryLoadClassMembersFromPyi 的 guard
|
||
# 会永久跳过"已存在但不完整"的表,此处即该冻结的解除点。重载后仍缺
|
||
# 才走下方推断/fast-fail。每类只重载一次(避免递归与重复 IO)。
|
||
_ReloadKey: tuple = (id(self), ClassName)
|
||
_ReloadTried: set = getattr(self, '_MemberReloadTried', None)
|
||
if _ReloadTried is None:
|
||
_ReloadTried = set()
|
||
self._MemberReloadTried = _ReloadTried
|
||
if _ReloadKey not in _ReloadTried:
|
||
_ReloadTried.add(_ReloadKey)
|
||
_Sha1Final: str | None = getattr(self, 'class_sha1_map', {}).get(ClassName)
|
||
if _Sha1Final and self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||
_OldMembers = _members_final
|
||
try:
|
||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(
|
||
ClassName, f"{_Sha1Final}.stub.ll", self, force=True)
|
||
except Exception:
|
||
pass
|
||
_ReloadedMembers: list = self.class_members.get(ClassName, [])
|
||
if len(_ReloadedMembers) < len(_OldMembers):
|
||
# 重载反而变少(.vpi 缺该类):回滚,避免丢字段
|
||
self.class_members[ClassName] = _OldMembers
|
||
_ReloadedMembers = _OldMembers
|
||
_members_final = _ReloadedMembers
|
||
_num_members_final = len({_Rn for _Rn, _ in _members_final})
|
||
_rfIdx: int = self._MemberSlotIndex(_members_final, field_name)
|
||
if _rfIdx >= 0:
|
||
return _BaseNow() + _rfIdx
|
||
_num_inherited_final: int = _num_elements_final - _num_members_final - _BaseNow()
|
||
if _num_inherited_final == 1:
|
||
# 推断(缺失槽位 = 位于 base+0 的未展平继承字段)仅在成员表
|
||
# 尚未含展平继承字段时成立:若成员表首项本身就是任一已加载
|
||
# 祖先的字段名(如 ImportFrom 'parent' ← Stmt),说明继承字段
|
||
# 已展平、缺的是类自身字段,推断 base+0 必然读错槽位。
|
||
_ParentNamesFinal: set = set()
|
||
for _ParentF in (getattr(self, 'class_parents', {}).get(ClassName) or []):
|
||
for _Np, _Tp in self.class_members.get(_ParentF, []) or []:
|
||
_ParentNamesFinal.add(_Np)
|
||
if _members_final and _members_final[0][0] in _ParentNamesFinal:
|
||
pass # 已含展平继承字段 → 落入下方 fast-fail 暴露真实缺失
|
||
else:
|
||
# 单继承字段,偏移 = base(vtable 之后第一个位置)
|
||
return _BaseNow() + 0
|
||
# fast-fail(治本修复 2026-08-10):字段未找到不再静默返回 None。
|
||
# 静默 None 会让调用方(_gep_and_Load_member L26-27)直接 return None,
|
||
# AnnAssign InitValue 变 None → 变量 alloca 未初始化 → 运行期读垃圾指针
|
||
# (如 HandlesImports.HandleImport 的 imp.names → 0xffffffffffffffff 崩溃)。
|
||
# 抛出编译错误,暴露 class_members/struct 的真实状态便于定位根因。
|
||
_members_diag: list = self.class_members.get(ClassName, []) if ClassName else []
|
||
_struct_diag: str = 'not-registered'
|
||
if ClassName and ClassName in self.structs:
|
||
_st_diag = self.structs[ClassName]
|
||
if isinstance(_st_diag, ir.IdentifiedStructType):
|
||
_struct_diag = f"{len(_st_diag.elements)} elems" if _st_diag.elements is not None else 'opaque'
|
||
_member_names_diag: list = [n for n, _ in _members_diag][:16]
|
||
# ---- 取证(只读,不改变行为,2026-09-30)----
|
||
# 现场:CallGenDispatch.vp:787 `len_val.Ty.Pointee` 里 `.Pointee` 被判成在类
|
||
# 'ParamNode'(2 字段 Next/Ty)里找 —— 而 'Pointee' 本属 LLVMType 的 REnum
|
||
# 变体镜像字段。要一次定位"是谁把接收者记成了 ParamNode",报文需给出:
|
||
# ① 本类键 → struct 实例 IR 名(键/实例名不一致即"同名两份实例");
|
||
# ② **真正声明了该字段**的类键(唯一者即"本该用的类");
|
||
# ③ var_struct_class 里被登记为**本类**的变量(封顶 6 个)及其实际 pointee
|
||
# IR 名 —— 两者不一致即"记录类名与实际 IR 类型不符",直接圈定记录点。
|
||
_extra_diag: str = ''
|
||
try:
|
||
_st_key = self.structs.get(ClassName) if ClassName else None
|
||
if isinstance(_st_key, ir.IdentifiedStructType):
|
||
_extra_diag += (f"\n 本类键 '{ClassName}' → struct 实例 IR 名 "
|
||
f"'{getattr(_st_key, 'name', '?')}'")
|
||
_owners: list = []
|
||
for _ck2, _cm2 in self.class_members.items():
|
||
if _ck2 == ClassName:
|
||
continue
|
||
if any(_n2 == field_name for _n2, _ in (_cm2 or [])):
|
||
_owners.append(_ck2)
|
||
_extra_diag += (f"\n 声明了 '{field_name}' 的其它类键({len(_owners)} 个): "
|
||
f"{_owners[:8]}{' …' if len(_owners) > 8 else ''}")
|
||
_vsc: dict = getattr(self, 'var_struct_class', {}) or {}
|
||
_vars_here: list = [v for v, cn in _vsc.items() if cn == ClassName]
|
||
if _vars_here:
|
||
_vdesc: list = []
|
||
for _v2 in _vars_here[:6]:
|
||
_pointee_desc: str = '?'
|
||
for _src3 in (getattr(self, 'variables', {}) or {},
|
||
getattr(self, '_reg_values', {}) or {},
|
||
getattr(self, '_direct_values', {}) or {}):
|
||
_val3 = _src3.get(_v2)
|
||
if _val3 is not None and isinstance(getattr(_val3, 'type', None), ir.PointerType):
|
||
_pointee_desc = str(_val3.type.pointee)
|
||
break
|
||
_vdesc.append(f"{_v2}(pointee={_pointee_desc})")
|
||
_extra_diag += (f"\n var_struct_class 登记为 '{ClassName}' 的变量("
|
||
f"{len(_vars_here)} 个,示前 6): {_vdesc}")
|
||
except Exception as _e_diag:
|
||
_extra_diag += f"\n (取证附加信息生成失败: {type(_e_diag).__name__}: {_e_diag})"
|
||
# 异常类型:MemberLookupFailFast(KeyError 子类 + 裸消息 __str__)——
|
||
# 既保留 `except KeyError` 兜底点,又让报文不再被 repr 成"带引号 + \n 转义"
|
||
# 的不可读形式(原有 traceback.print_stack 调试转储一并移除,保持输出干净)。
|
||
raise MemberLookupFailFast(
|
||
f"[fast-fail] 属性 '{field_name}' 在类 '{ClassName}' 中找不到"
|
||
f"(class_members={len(_members_diag)} 个: {_member_names_diag}, struct={_struct_diag})"
|
||
f"{_extra_diag}"
|
||
)
|
||
|
||
def _resolve_class_for_var(self, var_name: str) -> str | None:
|
||
for ClassName in self.class_methods:
|
||
if var_name == ClassName or var_name.startswith(f"__with_{ClassName}_"):
|
||
return ClassName
|
||
if var_name in self.variables:
|
||
var: ir.Value = self.variables[var_name]
|
||
if isinstance(var.type, ir.PointerType):
|
||
for ClassName, struct_type in self.structs.items():
|
||
if var.type.pointee == struct_type:
|
||
return ClassName
|
||
if isinstance(var.type.pointee, ir.PointerType) and var.type.pointee.pointee == struct_type:
|
||
return ClassName
|
||
return None
|
||
|
||
def _infer_return_type(self, FuncName: str) -> ir.Type:
|
||
if FuncName in self.functions:
|
||
return self.functions[FuncName].type.pointee.return_type
|
||
# 也尝试混淆后的名称
|
||
mangled: str = self._mangle_func_name(FuncName)
|
||
if mangled != FuncName and mangled in self.functions:
|
||
return self.functions[mangled].type.pointee.return_type
|
||
if FuncName in self.known_return_types:
|
||
return self.known_return_types[FuncName]
|
||
if mangled != FuncName and mangled in self.known_return_types:
|
||
return self.known_return_types[mangled]
|
||
for ClassName, methods in self.class_methods.items():
|
||
for method in methods:
|
||
if FuncName == method:
|
||
return ir.VoidType()
|
||
if self._import_handler_ref:
|
||
try:
|
||
# 先用原始名称查找,再用混淆名称查找
|
||
stub_func_type: Any = self._import_handler_ref._LookupStubFuncType(FuncName, self)
|
||
if stub_func_type is None and mangled != FuncName:
|
||
stub_func_type = self._import_handler_ref._LookupStubFuncType(mangled, self)
|
||
if stub_func_type and hasattr(stub_func_type, 'return_type'):
|
||
return stub_func_type.return_type
|
||
except Exception as _e:
|
||
if _config_mode == "strict":
|
||
raise
|
||
_vlog().warning(f"查找桩函数返回类型失败: {_e}", "Exception")
|
||
return ir.IntType(32)
|
||
|
||
def _get_or_declare_func(self, name: str, func_type: ir.FunctionType) -> ir.Function:
|
||
for fname, fobj in self.functions.items():
|
||
if fname == name:
|
||
return fobj
|
||
if re.match(r'^[0-9a-f]{16,}\.' + re.escape(name) + r'$', fname):
|
||
return fobj
|
||
# module.globals 里可能已有同名函数而 Gen.functions 未登记(如
|
||
# BaseGen._get_or_declare_function 的历史路径):直接新建必抛 llvmlite
|
||
# DuplicatedNameError(NameScope 按名去重)⇒ 统一走幂等入口。
|
||
return self._CreateOrReuseFuncDecl(name, func_type)
|
||
|
||
def get_or_declare_c_func(self, name: str, fallback_type: ir.FunctionType | None = None) -> ir.Function | None:
|
||
if name in self.functions:
|
||
return self.functions[name]
|
||
for fname, fobj in self.functions.items():
|
||
if re.match(r'^[0-9a-f]{16,}\.' + re.escape(name) + r'$', fname):
|
||
return fobj
|
||
stub_func_type: ir.FunctionType | None = None
|
||
if self._import_handler_ref:
|
||
try:
|
||
stub_func_type = self._import_handler_ref._LookupStubFuncType(name, self)
|
||
except Exception as _e:
|
||
if _config_mode == "strict":
|
||
raise
|
||
_vlog().warning(f"查找桩函数类型失败: {_e}", "Exception")
|
||
if stub_func_type:
|
||
return self._CreateOrReuseFuncDecl(name, stub_func_type)
|
||
if fallback_type is not None:
|
||
return self._CreateOrReuseFuncDecl(name, fallback_type)
|
||
return None |