diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index f1ebcc5..2238834 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -650,7 +650,19 @@ class FuncGenMixin: # 配对类型必须同口径,否则该类字段会被整条跳过。 _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): + _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 布局 @@ -677,7 +689,35 @@ class FuncGenMixin: return _Cursor _Cursor += 1 if _ResolvedClass and _ResolvedClass in self.class_members: - for i, (name, _) in enumerate(self.class_members[_ResolvedClass]): + # 只有走到这里(布局配对未命中)才做一次重名检查:重名条目**不占布局槽**, + # 却会让 `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") + for i, (name, _) in enumerate(_MemList): if name == field_name: return _BaseNow() + i # 继承链回退:当字段不在当前类的 class_members 中时(常见于跨模块导入 diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index b745a4e..ac5c88b 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -614,6 +614,33 @@ class StructGenMixin: if ClassName in self.class_packed and not existing_st.packed: existing_st.packed = True continue + # 成员表**重名**时长度判据不可信:重名条目不是布局槽位(body 由定义模块的 + # stub 决定),却会让 `_expected_count_pre` 多算一项 ⇒ 误判"body 不完整" + # 而用带重复项的表重建 body(本模块布局比其它模块多一槽 ⇒ 跨模块 ABI 撕裂)。 + # 现场:astaux.ExceptHandler 表 10 项含两个 'children',而定义模块 stub 的 + # body 恰为 10 槽 = vtable + 9 个不同字段名。判据:表有重名**且** body 槽数 + # 恰等于「去重名数 + vtable」⇒ body 已是权威布局,只报事实、不重建。 + _uniq_names_pre: set[str] = {n for n, _ in _members_pre} + if (len(_uniq_names_pre) < len(_members_pre) + and len(existing_st.elements) == len(_uniq_names_pre) + (1 if _has_vtable_precheck else 0) + and _vtable_first_ok and not _has_t_placeholder_pre): + _DupRep: set = getattr(self, '_StructDupMembersReported', None) + if _DupRep is None: + _DupRep = set() + self._StructDupMembersReported = _DupRep + if ClassName not in _DupRep: + _DupRep.add(ClassName) + _vlog().warning( + f"[结构体布局] 类 '{ClassName}' 的成员表含**重名条目**:表 " + f"{len(_members_pre)} 项 / 去重 {len(_uniq_names_pre)} 项," + f"现有 body {len(existing_st.elements)} 槽恰等于「去重名数 + vtable」" + f"⇒ 认定 body 为权威布局(来自定义模块 stub),**不按重复表重建**" + f"(重建会多出一槽、与其它模块布局不一致)。" + f"请核对成员表为何重复登记同一字段名。", + "StructGen") + if ClassName in self.class_packed and not existing_st.packed: + existing_st.packed = True + continue # struct body 不完整,清除 elements 和 stub 缓存以便重新加载。 # 注意:本仓 llvmlite 版本的字段名是**公开属性 `elements`** # (types.py: `self.elements = None`;`is_opaque` = `elements is None`;