fix(vm): 3 处保真修复 + 精确定位到模块实参为 null

做了什么:
- 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)
This commit is contained in:
yml2213
2026-10-11 22:07:16 +08:00
parent 3f91b0a4e3
commit 9008af0d18
+20 -5
View File
@@ -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 "<oob>"
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]: