fix(vm): 修正解释器 4 处保真度问题 + 增加事件追踪

做了什么:
- E 初值改为 [[this],[{}]] (与 VM 源码默认一致)
- Stub 改为真正保存被赋属性 (模块工厂挂在对象上, 丢弃就找不到模块)
- Object/Array/String/... 改为 HostFn, new 时真正构造容器
- getprop/setprop 采用 JS 非严格语义 (原始值: 读=undefined, 写=静默忽略)
- 新增 TRACE=1 事件追踪 (CREATE_CLOSURE/CALL_*/RET_TRUE) 与模块表收集

为什么:
- 这些是解释器与真实 VM 保真度不符的根因, 导致顶层跑完却收不到任何模块闭包

怎么验证:
- py_compile 通过; TRACE=1 跑出 66 条关键事件(其中 CREATE_CLOSURE ~65)
- 现状: 仍只跑完顶层引导(739 步), 模块表为空 -> 待用 PC 轨迹对拍定位首个分歧
This commit is contained in:
yml2213
2026-10-11 22:02:20 +08:00
parent 70f2ece32c
commit 1fbd8f46f5
+96 -29
View File
@@ -64,10 +64,12 @@ JS_UNDEF = object()
class Stub:
"""未知宿主对象的占位: 可调用、可属性访问、可下标, 一律返回自身/None。"""
"""未知宿主对象: 可调用、可属性访问、可下标; **会真正记住被赋的属性**
(tdc.js 把模块工厂挂到对象上, 若丢弃就永远找不到模块)。"""
def __init__(self, name="stub"):
self._name = name
self._props: Dict[Any, Any] = {}
def __call__(self, *a, **k):
return Stub(self._name + "()")
@@ -75,16 +77,47 @@ class Stub:
def __getattr__(self, k):
if k.startswith("_"):
raise AttributeError(k)
return Stub(f"{self._name}.{k}")
return self[k]
def __getitem__(self, k):
if k in self._props:
return self._props[k]
v = Stub(f"{self._name}.{k}")
self._props[k] = v
return v
def __setitem__(self, k, v):
self._props[k] = v
def __repr__(self):
return f"<{self._name} props={list(self._props)[:5]}>"
class HostFn:
"""宿主内置函数/构造器 (Object/Array/String/...)。"""
def __init__(self, name, fn):
self._name = name
self._fn = fn
def __call__(self, *a):
return self._fn(*a)
def __getitem__(self, k):
return Stub(f"{self._name}[{k}]")
def __setitem__(self, k, v):
pass
def __repr__(self):
return f"<{self._name}>"
return f"<host {self._name}>"
def _mk_array(*a):
if len(a) == 1 and isinstance(a[0], int):
return JArr([JS_UNDEF] * a[0])
return JArr(a)
def _mk_object(*a):
return JArr()
def _to_host(**kw):
@@ -97,29 +130,26 @@ def make_host() -> Any:
"""最小宿主 (c = window)。未知项退化为 Stub, 保证不因缺属性而崩。"""
win = _to_host()
class MathMod:
pass
win._ext.update({
"Math": Stub("Math"),
"JSON": type("J", (), {"parse": staticmethod(json.loads), "stringify": staticmethod(json.dumps)})(),
"Object": JArr,
"Array": JArr,
"String": str,
"Number": float,
"Boolean": bool,
"Date": int(time.time() * 1000),
"Object": HostFn("Object", _mk_object),
"Array": HostFn("Array", _mk_array),
"String": HostFn("String", lambda *a: "" if not a else str(a[0])),
"Number": HostFn("Number", lambda *a: 0 if not a else float(a[0])),
"Boolean": HostFn("Boolean", lambda *a: bool(a[0]) if a else False),
"Date": HostFn("Date", lambda *a: int(time.time() * 1000)),
"RegExp": Stub("RegExp"),
"Error": Stub("Error"),
"TypeError": Stub("TypeError"),
"Uint8Array": bytearray,
"Uint8ClampedArray": bytearray,
"parseInt": int,
"parseFloat": float,
"encodeURIComponent": lambda s: s,
"decodeURIComponent": lambda s: s,
"atob": lambda s: s,
"btoa": lambda s: s,
"Uint8Array": HostFn("Uint8Array", lambda *a: JArr([0] * (a[0] if a and isinstance(a[0], int) else 0))),
"Uint8ClampedArray": HostFn("Uint8ClampedArray", lambda *a: JArr([0] * (a[0] if a and isinstance(a[0], int) else 0))),
"parseInt": HostFn("parseInt", lambda *a: int(float(a[0])) if a else 0),
"parseFloat": HostFn("parseFloat", lambda *a: float(a[0]) if a else 0.0),
"encodeURIComponent": HostFn("encodeURIComponent", lambda *a: a[0] if a else ""),
"decodeURIComponent": HostFn("decodeURIComponent", lambda *a: a[0] if a else ""),
"atob": HostFn("atob", lambda *a: a[0] if a else ""),
"btoa": HostFn("btoa", lambda *a: a[0] if a else ""),
"undefined": JS_UNDEF,
"NaN": float("nan"),
})
@@ -186,7 +216,7 @@ 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] = [] # 寄存器区 + 栈
self.E: List[Any] = [JArr([None]), JArr([JArr()])] # 与 VM 默认一致: [[this],[{}]]
self.B: List[Any] = [] # try 栈
self.w = None # 异常标志
self.H = 0
@@ -195,6 +225,8 @@ class VM:
self.done = False
self.ret = None
self.key_hits: List[Any] = []
self.trace = bool(os.environ.get("TRACE"))
self.events: List[Any] = [] # 记录调用/闭包/返回等关键事件
# ---- 工具
def pu(self):
@@ -218,6 +250,8 @@ class VM:
if self.steps > self.max_steps:
raise RuntimeError("step limit exceeded")
op = z[self.H]; self.H += 1
if self.trace and op in (1, 6, 19, 34, 41, 49, 57, 61):
self.events.append((self.H - 1, op))
h = self.OP.get(op)
if h is None:
raise RuntimeError(f"unknown opcode {op} at pc {self.H-1}")
@@ -515,6 +549,13 @@ class VM:
return v
except Exception:
return JS_UNDEF
if isinstance(fn, HostFn):
return fn(*args)
if callable(fn) and not isinstance(fn, (JArr, Stub, tuple)):
try:
return fn(*args)
except Exception:
return JS_UNDEF
if isinstance(fn, Stub):
return Stub("call")
return JS_UNDEF
@@ -537,12 +578,38 @@ def main() -> int:
except Exception as e:
print("VM halted with:", type(e).__name__, str(e)[:200])
print("steps:", vm.steps, "done:", vm.done, "ret:", str(vm.ret)[:80])
names = {1: "IS_EXCEPTION", 6: "CALL_FUNC", 19: "RET_TRUE", 34: "CALL_METHOD",
41: "NEW_METHOD", 49: "THROW", 57: "CREATE_CLOSURE", 61: "NEW_FUNC"}
print("关键事件 (pc, op):")
for pc, op in vm.events[:40]:
print(f" @{pc:05d} {names.get(op, op)}")
print(f" ... 共 {len(vm.events)} 条")
# tdc.js 顶层只"注册"模块工厂; 这里主动调用寄存器里保存的闭包 (模拟模块系统/宿主调用)
closures = [cell[0] for cell in vm.E
if isinstance(cell, list) and cell and isinstance(cell[0], tuple)
and cell[0] and cell[0][0] == "closure"]
print("registered closures in registers:", len(closures))
# tdc.js 顶层只"注册"模块工厂; 这里把寄存器/数组里保存的闭包全收集出来 = 模块表
def collect_closures(obj, out, seen):
oid = id(obj)
if oid in seen:
return
seen.add(oid)
if isinstance(obj, tuple) and obj and obj[0] == "closure":
out.append(obj)
return
if isinstance(obj, Stub):
for v in obj._props.values():
collect_closures(v, out, seen)
elif isinstance(obj, (JArr, list)):
for v in list(obj):
collect_closures(v, out, seen)
if isinstance(obj, JArr):
for v in obj._ext.values():
collect_closures(v, out, seen)
elif isinstance(obj, dict):
for v in obj.values():
collect_closures(v, out, seen)
closures: List[Any] = []
collect_closures([vm.E, vm.c], closures, set())
print("模块表: 收集到闭包", len(closures), "个 -> target:", [c[1] for c in closures][:80])
for i, cl in enumerate(closures):
try:
vm.call(cl, [Stub("arg")], {})