diff --git a/lib/core/Handles/HandlesExprAttr.py b/lib/core/Handles/HandlesExprAttr.py index 3c53ea0..2bec0c1 100644 --- a/lib/core/Handles/HandlesExprAttr.py +++ b/lib/core/Handles/HandlesExprAttr.py @@ -708,7 +708,17 @@ class ExprAttrHandle(BaseHandle): Gen.class_member_bitoffsets[ClassName][AttrName] = bo return self._load_bitfield_member(ObjVal, ClassName, AttrName) - offset = Gen._get_member_offset(AttrName, ClassName) + # 偏移与 GEP 必须**同源**:把"实际将被索引的那个 struct 实例"传给偏移计算。 + # 同一类键上可能存在两个实例(同名类 sha1 冲突双注册 / 一方仍 opaque), + # 用类键上那份算偏移、用 ObjVal 这份做 GEP ⇒ 整类字段偏移平移 + # (现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*) → 第 6 个实参被报 + # "指针不能隐式转整数 i32")。_member_base 收到不同实例时会报出事实。 + _StructHint: Any = None + if isinstance(ObjVal.type, ir.PointerType): + _ObjPointee: Any = ObjVal.type.pointee + if isinstance(_ObjPointee, ir.IdentifiedStructType): + _StructHint = _ObjPointee + offset = Gen._get_member_offset(AttrName, ClassName, _StructHint) # 检查 offset 是否超出实际结构体 if isinstance(ObjVal.type, ir.PointerType): actual_pointee: Any = ObjVal.type.pointee diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index ad3a49a..ca6e902 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -351,9 +351,17 @@ class FuncGenMixin: _Cls = _hit return _Cls - def _member_base(self, ClassName: str | None) -> int: + 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`——旧实现只认这两个装饰器集合, @@ -393,7 +401,34 @@ class FuncGenMixin: _vlog().warning(f"补齐类成员展平失败: {_e}", "Exception") _ByInheritVtable: bool = self._class_has_vtable(_Cls) base: int = 1 if _ByInheritVtable else 0 - _StructBase: ir.IdentifiedStructType | None = self.structs.get(_Cls) + # 布局来源:优先调用方**实际 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) @@ -445,7 +480,8 @@ class FuncGenMixin: base = _BestB return base - def _get_member_offset(self, field_name: str, ClassName: str | None = None) -> int | None: + 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 键"这一角落改变取值)。 @@ -500,7 +536,7 @@ class FuncGenMixin: # 偏移整体差 1(ast.ExceptHandler.children 落到 end_col_offset(i32), # len 分派拿到 i32)。 def _BaseNow() -> int: - return self._member_base(_MemberBaseKey) + return self._member_base(_MemberBaseKey, StructHint) # 预热(与原行为逐字一致):先触发一次 EnsureClassMembersFlattened, # 再决定下面的懒加载。 _BaseNow()