fix(HandlesImports): 跨模块展平父类无条件 force 重载,消除并行 worker 布局不确定

并行 worker 中 class_members[Generator] 曾出现 83/85/88/89/105/106 vs 114
的不确定变体:父类在某次展平时祖先未加载完成即被冻结为部分成员,guard
(成员非空即跳过)又阻止重载 → this_adjust 按不完整 members 计算字节偏移
→ 继承方法 self 错位 → 运行时越界崩溃(FuncTest is_exception_name strcmp,
错位 68 字节)。

修复:
- 父类加载循环无条件 force 重载:_TryLoadClassMembersFromPyi(force=True)
  先清空该类 class_members/defaults/signeds/bitfields/byteorders/bitoffsets
  再重建,字段顺序与定义模块 stub 一致;对其祖先递归同样强制重载,
  最终所有 worker 展平结果确定性一致。
- 已加载父类经 class_sha1_map 直连定义模块 sha1 强制重载(O(1)),
  避免对每个父类全量扫描 ModuleSha1Map。
- _TryLoadClassMembersFromPyi 返回 bool(找到即停),移除 [DIAG-EXT] 诊断。
This commit is contained in:
TermiNexus committed 2026-08-18 14:38:06 +08:00
1 parent 858aba46d0
commit bdc5ec3e88
1 file changed
+86 -25
+86 -25
View File
@@ -1931,26 +1931,39 @@ class ImportHandle(BaseHandle):
_parent_all_placeholders: bool = bool(_parent_members_check) and all(
n.startswith('__inherited_') for n, _ in _parent_members_check
)
if not _parent_members_check or _parent_all_placeholders:
# 清除占位条目以便 _TryLoadClassMembersFromPyi 的 guard 通过
_old_parent_members: list = _parent_members_check
if _parent_all_placeholders:
Gen.class_members[_ParentClass] = []
_sha1_map: dict[str, str] = getattr(Gen, 'ModuleSha1Map', {})
_parent_loaded: bool = False
# 2026-08-18 关键修复:无条件 force 重载父类成员(此前仅当成员为空/
# 全占位时才加载)。并行 worker 中父类可能已存在"不完整"的成员表
# (此前某次展平时祖先未加载完成即冻结部分字段,guard 又阻止重载),
# 导致 class_members[Generator] 83~106 vs 114 不确定 → this_adjust
# 错位 → 继承方法 self 偏移错误 → 运行时越界崩溃。force=True 会先
# 清空再重建(缺啥补啥、顺序与定义模块一致),且 _EmitExternalClassDeclLlvm
# 对其祖先递归同样强制重载,最终所有 worker 展平结果确定性一致。
_old_parent_members: list = _parent_members_check
if _parent_all_placeholders:
Gen.class_members[_ParentClass] = []
_sha1_map: dict[str, str] = getattr(Gen, 'ModuleSha1Map', {}) or {}
_parent_found: bool = False
# 已加载过的父类直接用其定义模块 sha1 强制重载(O(1)),
# 避免对每个父类线性扫描全部 ModuleSha1Map。
_parent_sha1: str | None = getattr(Gen, 'class_sha1_map', {}).get(_ParentClass)
if _parent_sha1:
try:
_parent_found = self._TryLoadClassMembersFromPyi(_ParentClass, f"{_parent_sha1}.stub.ll", Gen, force=True)
except Exception:
_parent_found = False
else:
for _mod_name, _mod_sha1 in _sha1_map.items():
_stub_name: str = f"{_mod_sha1}.stub.ll"
try:
self._TryLoadClassMembersFromPyi(_ParentClass, _stub_name, Gen)
_parent_found = self._TryLoadClassMembersFromPyi(_ParentClass, _stub_name, Gen, force=True)
except Exception:
pass
_parent_members_check = Gen.class_members.get(_ParentClass, [])
if _parent_members_check and not all(n.startswith('__inherited_') for n, _ in _parent_members_check):
_parent_loaded = True
_parent_found = False
if _parent_found:
break
if not _parent_loaded and _old_parent_members:
# 加载失败,回滚到旧占位条目(有总比没有好)
Gen.class_members[_ParentClass] = _old_parent_members
_parent_members_check = Gen.class_members.get(_ParentClass, [])
if not _parent_found and _old_parent_members:
# 加载失败(父类不在任何 pyi 中,如泛型特化基类),回滚旧条目(有总比没有好)
Gen.class_members[_ParentClass] = _old_parent_members
if _ParentClass in Gen.class_members:
# 按继承源去重(与 HandlesClassDef._FlattenInherited 一致,2026-08-18):
# 菱形继承(Generator 同时继承 ClassGenMixin/ClassGenEnum/.../ClassGenBase,
@@ -2257,11 +2270,48 @@ class ImportHandle(BaseHandle):
except KeyError:
pass
def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin) -> None:
def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin,
force: bool = False) -> bool:
"""从 .vpi/.pyi 声明文件加载类成员。返回 True 表示在 pyi 中找到该类并完成处理。
force=True 时(跨模块展平父类的强制重载):先清空该类的成员数据再重建,
保证并行 worker 间展平结果确定性一致(缺啥补啥、字段顺序与定义模块一致),
避免此前"部分成员被冻结、guard 阻止重载"导致的 class_members[Generator]
83~106 vs 114 不确定变体(this_adjust 错位崩溃)。
"""
ProjectRoot = self._find_project_root()
temp_dir = os.path.join(ProjectRoot, 'temp')
if class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0:
return
if (not force) and class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0:
# guard 命中:class_members 已存在(可能从别处预置、不完整),但
# class_parents 可能未设置 → 多继承展平(_FlattenExt)的递归/anc_names
# 失效 → 菱形去重失败 → class_members 不完整(Generator 89/83 vs 114)
# → this_adjust 错位崩溃。此处补解析 .vpi 设置 class_parents(不重载
# class_members,避免覆盖可能更完整的已有数据)。
try:
_pyi_b0: str = stub_filename.replace('.stub.ll', '.vpi')
_path0: str = os.path.join(temp_dir, _pyi_b0)
if os.path.isfile(_path0):
_tree0: ast.AST | None = getattr(self, '_pyi_cache', {}).get(_path0)
if _tree0 is None:
with open(_path0, 'r', encoding='utf-8') as _fh0:
_tree0 = ast.parse(_fh0.read())
getattr(self, '_pyi_cache', {})[_path0] = _tree0
if _tree0 is not None:
for _n0 in _tree0.body:
if isinstance(_n0, ast.ClassDef) and _n0.name == class_name:
_b0: list[str] = []
for _bb in _n0.bases:
_bn0: str | None = getattr(_bb, 'id', None) or getattr(_bb, 'attr', None)
if _bn0 and _bn0 not in ('Object', 'CVTable', 'NoVTable', 'CStruct', 'CUnion', 'CEnum', 'REnum', 'Exception', 'Enum'):
_b0.append(_bn0)
if _b0:
if not hasattr(Gen, 'class_parents'):
Gen.class_parents = {}
Gen.class_parents[class_name] = _b0
break
except Exception:
pass
return False
pyi_basename = stub_filename.replace('.stub.ll', '.vpi')
pyi_path = os.path.join(temp_dir, pyi_basename)
if not os.path.isfile(pyi_path):
@@ -2276,18 +2326,18 @@ class ImportHandle(BaseHandle):
if pyi_path not in self._pyi_cache:
if not os.path.isfile(pyi_path):
self._pyi_cache[pyi_path] = None
return
return False
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
self._pyi_cache[pyi_path] = ast.parse(f.read())
except Exception as e:
_vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e)
self._pyi_cache[pyi_path] = None
return
return False
pyi_tree = self._pyi_cache[pyi_path]
if pyi_tree is None:
return
return False
source_sha1 = stub_filename.replace('.stub.ll', '')
module_name = None
if hasattr(Gen, 'ModuleSha1Map'):
@@ -2319,11 +2369,22 @@ class ImportHandle(BaseHandle):
found = True
break
if not found:
return
return False
if force:
# 强制重载:清空按类缓存的成员数据,重建出与定义模块一致的完整布局。
# 不清空则 dedup 只在末尾追加缺失字段 → 字段顺序与 struct elements 错位。
Gen.class_members[class_name] = []
for _gd_attr in ('class_member_defaults', 'class_member_signeds', 'class_member_bitfields',
'class_member_byteorders', 'class_member_bitoffsets'):
_gd: dict | None = getattr(Gen, _gd_attr, None)
if _gd is not None:
_gd.pop(class_name, None)
if hasattr(Gen, 'class_parents'):
Gen.class_parents.pop(class_name, None)
if hasattr(Gen, 'class_parent'):
Gen.class_parent.pop(class_name, None)
self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, source_sha1=source_sha1)
if class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0:
return
return
return True
def _BuildScalarConstant(self, value_node: ast.AST, target_type: ir.Type, Gen: LlvmGeneratorMixin) -> ir.Constant | None:
"""Build a scalar constant value from AST node, delegated to unified method."""