Files
TransPyC/lib/core/LLVMCG/FuncGen.py
T
TermiNexus f28ff1b414 fix(tpc): 字段偏移改为「布局真源优先」,成员表漂移不再整体平移
类别 a(编译器缺陷):_get_member_offset 返回 `base + 成员表下标`,但 class_members
是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载/重跑),而 struct
body 一旦 set_body 即**永久冻结** ⇒ 表与布局的序位一旦漂移,整类字段偏移整体平移。
现场(IR 实证):Converter 模块读 `Alias.name` 落在 struct 槽 9(= asname),而定义
模块 Alias.__init__ 写入槽 8;`import t` 无 as 子句 ⇒ asname=NULL ⇒ TransPyV StubGen
误报「import 别名 name 为空(_copy_str 返回 None)」并终止 includes/stdint.vp 的 vpi
生成。同族旧现场:ast.Constant.lineno 取到 pool 槽(已由成员偏移同源修复过)。

- StructGen._generate_structs:set_body 时把「成员名 → 槽位」映射冻结到 struct
  实例上(_tpc_member_slots / _tpc_member_base)。用**实例属性**而非全局表:同一
  类名可能存在不止一个 struct 实例,偏移必须与被 GEP 的那一份布局同源;仅当
  member_types 恰为 `[vtable?] + 全部成员` 时记录(位域类折叠为单一 storage 槽,
  其字段位置另由 class_member_bitoffsets 表达;stub 已置 body 的情形不记录)。
- FuncGen._get_member_offset:新增两条「只看布局」的路径,优先于原下标法——
  ① 冻结映射命中即返回;
  ② 用**类型**把成员表逐项配对到 elements(类型不匹配、重名的条目视为方法/占位/
     漂移项,不占槽位),配对中类型相等的槽位即字段真实槽位。
  表与布局自洽时两者给出与旧下标法逐字相同的结果;都覆盖不到才落回下标法(对既有
  正确行为零变化)。另在「配对结果 ≠ 下标法」时每类每字段告警一次,报出成员表项数
  与 struct 槽数,把漂移来源变成可见事实。

本地冒烟(三类,均通过):自洽表 8 / 漂移表(类型不匹配)8(旧下标法 9)/
全同型漂移且命中冻结映射 8。
2026-09-29 19:57:57 +08:00

941 lines
64 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
_NumMembers: int = len(self.class_members.get(_Cls, []))
_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 撞运气,分数明显更低;平手保留启发式。
_MembersCal: list = self.class_members.get(_Cls, [])
if _MembersCal and _NumElems > 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 _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()
# ---- 布局真源优先(配对式偏移):偏移只认"运行时真正被 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)
if isinstance(_Frozen, dict) 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)):
_Cursor: int = _BaseNow()
_ElemN: int = len(_OffEls)
_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]
if _mtEff is _etP or str(_mtEff) == str(_etP):
_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:
for i, (name, _) in enumerate(self.class_members[_ResolvedClass]):
if name == field_name:
return _BaseNow() + i
# 继承链回退:当字段不在当前类的 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:
for i, (name, _) in enumerate(_members):
if name == field_name:
_hit_idx = i
break
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]):
for i, (name, _) in enumerate(self.class_members[_k]):
if name == field_name:
return _BaseNow() + i
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:
for i, (name, _) in enumerate(self.class_members[short_name]):
if name == field_name:
return _BaseNow() + i
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:
for i, (name, _) in enumerate(self.class_members[ClassName]):
if name == field_name:
return _BaseNow() + i
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:
for i, (name, _) in enumerate(self.class_members[short_name]):
if name == field_name:
return _BaseNow() + i
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}'))):
for i, (name, _) in enumerate(self.class_members[cn_key]):
if name == field_name:
return _BaseNow() + i
# 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):
for i, (name, _) in enumerate(self.class_members[cn_key]):
if name == field_name:
return i
if short_name:
variant_prefix = f"{short_name}_"
for cn_key in self.class_members:
if cn_key.startswith(variant_prefix):
for i, (name, _) in enumerate(self.class_members[cn_key]):
if name == field_name:
return i
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}')):
for i, (name, _) in enumerate(members):
if name == field_name and i < len(struct_type.elements):
return i
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:
for i, (name, _) in enumerate(members):
if name == field_name and i < len(struct_type.elements):
return i
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:
for i, (name, _) in enumerate(self.class_members[short_cn]):
if name == field_name:
return _BaseNow() + i
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:
for i, (name, _) in enumerate(self.class_members[ClassName]):
if name == field_name:
return _BaseNow() + i
if short_name and short_name in self.class_members:
for i, (name, _) in enumerate(self.class_members[short_name]):
if name == field_name:
return _BaseNow() + i
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, [])
_num_members_final: int = len(_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(_members_final)
for _Ri, (_Rn, _Rt) in enumerate(_members_final):
if _Rn == field_name:
return _BaseNow() + _Ri
_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]
# 异常类型: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})"
)
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