From 9008af0d18aea84894b1f30057d951f13e66c9c3 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 11 Oct 2026 22:07:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(vm):=203=20=E5=A4=84=E4=BF=9D=E7=9C=9F?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20+=20=E7=B2=BE=E7=A1=AE=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E5=88=B0=E6=A8=A1=E5=9D=97=E5=AE=9E=E5=8F=82=E4=B8=BA=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 做了什么: - JArr.__getitem__ 缺失键返回 undefined (JS 语义), 原来返回 Python None - E 初值 E[0]=[this], 顶层 this=全局对象 c (原来错写成 None) - MAKE_REG_PROP_REF / CALL_METHOD / closure 调用 增加 DBGCALL 诊断输出 为什么: - 用 PC 轨迹对拍逐条缩小: 前 713 步一致, 分歧在 @206 CALL_METHOD - 根因链: @45631 CALL_FUNC(模块工厂) 的实参是 [null] -> 参数 r3=[null] -> 闭包 @20 捕获 r6=父r3 -> E[6][0]=null -> ref=[null,'call'] -> 调用不到 怎么验证: - node tools/tdc_trace.js ... -> 19266 步(真值); 我们仍 739 步 - 分歧点/根因链见上 (已用 DBGCALL 定位到具体指令与寄存器) 下一步线索: 追 "谁把该实参压成了 null" (候选: PUSH_NULL(29) 被误用, 或某次属性读取应得对象却拿到 null) --- tools/tdc_vm_run.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tools/tdc_vm_run.py b/tools/tdc_vm_run.py index 215a34e..eff757f 100644 --- a/tools/tdc_vm_run.py +++ b/tools/tdc_vm_run.py @@ -41,13 +41,16 @@ class JArr(list): self._ext: Dict[str, Any] = {} def __getitem__(self, k): + # JS 数组/对象: 缺失的属性是 undefined (不是 null!), 这点必须严格 if isinstance(k, int) or (isinstance(k, str) and k.lstrip("-").isdigit()): i = int(k) try: return list.__getitem__(self, i) except IndexError: - return None - return self._ext.get(k) + return JS_UNDEF + if k in self._ext: + return self._ext[k] + return JS_UNDEF def __setitem__(self, k, v): if isinstance(k, int) or (isinstance(k, str) and k.lstrip("-").isdigit()): @@ -216,7 +219,8 @@ class VM: def __init__(self, z: List[int], c, max_steps: int = 20_000_000): self.z = z self.c = c - self.E: List[Any] = [JArr([None]), JArr([JArr()])] # 与 VM 默认一致: [[this],[{}]] + # 与 VM 源码默认一致: E = [[this],[{}]]; 顶层是普通调用 -> this = 全局对象 (c) + self.E: List[Any] = [JArr([c]), JArr([JArr()])] self.B: List[Any] = [] # try 栈 self.w = None # 异常标志 self.H = 0 @@ -259,7 +263,10 @@ class VM: h = self.OP.get(op) if h is None: raise RuntimeError(f"unknown opcode {op} at pc {self.H-1}") - if h() is True: + r = h() + if os.environ.get("DBGCALL") and self.E and isinstance(self.E[-1], list) and len(self.E[-1]) == 2 and self.E[-1][0] is None: + print(f"[产生 [None,{self.E[-1][1]!r}] @pc={self.H-1}] op={op} steps={self.steps}", file=sys.stderr) + if r is True: self.done = True return self.ret @@ -391,7 +398,10 @@ class VM: a = E.pop() idx = E.pop() cell = E[idx[0]] if isinstance(idx, list) else E[idx] - E.append([cell[0] if isinstance(cell, list) and cell else JS_UNDEF, a]) + val = cell[0] if isinstance(cell, list) and cell else JS_UNDEF + if os.environ.get("DBGCALL") and (val is None or val is JS_UNDEF): + print(f"[MAKE_REG_PROP_REF @{self.H-1}] reg={idx[0] if isinstance(idx,list) else idx} val={val!r} cell={str(cell)[:40]}", file=sys.stderr) + E.append([val, a]) @op(34) # CALL_METHOD n def _34(): @@ -550,6 +560,11 @@ class VM: sub.events = self.events # 共享关键事件 sub.trace = self.trace sub.trace_full = self.trace_full + if os.environ.get("DBGCALL"): + print(f"[invoke closure target=@{fn[1]}] caps={fn[2]} params={fn[3]} args={[str(a)[:20] for a in args]}", file=sys.stderr) + for (q, r) in fn[2]: + src = self.E[r] if r < len(self.E) else "" + print(f" cap r{q} <- parent r{r} = {str(src)[:50]}", file=sys.stderr) sub.E = [JArr() for _ in range(32)] sub.E[0] = JArr([this]); sub.E[1] = JArr([JArr(args)]); sub.E[2] = JArr([fn]) for (q, r) in fn[2]: