From b652634356c8dc0e3e959f1e2d18303760449b9c Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 17:09:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E8=A3=B8=E8=B5=8B=E5=80=BC=20RHS?= =?UTF-8?q?=20=E6=98=AF"=E8=81=94=E5=90=88=E6=B3=A8=E8=A7=A3=E8=BF=94?= =?UTF-8?q?=E5=9B=9E"=E7=9A=84=E8=B0=83=E7=94=A8=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E4=BB=8E=E8=BF=94=E5=9B=9E=E6=B3=A8=E8=A7=A3=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E8=A2=AB=E6=8C=87=E7=B1=BB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:a(编译器缺陷)—— 类型信息被静默丢成通用指针 i8*,后果在数行之后以看不出真因 的报文暴露。 现场(Test/FakeDuckTest/App/main.vp:111/115): `parts = s1.split(',')` → `p0: str = parts[0]` 报"整数隐式转指针已禁止…出错表达式: 下标表达式 Subscript"。 证据链(三段各有代码/探针依据): 1. `_fakeduck.vp:273` 的 `def split(...) -> list[str]*`:只读探针确认 `list[str]*` 与 `list[str] | t.CPtr` 的 AST **逐字同形** (`BinOp(BitOr, Subscript(Name(list)), Attribute(t.CPtr))`)⇒ 注解写法没问题。 2. 该联合形态在 LLVM 层映射成**通用指针 i8***(`list[T] | t.CPtr` 的既有约定, 见 HandlesAnnAssign:372-383)。 3. `HandlesAssign` 原有的类名推断整块以 `Value.type.pointee` 是结构体为前提 (:186 的 `isinstance(..., (LiteralStructType, IdentifiedStructType))`)⇒ i8* 直接 整块跳过 ⇒ `var_struct_class['parts']` 从不登记 ⇒ `parts[0]` 不走 `list[str].__getitem__`,退化成通用指针/字节索引 ⇒ 结果类型是整数 i8 ⇒ 赋给 `str` 时触发 int→ptr 拦截。真因位置比报文位置早 4 行。 修复(编译器侧,不要求源码补注解): - `HandlesExprCall._CallReturnClass(Node, Gen)`:从被调者的 **returns 注解**取回被指 类名。被调者候选键覆盖三种既有形态(`Name` 函数名、`<接收者类>.<方法>`、 FakeDuck 影子类 `_str`/`_bytes`、裸 `<方法>`),取第一个能解析出类名的。 - `HandlesExprCall._ClassFromAnnotation(Ann, Gen)`:剔除 `| t.CPtr` 侧取核类型, 泛型 Subscript 走 `_resolve_generic_slice_type` 构特化名(`list[str]`),并要求该名 能在 `Gen.structs` / `Gen.class_members` 命中(含 sha1 前缀全名的短名归一); 取不到一律返回 None —— **不硬塞名字**。 - `HandlesAssign`:仅当"RHS 是调用 + 值类型是通用指针(pointee 非结构体)+ var_struct_class 尚无该变量"这三个条件同时成立时,才据上恢复登记 `var_struct_class` / `global_struct_class` / `_var_to_heap_ptr`。其余路径逐字不变。 验证:只读探针覆盖 7 种注解形态(`list[str]*` / `list[str] | t.CPtr` / `Foo*` / `Foo | t.CPtr` / `ast.AST*` …),提取结果全部符合预期(未命中类名时返回 None); GetDiagnostics 干净。未跑编译器。 遗留(同族,下一轮):上游"联合注解→i8*"这一步丢类名才是真根因;本次是在调用点 恢复。另外 `parts.__len__()` 在类名缺失时可能被 FakeDuck `_str.__len__` 错接, 修完建议跑一次该测试确认 `split len OK`。 --- lib/core/Handles/HandlesAssign.py | 19 ++++++ lib/core/Handles/HandlesExprCall.py | 102 ++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/lib/core/Handles/HandlesAssign.py b/lib/core/Handles/HandlesAssign.py index 6e7c6b5..72f4a3d 100644 --- a/lib/core/Handles/HandlesAssign.py +++ b/lib/core/Handles/HandlesAssign.py @@ -278,6 +278,25 @@ class AssignHandle(BaseHandle): else: if VarName in ('mctx', 's1ctx', 's2ctx', 's5ctx'): pass + # 【联合返回注解的被指类恢复】RHS 是调用、且值的 LLVM 类型是**通用指针** + # (i8*/i8**,pointee 不是结构体)时,上面那块类名推断整块失效 —— 现场 + # `parts = s1.split(',')`:`split` 的返回注解 `list[str]*` ≡ + # `list[str] | t.CPtr`(只读探针确认二者 AST 逐字同形)被映射成 i8* + # ⇒ 类名丢失 ⇒ 随后 `parts[0]` 不走 `list[str].__getitem__` 而按通用 + # 指针/字节索引 ⇒ 得到整数 ⇒ 赋给 `str` 时报"整数隐式转指针已禁止" + # (真因在 4 行之前,报文完全指不到)。此处从被调者的**返回注解** + # 取回被指类名(判据与 HandlesAnnAssign 的同类恢复同源)。 + if (not Gen.var_struct_class.get(VarName) + and isinstance(Node.value, ast.Call) + and isinstance(Value.type, ir.PointerType) + and not isinstance(Value.type.pointee, + (ir.IdentifiedStructType, ir.LiteralStructType))): + _rc: str | None = self.Trans.ExprCallHandle._CallReturnClass(Node.value, Gen) + if _rc: + Gen.var_struct_class[VarName] = _rc + if getattr(Gen, 'global_struct_class', None) is not None: + Gen.global_struct_class[VarName] = _rc + Gen._var_to_heap_ptr[VarName] = Value if isinstance(Value.type, ir.PointerType) and isinstance(Value.type.pointee, (ir.LiteralStructType, ir.IdentifiedStructType)): # BUG-8 修复:清除读取全局变量时缓存的 GlobalVariable(非显式 global 声明) if (VarName in Gen.variables and VarName not in Gen.global_vars diff --git a/lib/core/Handles/HandlesExprCall.py b/lib/core/Handles/HandlesExprCall.py index 0da4df0..46f22f7 100644 --- a/lib/core/Handles/HandlesExprCall.py +++ b/lib/core/Handles/HandlesExprCall.py @@ -4266,6 +4266,108 @@ class ExprCallHandle(BaseHandle): raw_name: str = self._get_raw_type_name(sn) return (type_str, raw_name) + @staticmethod + def _IsCPtrMark(Node: ast.AST) -> bool: + """`t.CPtr` / 裸名 `CPtr` 标记节点。""" + if isinstance(Node, ast.Attribute): + return Node.attr == 'CPtr' + if isinstance(Node, ast.Name): + return Node.id == 'CPtr' + return False + + def _CallReturnClass(self, Node: ast.Call, Gen: LlvmGeneratorMixin) -> str | None: + """调用结果的**被指类名**:从被调者的返回注解恢复。 + + 背景:`list[str]*` ≡ `list[str] | t.CPtr`(只读探针已确认二者 AST 逐字同形, + 都是 `BinOp(BitOr, Subscript(...), Attribute(t.CPtr))`)这类返回注解在 LLVM 层 + 被映射成通用指针 `i8*`,类名随之丢失 ⇒ 调用点只能按通用指针处理 + (`parts = s1.split(',')` 之后 `parts[0]` 退化成按字节索引、得到整数,赋给 + `str` 时报"整数隐式转指针已禁止",真因在 4 行之前)。 + 本方法把类名从**注解**取回;判据与 HandlesAnnAssign 的同类恢复 + (:644-684 `rawptr[X]`、:1012-1034 联合注解构特化名)同源。 + 取不到返回 None —— 调用方据此不改动既有行为。 + """ + _fn: ast.AST = Node.func + _cands: list[str] = [] + if isinstance(_fn, ast.Name): + _cands.append(_fn.id) + elif isinstance(_fn, ast.Attribute): + _m: str = _fn.attr + _recv_cls: str | None = None + if isinstance(_fn.value, ast.Name): + _recv_cls = (Gen.var_struct_class.get(_fn.value.id) + or (getattr(Gen, 'global_struct_class', {}) or {}).get(_fn.value.id)) + # FakeDuck 影子类(str→_str / bytes→_bytes)与接收者真实类名两条线都试; + # FuncDefCache 的方法键在仓库里既有 'cls.method' 也有裸 'method' 两种形态。 + for _c in ([_recv_cls] if _recv_cls else []) + ['_str', '_bytes']: + _cands.append(f'{_c}.{_m}') + _cands.append(_m) + for _nm in _cands: + _fd: ast.FunctionDef | None = self.Trans.FunctionDefCache.get(_nm) + if _fd is not None and _fd.returns is not None: + _cls: str | None = self._ClassFromAnnotation(_fd.returns, Gen) + if _cls: + return _cls + return None + + def _ClassFromAnnotation(self, Ann: ast.AST, Gen: LlvmGeneratorMixin) -> str | None: + """从注解恢复"类名(含泛型特化名)":剔除 `| t.CPtr` 侧与指针层后取核类型。 + + - `list[str] | t.CPtr` / `list[str]*` → `list[str]`(泛型特化名,与 + HandlesAnnAssign:1012-1034 的构名方式一致); + - `Foo | t.CPtr` / `Foo*` → `Foo`; + - 核类型名必须能在 `Gen.structs` / `Gen.class_members` 命中(含 sha1 前缀 + 全名的短名归一),否则返回 None(类型信息不完整时不硬塞名字)。 + """ + _core: ast.AST = Ann + if isinstance(_core, ast.BinOp) and isinstance(_core.op, ast.BitOr): + _l: ast.AST = _core.left + _r: ast.AST = _core.right + if self._IsCPtrMark(_r): + _core = _l + elif self._IsCPtrMark(_l): + _core = _r + else: + return None + if isinstance(_core, ast.Subscript): + _base: ast.expr = _core.value + _bn: str | None = None + if isinstance(_base, ast.Name): + _bn = _base.id + elif isinstance(_base, ast.Attribute): + _bn = _base.attr + if not _bn: + return None + _sl: ast.AST = _core.slice + if hasattr(ast, 'Index') and isinstance(_sl, ast.Index): + _sl = _sl.value + _r2: tuple[str, str] | None = self._resolve_generic_slice_type(_sl) + if not _r2: + return None + _spec: str = f'{_bn}[{_r2[0]}]' + if _spec in Gen.structs or _spec in Gen.class_members: + return _spec + # 端键归一:特化键可能是 sha1 前缀全名 + for _k in Gen.structs: + if _k.split('.')[-1] == _spec: + return _k + return _spec + if isinstance(_core, ast.Name): + if _core.id in Gen.structs or _core.id in Gen.class_members: + return _core.id + for _k in Gen.structs: + if _k.split('.')[-1] == _core.id: + return _k + return None + if isinstance(_core, ast.Attribute): + if _core.attr in Gen.structs or _core.attr in Gen.class_members: + return _core.attr + for _k in Gen.structs: + if _k.split('.')[-1] == _core.attr: + return _k + return None + return None + def _ResolveGenericSlice(self, Sub: ast.Subscript) -> str | None: """从泛型 Subscript (如 Stack[int]) 解析特化名,确保特化类型已生成。""" if not isinstance(Sub.value, ast.Name):