From 8783340385f8884d268633151640df4bb37c8061 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 10 Aug 2026 19:11:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(TPC):=20=E5=AE=9E=E7=8E=B0=20isinstance=20?= =?UTF-8?q?=E5=86=85=E5=BB=BA=20+=20=E5=A4=9A=E7=BB=A7=E6=89=BF=20this=5Fa?= =?UTF-8?q?djust=20=E5=AD=97=E8=8A=82=E5=81=8F=E7=A7=BB=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isinstance 沿 class_parents 反向 BFS 收集目标类全部子类 vtable,运行时比较对象头部 vtable 指针,修复"目标是实际类祖先"场景误判;this_adjust 改用字节偏移(字段在子类与父类自身布局的字节差)修正第二父类方法体内二次 GEP 越界读。新增 Test/IsinstanceTest 覆盖 5 大场景 14 断言。 --- Test/IsinstanceTest/App/main.vp | 127 +++++++++++++++++ Test/IsinstanceTest/project.json | 29 ++++ lib/core/Handles/HandlesExprBuiltin.py | 183 ++++++++++++++++++++----- lib/core/Handles/HandlesExprCall.py | 71 ++++++++-- 4 files changed, 363 insertions(+), 47 deletions(-) create mode 100644 Test/IsinstanceTest/App/main.vp create mode 100644 Test/IsinstanceTest/project.json diff --git a/Test/IsinstanceTest/App/main.vp b/Test/IsinstanceTest/App/main.vp new file mode 100644 index 0000000..95a94f2 --- /dev/null +++ b/Test/IsinstanceTest/App/main.vp @@ -0,0 +1,127 @@ +from stdint import * +import t, c +import testcheck + + +# ============================================================ +# isinstance 内建函数测试(运行时 vtable 继承链比较) +# +# 场景覆盖: +# 1. CVTable 自身/子类→基类 +# 2. 运行时多态(基类指针指向子类对象) +# 3. 元组 (A, B) +# 4. 多继承(AB 同时是 A 和 B 的实例) +# 5. NoVTable 类(编译时静态判断) +# ============================================================ + + +# ========== CVTable 继承 ========== + +@t.CVTable +class Animal: + Name: str + + def __init__(self, name: str): + self.Name = name + + def speak(self) -> str: + return "..." + + +@t.CVTable +class Dog(Animal): + def __init__(self, name: str): + self.Name = name + + def speak(self) -> str: + return "Woof" + + +@t.CVTable +class Cat(Animal): + def speak(self) -> str: + return "Meow" + + +# ========== 多继承 ========== + +@t.CVTable +class A: + a: CInt + + def get_a(self) -> CInt: + return self.a + + +@t.CVTable +class B: + b: CInt + + def get_b(self) -> CInt: + return self.b + + +@t.CVTable +class AB(A, B): + def __init__(self, a: CInt, b: CInt): + self.a = a + self.b = b + + +# ========== NoVTable(编译时判断) ========== + +@t.NoVTable +class Plain: + v: CInt + + +def main() -> CInt | CExport: + testcheck.begin("IsinstanceTest: isinstance 内建函数测试") + + # --- 1. 自身类型 + 子类→基类 --- + d: Dog | CPtr = Dog("dog") + testcheck.check(isinstance(d, Dog) == 1, "isinstance(d, Dog) 自身 OK", + "isinstance(d, Dog) FAILED") + testcheck.check(isinstance(d, Animal) == 1, "isinstance(d, Animal) 子类→基类 OK", + "isinstance(d, Animal) FAILED") + testcheck.check(isinstance(d, Cat) == 0, "isinstance(d, Cat) 不匹配 OK", + "isinstance(d, Cat) FAILED") + + # --- 2. 运行时多态:基类指针指向子类对象 --- + a: Animal | CPtr = (Animal | CPtr)(d) + testcheck.check(isinstance(a, Dog) == 1, + "isinstance(Animal*, Dog 对象) 运行时多态 OK", + "isinstance(Animal*, Dog) FAILED") + testcheck.check(isinstance(a, Animal) == 1, + "isinstance(Animal*, Animal) 基类自身 OK", + "isinstance(Animal*, Animal) FAILED") + testcheck.check(isinstance(a, Cat) == 0, + "isinstance(Animal*, Cat) 运行时 False OK", + "isinstance(Animal*, Cat) FAILED") + + # --- 3. 元组 --- + testcheck.check(isinstance(d, (Cat, Dog)) == 1, "isinstance 元组命中 OK", + "isinstance 元组 FAILED") + testcheck.check(isinstance(d, (Cat, A)) == 0, "isinstance 元组未命中 OK", + "isinstance 元组未命中 FAILED") + + # --- 4. 多继承 --- + ab: AB | CPtr = AB(1, 2) + testcheck.check(isinstance(ab, A) == 1, "isinstance(AB, A) 多继承 OK", + "isinstance(AB, A) FAILED") + testcheck.check(isinstance(ab, B) == 1, "isinstance(AB, B) 多继承 OK", + "isinstance(AB, B) FAILED") + testcheck.check(isinstance(ab, Animal) == 0, "isinstance(AB, Animal) False OK", + "isinstance(AB, Animal) FAILED") + testcheck.check(ab.get_a() == 1 and ab.get_b() == 2, "AB 字段/方法正常 OK", + "AB 字段/方法 FAILED") + + # --- 5. NoVTable(编译时静态判断) --- + p: Plain | CPtr = Plain() + p.v = 42 + testcheck.check(isinstance(p, Plain) == 1, "isinstance NoVTable 编译时 OK", + "isinstance NoVTable FAILED") + testcheck.check(p.v == 42, "NoVTable 字段正常 OK", "NoVTable 字段 FAILED") + + testcheck.ok("isinstance 全部测试完成") + return testcheck.end() diff --git a/Test/IsinstanceTest/project.json b/Test/IsinstanceTest/project.json new file mode 100644 index 0000000..2298d59 --- /dev/null +++ b/Test/IsinstanceTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "IsinstanceTest", + "version": "1.0.0", + "source_dir": "./App", + "build_dir": "./.tpv_build", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-relocation-model=pic"] + }, + "linker": { + "cmd": "clang++", + "flags": ["-Wl,--allow-multiple-definition", "-Wl,--unresolved-symbols=ignore-in-object-files", "-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-luser32", "-latomic"], + "output": "IsinstanceTest.exe" + }, + "includes": [ + "../../includes" + ], + "target": { + "triple": "x86_64-pc-windows-gnu", + "datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "sha1_slice_level": 1, + "target": "llvm", + "strict_mode": true + } +} diff --git a/lib/core/Handles/HandlesExprBuiltin.py b/lib/core/Handles/HandlesExprBuiltin.py index ebc337b..c4e67a4 100644 --- a/lib/core/Handles/HandlesExprBuiltin.py +++ b/lib/core/Handles/HandlesExprBuiltin.py @@ -166,38 +166,11 @@ class ExprBuiltinHandle(BaseHandle): elif FuncName == 'input': return self._HandleInputLlvm(Node) elif FuncName == 'isinstance': - # isinstance(obj, Type) — 静态类型系统下编译时解析 - # TransPyC 是静态类型系统,运行时无 RTTI。 - # 对于编译时已知类型,若变量类型是目标类型的子类则生成 true,否则 false。 - # 若无法确定(如基类指针指向子类),保守生成 false。 - # 注意:这主要让编译时辅助方法(如 c.py 中的 Asm._parse_asm_descr)能通过编译, - # 这些方法不在运行时被调用,行为不正确不影响程序。 - if len(Node.args) >= 2: - ObjVal: ir.Value | None = self.HandleExprLlvm(Node.args[0]) - TypeNode: ast.expr = Node.args[1] - TargetClassName: str = '' - if isinstance(TypeNode, ast.Name): - TargetClassName = TypeNode.id - elif isinstance(TypeNode, ast.Attribute): - TargetClassName = TypeNode.attr - if ObjVal is not None and TargetClassName: - ObjType: ir.Type = ObjVal.type - # 解引用指针获取 pointee 类型 - if isinstance(ObjType, ir.PointerType): - ObjType = ObjType.pointee - # 检查 pointee 是否是目标结构体类型 - if isinstance(ObjType, ir.IdentifiedStructType): - ObjStructName: str = getattr(ObjType, 'name', '') - ShortName: str = ObjStructName.split('.')[-1] if '.' in ObjStructName else ObjStructName - if ShortName == TargetClassName: - return ir.Constant(ir.IntType(1), 1) - # 检查 Gen.var_struct_class - if isinstance(Node.args[0], ast.Name): - VarClassName: str | None = Gen.var_struct_class.get(Node.args[0].id) - if VarClassName == TargetClassName: - return ir.Constant(ir.IntType(1), 1) - return ir.Constant(ir.IntType(1), 0) - return ir.Constant(ir.IntType(1), 0) + # isinstance(obj, Type) — 运行时 vtable 继承链比较(2026-08-10) + # CVTable 对象头部 vtable 指针唯一标识类;isinstance = 对象 vtable + # ∈ 目标类及其 MRO 祖先的 vtable 集合(运行时真多态判断)。 + # NoVTable/基础类型/无法解析时回退编译时静态判断。 + return self._HandleIsinstanceLlvm(Node) elif FuncName == 'ord': if Node.args: return self._HandleOrdLlvm(Node.args[0]) @@ -228,6 +201,152 @@ class ExprBuiltinHandle(BaseHandle): return self._HandleTypeLlvm(Node) return None + # ============================================================ + # isinstance - 运行时 vtable 子类集合比较(2026-08-10) + # + # CVTable 对象头部 vtable 指针唯一标识对象**实际类**(创建对象时写入 + # 实际类的 vtable)。isinstance(obj, Cls) 语义 = Cls 是实际类的祖先 + # (实际类 == Cls 或继承自 Cls)。运行时只能拿到实际类 vtable 地址, + # 无法向上推导祖先链,因此在编译期沿 class_parents 反向 BFS 枚举 + # Cls 的全部已知子类(含自身),比较 obj->vtable ∈ {Cls 子类 vtable}。 + # class_parents 由 Phase1 声明阶段对全部可达文件填充,子类集合完整。 + # NoVTable / 基础类型 / 无 vtable 可解析 → 回退编译时静态判断。 + # ============================================================ + def _HandleIsinstanceLlvm(self, Node: ast.Call) -> ir.Value: + """isinstance(obj, Cls):运行时 vtable 继承链比较(元组 Cls 支持 or 合并)""" + Gen: LlvmGeneratorMixin = self.Trans.LlvmGen + if len(Node.args) < 2: + return ir.Constant(ir.IntType(1), 0) + ObjVal: ir.Value | None = self.HandleExprLlvm(Node.args[0]) + if ObjVal is None: + return ir.Constant(ir.IntType(1), 0) + ClsNames: list[str] = self._ExtractIsinstanceClassNames(Node.args[1]) + if not ClsNames: + return ir.Constant(ir.IntType(1), 0) + Res: ir.Value | None = None + for CN in ClsNames: + R: ir.Value = self._IsinstanceOne(Gen, ObjVal, CN) + if Res is None: + Res = R + else: + Res = Gen.builder.or_(Res, R, name="isinstance_or") + return Res if Res is not None else ir.Constant(ir.IntType(1), 0) + + def _ExtractIsinstanceClassNames(self, TypeNode: ast.expr) -> list[str]: + """从 isinstance 第二参数提取类名列表(支持单类 / (A, B) 元组 / 泛型特化)""" + if isinstance(TypeNode, ast.Tuple): + names: list[str] = [] + for elt in TypeNode.elts: + names.extend(self._ExtractIsinstanceClassNames(elt)) + return names + if isinstance(TypeNode, ast.Name): + return [TypeNode.id] + if isinstance(TypeNode, ast.Attribute): + return [TypeNode.attr] + if isinstance(TypeNode, ast.Subscript): + spec = self.Trans.ExprCallHandle._ResolveGenericSlice(TypeNode) + if spec: + return [spec] + return [] + + def _IsinstanceOne(self, Gen, ObjVal: ir.Value, ClassName: str) -> ir.Value: + """单类判断:优先运行时 vtable 比较,失败回退编译时静态判断""" + VtableAddrs: list = self._CollectSubclassVtables(Gen, ClassName) + if VtableAddrs: + try: + return self._EmitVtableCompare(Gen, ObjVal, VtableAddrs) + except Exception: + pass + return self._IsinstanceCompileTime(Gen, ObjVal, ClassName) + + def _CollectSubclassVtables(self, Gen, ClassName: str) -> list: + """收集目标类及**所有已知子类**的 vtable 全局地址(沿 class_parents 反向 BFS)。 + + isinstance(obj, Cls) 的正确语义:obj 头部 vtable 标识对象实际类 C, + 判断 Cls 是否 ∈ C 的祖先链。运行时无法从 C_vtable 向上推导祖先, + 因此在编译期反向枚举 Cls 的子树(含 Cls 自身)的 vtable 地址, + 运行时比较 obj->vtable ∈ 该集合(C 是 Cls 子类 → 命中)。 + """ + addrs: list = [] + # 反向子类表:父类名 -> [子类名...](class_parents 由 Phase1 全量填充) + children: dict[str, list[str]] = {} + for cn, parents in getattr(Gen, 'class_parents', {}).items(): + for p in parents: + children.setdefault(p, []).append(cn) + queue: list[str] = [ClassName] + seen: set[str] = set() + while queue: + cn: str = queue.pop(0) + if cn in seen: + continue + seen.add(cn) + vt = self._ResolveVtableAddr(Gen, cn) + if vt is not None: + addrs.append(vt) + for ch in children.get(cn, []): + if ch not in seen: + queue.append(ch) + return addrs + + def _ResolveVtableAddr(self, Gen, ClassName: str): + """解析类 ClassName 的 vtable 全局地址(本地 Vtables / 跨模块外部引用), + 无 vtable(NoVTable / 未注册)返回 None。 + """ + vt = Gen.Vtables.get(ClassName) if hasattr(Gen, 'Vtables') else None + if vt is not None: + return vt + sha1_map: dict = getattr(Gen, 'class_sha1_map', {}) + sha1 = sha1_map.get(ClassName) + if not sha1: + return None + vname: str = f"{sha1}.{ClassName}_Vtable" + if vname in Gen.module.globals: + return Gen.module.globals[vname] + try: + ext = ir.GlobalVariable(Gen.module, ir.ArrayType(ir.PointerType(ir.IntType(8)), 1), name=vname) + ext.linkage = 'external' + return ext + except Exception: + return None + + def _EmitVtableCompare(self, Gen, ObjVal: ir.Value, VtableAddrs: list) -> ir.Value: + """运行时:读对象头部 vtable 指针,与集合中每个 vtable 地址比较""" + ObjPtr: ir.Value = ObjVal + if isinstance(ObjVal.type, ir.PointerType) and isinstance(ObjVal.type.pointee, ir.PointerType): + # 二级指针(变量 alloca / 联合 Class|t.CPtr)→ 先 load 出对象指针 + ObjPtr = Gen._load(ObjVal, name="isinstance_load") + if not isinstance(ObjPtr.type, ir.PointerType): + return self._IsinstanceCompileTime(Gen, ObjVal, '') + ObjAsVt: ir.Value = Gen.builder.bitcast(ObjPtr, ir.PointerType(ir.PointerType(ir.IntType(8))), name="isinstance_cast") + VtPtr: ir.Value = Gen._load(ObjAsVt, name="isinstance_vt") + Res: ir.Value | None = None + for addr in VtableAddrs: + AddrI8: ir.Value = Gen.builder.bitcast(addr, ir.PointerType(ir.IntType(8)), name="vt_addr") + Cmp: ir.Value = Gen.builder.icmp_unsigned('==', VtPtr, AddrI8, name="isinstance_cmp") + if Res is None: + Res = Cmp + else: + Res = Gen.builder.or_(Res, Cmp, name="isinstance_or") + return Res if Res is not None else ir.Constant(ir.IntType(1), 0) + + def _IsinstanceCompileTime(self, Gen, ObjVal: ir.Value, ClassName: str) -> ir.Value: + """编译时静态判断:obj 静态类型 == ClassName 或其 MRO 后代 → 1,否则 0""" + if ClassName: + ObjType: ir.Type = ObjVal.type + if isinstance(ObjType, ir.PointerType): + ObjType = ObjType.pointee + if isinstance(ObjType, ir.IdentifiedStructType): + ShortName: str = Gen._extract_short_name(ObjType.name) + mro: list = Gen._get_mro_chain(ClassName) if hasattr(Gen, '_get_mro_chain') else [ClassName] + if ShortName == ClassName or ShortName in mro: + return ir.Constant(ir.IntType(1), 1) + # obj 是 ClassName 的子类(obj 的 MRO 含 ClassName) + obj_mro: list = Gen._get_mro_chain(ShortName) if hasattr(Gen, '_get_mro_chain') else [] + if ClassName in obj_mro: + return ir.Constant(ir.IntType(1), 1) + # 无法判断 → 保守 false + return ir.Constant(ir.IntType(1), 0) + def _HandlePrintLlvm(self, Node: ast.Call) -> ir.Value | None: Gen: LlvmGeneratorMixin = self.Trans.LlvmGen printf_func: ir.Function = Gen.get_or_declare_c_func('printf', ir.FunctionType(ir.IntType(32), [ir.PointerType(ir.IntType(8))], var_arg=True)) diff --git a/lib/core/Handles/HandlesExprCall.py b/lib/core/Handles/HandlesExprCall.py index 9b4f422..eb2b30c 100644 --- a/lib/core/Handles/HandlesExprCall.py +++ b/lib/core/Handles/HandlesExprCall.py @@ -2369,22 +2369,57 @@ class ExprCallHandle(BaseHandle): # 失败时恢复 declare_only 标记,避免后续调用跳过补发 Gen._generic_class_specializations[spec_key] = (cached_name, True) + def _compute_field_byte_offset(self, Gen: LlvmGeneratorMixin, ClassName: str, field_name: str) -> int | None: + """计算字段在 struct 中的字节偏移(按 LLVM 布局含对齐)。 + + 遍历 struct elements 带对齐累加(与 _get_struct_size/_get_align 一致)。 + 有 vtable 的类 struct 首元素为 vtable 指针(i8*),class_members 不含它。 + 返回 None 表示无法计算(struct 未定义 / 字段不存在)。 + """ + st = Gen.structs.get(ClassName) + if not st or not isinstance(st, ir.IdentifiedStructType) or st.elements is None: + return None + members: list = Gen.class_members.get(ClassName, []) + has_vtable: bool = (ClassName in Gen.class_vtable + or ClassName in Gen._cross_module_vtable_classes) + base: int = 1 if has_vtable else 0 + idx: int | None = None + for i, (name, _) in enumerate(members): + if name == field_name: + idx = base + i + break + if idx is None or idx >= len(st.elements): + return None + offset: int = 0 + for i in range(idx): + et = st.elements[i] + align: int = Gen._get_align(et) + size: int = Gen._get_struct_size(et) + if align > 1: + offset = ((offset + align - 1) // align) * align + offset += size + align = Gen._get_align(st.elements[idx]) + if align > 1: + offset = ((offset + align - 1) // align) * align + return offset + def _adjust_this_for_inherited_method(self, Gen: LlvmGeneratorMixin, ObjVal: ir.Value, ClassName: str, method_anc: str | None) -> ir.Value: """多继承 this 调整(与 TPV _translate_method_call 一致): - 继承方法定义在"无 vtable 的父类"且该父类字段区在子类中偏移 > 0 时 - (如 C(A,B).get_b,B 的字段区从 b 开始),父类方法按自身布局读 - self.field@0 会读到前面父类的字段。调整:GEP 到父类第一个用户字段 - 在子类中的位置(索引 > 0),bitcast 成父类指针作为 self。 - 有 vtable 的父类布局是子类前缀(vptr 共享),self 无需调整。 + 继承方法体按"定义类自身布局"读 self 字段(如 B.get_b 内 + GEP(self_B, [0, 1]),字节偏移为 B 布局中 b 的 offset 8)。父类字段区 + 在子类中的**字节偏移**可能不同(如 AB={i8*,a@8,b@12} 中 b 偏移 12), + self 需按字节差前移,使父类方法内二次 GEP 恰好落到子类字段。 + + 修复(2026-08-10):字段索引法在"父类与子类元素大小不同"时错位 + (GEP 索引按元素个数计,非字节等价)。改用字节偏移: + delta = 字段在子类中字节偏移 − 字段在父类自身布局中字节偏移 + delta==0(第一父类,字段区前缀对齐)无需调整;delta>0 时按字节 + GEP 到父类字段区在子类中的等效基址,再 bitcast 成父类指针。 """ if method_anc is None or method_anc == ClassName: return ObjVal - anc_has_vtable: bool = (method_anc in Gen.class_vtable - or method_anc in Gen._cross_module_vtable_classes) - if anc_has_vtable: - return ObjVal anc_members: list = Gen.class_members.get(method_anc, []) if not anc_members: return ObjVal @@ -2396,15 +2431,21 @@ class ExprCallHandle(BaseHandle): break if first_field_name is None: return ObjVal - field_idx: int | None = Gen._get_member_offset(first_field_name, ClassName) - if field_idx is None or field_idx <= 0: + sub_off: int | None = self._compute_field_byte_offset(Gen, ClassName, first_field_name) + if sub_off is None: + return ObjVal + anc_off: int | None = self._compute_field_byte_offset(Gen, method_anc, first_field_name) + if anc_off is None: + return ObjVal + delta: int = sub_off - anc_off + if delta <= 0: return ObjVal if method_anc not in Gen.structs: return ObjVal - adj: ir.Value = Gen.builder.gep( - ObjVal, - [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), field_idx)], - name=f"this_adjust_{method_anc}") + obj_i8: ir.Value = Gen.builder.bitcast(ObjVal, ir.PointerType(ir.IntType(8)), + name=f"this_adjust_i8_{method_anc}") + adj: ir.Value = Gen.builder.gep(obj_i8, [ir.Constant(ir.IntType(64), delta)], + name=f"this_adjust_{method_anc}") return Gen.builder.bitcast(adj, ir.PointerType(Gen.structs[method_anc]), name=f"this_adjust_cast_{method_anc}")