docs: 补充注释与格式化
This commit is contained in:
@@ -56,7 +56,7 @@ pub fn bind_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
core,
|
||||
&[("toString", oxide_builtins::array::array_to_string::<crate::vm::Vm> as *const (), 0)],
|
||||
);
|
||||
// Built-in prototype methods are non-enumerable (otherwise they leak into for-in).
|
||||
// 内置原型方法不可枚举(否则会泄漏进 for-in)。
|
||||
let si = core.perm_interner().intern("toString").0;
|
||||
if let Some(pos) = core.shape_forge().lookup_position(array_proto.shape_id(), si) {
|
||||
array_proto.set_data_meta(pos, PropAttributes::new(true, false, true));
|
||||
|
||||
@@ -16,7 +16,7 @@ fn bind_error_subtype_constructor(
|
||||
|
||||
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto_ptr)));
|
||||
ctor.set_function(true);
|
||||
// SAFETY: ctor_fn is a NativeFn fn-item pointer cast to *const () by callers.
|
||||
// SAFETY: ctor_fn 是调用方转成 *const () 的 NativeFn 函数项指针。
|
||||
ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(ctor_fn) }));
|
||||
ctor.set_native_arg_count(1);
|
||||
|
||||
@@ -121,7 +121,7 @@ pub fn bind_error(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
{
|
||||
let err_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject;
|
||||
let err_ctor = unsafe { &mut *err_ctor_ptr };
|
||||
// SAFETY: error_constructor is a NativeFn fn-item.
|
||||
// SAFETY: error_constructor 是 NativeFn 函数项。
|
||||
err_ctor.set_native_fn(Some(unsafe {
|
||||
NativeFnPtr::from_raw(oxide_builtins::error::error_constructor::<crate::vm::Vm> as *const ())
|
||||
}));
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
("valueOf", oxide_builtins::object::object_proto_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
],
|
||||
);
|
||||
// Built-in prototype methods are non-enumerable (otherwise they leak into for-in).
|
||||
// 内置原型方法不可枚举(否则会泄漏进 for-in)。
|
||||
for name in ["toString", "valueOf"] {
|
||||
let si = core.perm_interner().intern(name).0;
|
||||
if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) {
|
||||
|
||||
@@ -83,7 +83,7 @@ macro_rules! bind_constructor {
|
||||
}
|
||||
|
||||
pub(crate) fn configure_native_constructor(ctor: &mut JsObject, native_fn: *const (), arg_count: u8) {
|
||||
// SAFETY: native_fn is always a valid NativeFn fn-item pointer cast to *const () by callers.
|
||||
// SAFETY: native_fn 始终是调用方转成 *const () 的合法 NativeFn 函数项指针。
|
||||
ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(native_fn) }));
|
||||
ctor.set_native_arg_count(arg_count);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ pub(crate) fn apply_binding_table(
|
||||
let shape_forge = core.shape_forge().as_ref();
|
||||
let string_forge = core.perm_interner().as_ref();
|
||||
for (name, func, nargs) in bindings {
|
||||
// SAFETY: all entries in the binding table are NativeFn fn-item pointers cast to *const ().
|
||||
// SAFETY: 绑定表中所有条目都是转成 *const () 的 NativeFn 函数项指针。
|
||||
let fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(*func) };
|
||||
let _ = world.bind_method(target, shape_forge, string_forge, name, fn_ptr, *nargs);
|
||||
}
|
||||
@@ -491,9 +491,11 @@ pub fn bind_global_builtin_slots(core: &Arc<KernelCore>, session: &KernelSession
|
||||
bind_existing_global(core, global, "globalThis", global_this);
|
||||
}
|
||||
|
||||
/// Maintenance: when adding a `BuiltinDirtySet` group, update this rebind map,
|
||||
/// `BuiltinSnapshot`, and `BuiltinWorld::rebuild_with_dirty()` together.
|
||||
/// 按脏标记重绑被污染的内置对象;`dirty` 为 `None` 时绑定全部(初始化路径)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 维护:新增 `BuiltinDirtySet` 分组时,须同步更新本重绑映射、`BuiltinSnapshot` 与
|
||||
/// `BuiltinWorld::rebuild_with_dirty()`。
|
||||
pub fn rebind_dirty_builtins(core: &Arc<KernelCore>, session: &mut KernelSession, dirty: Option<&BuiltinDirtySet>) {
|
||||
let global_ptr = session.global_object().as_ptr() as *mut JsObject;
|
||||
let global = unsafe { &mut *global_ptr };
|
||||
|
||||
@@ -33,14 +33,14 @@ impl Vm {
|
||||
let args_slice = &args_buf[..len];
|
||||
builtins_debug!("native_call depth={} args={}", self.native_call_depth, arg_count);
|
||||
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
// SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置;
|
||||
// native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(obj.native_fn().unwrap()) };
|
||||
// regs[254] is the caller's `this` register and doubles as the "current callee"
|
||||
// slot that dispatcher builtins (Function.prototype.bind/call/apply) read. Native
|
||||
// calls share the flat register file with the caller, so snapshot the caller's
|
||||
// `this`, expose the callee for the native's duration, then restore it — otherwise
|
||||
// every native call leaves its own function object in the caller's `this` register.
|
||||
// regs[254] 是调用方的 `this` 寄存器,同时充当分发型 builtin
|
||||
// (Function.prototype.bind/call/apply)读取的"当前 callee"槽。native 调用与
|
||||
// 调用方共享扁平寄存器文件,因此先快照调用方 `this`,在 native 执行期间暴露
|
||||
// callee,随后恢复——否则每次 native 调用都会把自己的函数对象留在调用方的
|
||||
// `this` 寄存器里。
|
||||
let saved_this = self.regs[254];
|
||||
self.regs[254] = callee;
|
||||
self.native_call_depth += 1;
|
||||
@@ -58,7 +58,7 @@ impl Vm {
|
||||
(err_val, self.thrown_error_kind(err_val))
|
||||
} else {
|
||||
let msg = if err_val.is_string() {
|
||||
// SAFETY: err_val is a string value.
|
||||
// SAFETY: err_val 是字符串值。
|
||||
unsafe { (*err_val.as_string_ptr()).data.clone() }
|
||||
} else {
|
||||
format!("{err_val}")
|
||||
@@ -184,8 +184,8 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cell not yet created (hoisting order: CREATE_CLOSURE before MAKE_CELL).
|
||||
// Create cell lazily with caller's register value.
|
||||
// cell 尚未创建(hoisting 顺序:CREATE_CLOSURE 先于 MAKE_CELL),
|
||||
// 用调用方寄存器值惰性创建 cell。
|
||||
self.lazy_create_upvalue_cell(rd, uv_idx)
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ impl Vm {
|
||||
let obj = unsafe { &mut *callee.as_js_object_ptr() };
|
||||
let upvals = obj.upvalues_slice_mut();
|
||||
if uv_idx < upvals.len() {
|
||||
// Try to get value from caller's cell table (MAKE_CELL ran after CREATE_CLOSURE)
|
||||
// 尝试从调用方 cell 表取初值(MAKE_CELL 在 CREATE_CLOSURE 之后执行)。
|
||||
let val = if self.cell_stack.len() >= 2 {
|
||||
let caller_cells = &self.cell_stack[self.cell_stack.len() - 2];
|
||||
if uv_idx < caller_cells.len() && !caller_cells[uv_idx].is_null() {
|
||||
@@ -317,10 +317,10 @@ impl Vm {
|
||||
|
||||
if super_obj.native_fn().is_some() {
|
||||
self.regs[253] = derived_this;
|
||||
self.regs[254] = super_ctor; // bind_dispatcher reads regs[254] as the wrapper callee
|
||||
self.regs[254] = super_ctor; // bind_dispatcher 把 regs[254] 当作包装 callee 读取
|
||||
let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, 253);
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
// SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置;
|
||||
// native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(super_obj.native_fn().unwrap()) };
|
||||
match func(self, &args_buf[..len]) {
|
||||
NativeResult::Ok(val) => {
|
||||
@@ -336,8 +336,8 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
NativeResult::TailCall { callee, this, args } => {
|
||||
// e.g. bound function: resolve the tail call, use its return
|
||||
// value as the constructed instance (or fall back to derived_this).
|
||||
// 如 bound 函数:解析尾调用,用其返回值作为构造实例
|
||||
// (或回退到 derived_this)。
|
||||
match self.call_function_sync(callee, this, &args) {
|
||||
Ok(val) => {
|
||||
self.regs[254] = if val.is_object() { val } else { derived_this };
|
||||
|
||||
@@ -1 +1 @@
|
||||
// dispatch helper functions extracted from vm.rs
|
||||
//! 异常处理相关 dispatch 辅助函数(占位,逻辑位于 vm_dispatch_ctrl / vm_dispatch_misc)。
|
||||
|
||||
@@ -1 +1 @@
|
||||
// dispatch helper functions extracted from vm.rs
|
||||
//! for-in 相关 dispatch 辅助函数(占位,逻辑位于 vm_dispatch_misc)。
|
||||
|
||||
@@ -65,7 +65,7 @@ impl Vm {
|
||||
pub(crate) fn dispatch_store_var(&mut self, rd: usize, a: usize, b: usize) -> Result<bool, String> {
|
||||
vm_trace!("STORE_VAR r{}={:?} const={}", rd, self.regs[a], b);
|
||||
if b != 0 {
|
||||
// const guard: check if already initialized
|
||||
// const guard:检查目标槽是否已初始化。
|
||||
if !self.regs[rd].is_undefined() {
|
||||
self.raise_error_kind("TypeError", "Assignment to constant variable")?;
|
||||
return Ok(true);
|
||||
@@ -75,8 +75,13 @@ impl Vm {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// SPILL:regs[rd] → spill_stack[帧基址 + slot]。ext 字 = slot u16(D-06)。
|
||||
/// 帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧 = 0,D-08)。
|
||||
/// SPILL:`regs[rd]` → `spill_stack[帧基址 + slot]`。
|
||||
///
|
||||
/// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。
|
||||
/// # 边界与前提
|
||||
/// - slot 超 u16 范围返回错误(RegAlloc 保证发射在界内)。
|
||||
/// # 副作用
|
||||
/// - 写 spill 栈,可能触发 resize 扩容。
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_spill(&mut self, rd: usize) -> Result<(), String> {
|
||||
let slot = self.bytecode[self.pc] as usize;
|
||||
@@ -94,7 +99,11 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// UNSPILL:spill_stack[帧基址 + slot] → regs[rd]。越界读 undefined(防御,D-06 slot 由 RegAlloc 保证在界内)。
|
||||
/// UNSPILL:`spill_stack[帧基址 + slot]` → `regs[rd]`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - slot 超 u16 范围返回错误。
|
||||
/// - 越界读返回 undefined(防御:slot 由 RegAlloc 保证在界内)。
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_unspill(&mut self, rd: usize) -> Result<(), String> {
|
||||
let slot = self.bytecode[self.pc] as usize;
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Vm {
|
||||
if val.is_string() {
|
||||
let length_si = self.kernel_core.perm_interner().intern("length").0;
|
||||
if prop_name_si == length_si {
|
||||
// SAFETY: val is a string value.
|
||||
// SAFETY: val 是字符串值。
|
||||
let len = unsafe { (*val.as_string_ptr()).data.encode_utf16().count() };
|
||||
return Ok(Some(JsValue::int(len as i32)));
|
||||
}
|
||||
@@ -207,14 +207,14 @@ impl Vm {
|
||||
prop_cache_miss();
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
// Try own property first (fast path, depth=0).
|
||||
// 先试自身属性(快路径,depth=0)。
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if !obj.is_accessor_meta(pos) {
|
||||
ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
|
||||
ic_debug!("IC_GET write-back own shape={} slot={}", obj.shape_id(), pos);
|
||||
}
|
||||
} else {
|
||||
// Walk proto chain to find which object actually owns this property.
|
||||
// 沿原型链查找真正拥有该属性的对象。
|
||||
let mut cursor = obj.proto().as_js_object_ptr();
|
||||
let mut depth = 1u8;
|
||||
while !cursor.is_null() {
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
//! Inline-cache (IC) bytecode-stream helpers.
|
||||
//! inline cache(IC)字节码流辅助函数。
|
||||
//!
|
||||
//! Contains ALL IC read/write/clear logic — property dispatch and member
|
||||
//! update handlers route through here, so IC format changes (like the future
|
||||
//! side-table migration) require editing only this file.
|
||||
//! 汇聚全部 IC 读写/清零逻辑——属性分发与成员更新 handler 都经由此处,IC 格式
|
||||
//! 变更(如未来的 side-table 迁移)只需改这一个文件。
|
||||
//!
|
||||
//! IC entry format (3 extension words after opcode):
|
||||
//! ext0 = shape_id (24 bits)
|
||||
//! ext1 = slot_index (32 bits)
|
||||
//! ext2 = proto_depth (u8, 0 = own property on receiver)
|
||||
//! IC 项格式(操作码后 3 个扩展字):
|
||||
//! ext0 = shape_id(24 位)
|
||||
//! ext1 = slot_index(32 位)
|
||||
//! ext2 = proto_depth(u8,0 = 接收者自身属性)
|
||||
|
||||
use crate::vm_trace;
|
||||
use oxide_bytecode::opcode::{self, Instr};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// Read the three IC extension words at `pc` (the bytes following an
|
||||
/// IC-bearing opcode), advance `pc` past them, and return the decoded
|
||||
/// `(shape_id, slot, proto_depth)`.
|
||||
/// 读取 `pc` 处 IC 指令后的三个扩展字,把 `pc` 前进越过它们,
|
||||
/// 返回解码的 `(shape_id, slot, proto_depth)`。
|
||||
pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8) {
|
||||
let ext0 = bytecode[*pc];
|
||||
let ext1 = bytecode[*pc + 1];
|
||||
@@ -25,9 +23,8 @@ pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8
|
||||
(ext0 & 0x00FF_FFFF, ext1, (ext2 & 0xFF) as u8)
|
||||
}
|
||||
|
||||
/// Write a resolved `(shape_id, slot, proto_depth)` back into the three IC
|
||||
/// extension words at `pc - 3` (they precede the current instruction).
|
||||
/// Called on an IC miss after resolving a property.
|
||||
/// 把解析出的 `(shape_id, slot, proto_depth)` 写回 `pc - 3` 处的三个 IC 扩展字
|
||||
/// (位于当前指令之前)。IC 未命中并完成属性解析后调用。
|
||||
pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32, proto_depth: u8) {
|
||||
debug_assert!(pc >= 3, "IC write-back requires 3 extension words before pc");
|
||||
vm_trace!("write_ic_back: pc={} shape_id={} slot={} depth={}", pc, shape_id, slot_index, proto_depth);
|
||||
@@ -36,7 +33,7 @@ pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, sl
|
||||
bytecode[pc - 1] = proto_depth as u32;
|
||||
}
|
||||
|
||||
/// Zero every IC extension word in the stream, invalidating all cached shapes.
|
||||
/// 把流中所有 IC 扩展字清零,使全部缓存 shape 失效。
|
||||
pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) {
|
||||
let mut i = 0;
|
||||
while i < bytecode.len() {
|
||||
@@ -54,8 +51,7 @@ pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk `proto_depth` steps up the prototype chain, returning a raw pointer
|
||||
/// to the target object (or null if the chain is shorter).
|
||||
/// 沿原型链上溯 `proto_depth` 步,返回目标对象的裸指针(链不足时返回 null)。
|
||||
#[inline(always)]
|
||||
fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsObject {
|
||||
if proto_depth == 0 {
|
||||
|
||||
@@ -244,8 +244,8 @@ mod tests {
|
||||
let first = plain_object(&mut vm);
|
||||
let promoted = vm.promote_object(first);
|
||||
assert!(!promoted.is_null());
|
||||
// The shared forwarding map must be cleared after each promote so a
|
||||
// later promote (or GC sweep) never observes stale old->new mappings.
|
||||
// 共享 forwarding 表必须在每次 promote 后清空,使后续 promote(或 GC 清扫)
|
||||
// 永不观察到过期的 old->new 映射。
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
|
||||
let second = plain_object(&mut vm);
|
||||
|
||||
@@ -47,8 +47,8 @@ impl SessionGc {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptrs in session_object_ptrs come only from `session_epoch.alloc` in
|
||||
// `promote_object_inner`, and are valid while the arena is alive.
|
||||
// SAFETY: session_object_ptrs 中的指针只来自 promote_object_inner 的
|
||||
// session_epoch.alloc,在 arena 存活期间有效。
|
||||
unsafe { (*ptr).set_gc_mark(false) };
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl SessionGc {
|
||||
if obj.is_data_view_obj() {
|
||||
edges.extend(data_view::data_view_native_edges(obj));
|
||||
}
|
||||
// Traverse upvalue cells for object references
|
||||
// 遍历 upvalue cell 中的对象引用。
|
||||
for cell_ptr in obj.upvalues_slice() {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
@@ -102,10 +102,10 @@ impl SessionGc {
|
||||
edges
|
||||
}
|
||||
|
||||
/// Record session-string values held directly by `obj` into `live`. JsStrings hold no GC
|
||||
/// references, so "reaching" a string IS marking it — there is no string DFS stack. Permanent
|
||||
/// strings are also recorded here harmlessly; the sweep only iterates `session_string_ptrs`, so
|
||||
/// non-session pointers in `live` are simply never consulted.
|
||||
/// 把 `obj` 直接持有的 session 字符串值记入 `live`。JsString 不持有 GC 引用,
|
||||
/// 因此"到达"一个字符串就等于标记它——不存在字符串 DFS 栈。永久字符串也会被
|
||||
/// 无害地记录;sweep 只遍历 `session_string_ptrs`,`live` 中的非 session 指针
|
||||
/// 永远不会被查询。
|
||||
fn record_object_string_edges(live: &mut HashSet<*mut JsString, FxBuildHasher>, obj: &JsObject) {
|
||||
if let Some(props) = obj.hash_props_vec() {
|
||||
for value in props.iter() {
|
||||
@@ -120,7 +120,7 @@ impl SessionGc {
|
||||
if obj.home_object().is_string() {
|
||||
live.insert(obj.home_object().as_string_ptr_mut());
|
||||
}
|
||||
// Scan upvalue cells for string references
|
||||
// 扫描 upvalue cell 中的字符串引用。
|
||||
for cell_ptr in obj.upvalues_slice() {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
@@ -179,7 +179,7 @@ impl SessionGc {
|
||||
stack.push(ptr);
|
||||
continue;
|
||||
}
|
||||
// SAFETY: object roots are produced by VM-owned fields and builtin objects.
|
||||
// SAFETY: 对象根由 VM 自有的字段与 builtin 对象产生。
|
||||
let obj = unsafe { &*ptr };
|
||||
Self::record_object_string_edges(live_strings, obj);
|
||||
for edge in Self::object_edges(obj) {
|
||||
@@ -196,8 +196,7 @@ impl SessionGc {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr was discovered from a root/session edge and session root checks require
|
||||
// this to be a valid session object pointer.
|
||||
// SAFETY: ptr 由根/session 边发现,session 根检查保证它是合法 session 对象指针。
|
||||
unsafe {
|
||||
let obj = &mut *ptr;
|
||||
if obj.is_gc_marked() {
|
||||
@@ -223,9 +222,9 @@ impl SessionGc {
|
||||
if obj_ptr.is_null() {
|
||||
return 0;
|
||||
}
|
||||
// SAFETY: `obj_ptr` is verified before calling this helper and points to a session object
|
||||
// owned by the VM session arena. We only reconstruct Boxes that were allocated in
|
||||
// `JsObject::ensure_hash_props`/`ensure_prop_meta` and then drop them once here.
|
||||
// SAFETY: `obj_ptr` 在调用本辅助函数前已校验,指向 VM session arena 拥有的
|
||||
// session 对象。只重建 JsObject::ensure_hash_props/ensure_prop_meta 分配的 Box,
|
||||
// 且只在这里释放一次。
|
||||
unsafe {
|
||||
let obj = &mut *obj_ptr;
|
||||
if require_session {
|
||||
@@ -267,12 +266,13 @@ impl SessionGc {
|
||||
Self::drop_session_object_heap_data(obj_ptr) + size_of::<JsObject>() as u64
|
||||
}
|
||||
|
||||
/// Drop a dead session `JsString` (allocated by `Vm::new_string` via `Box::into_raw`),
|
||||
/// returning the bytes freed. Mirrors `Vm::free_session_string_heap_data`'s drop, but applied
|
||||
/// selectively to a single dead pointer.
|
||||
/// 释放一个已死 session `JsString`(由 `Vm::new_string` 经 `Box::into_raw` 分配),
|
||||
/// 返回释放的字节数。与 `Vm::free_session_string_heap_data` 的释放一致,但只
|
||||
/// 选择性作用于单个已死指针。
|
||||
///
|
||||
/// SAFETY: `ptr` must be a non-null pointer produced by `Box::into_raw(Box::new(JsString))` in
|
||||
/// `Vm::new_string`, still present in `session_string_ptrs`, and dropped exactly once.
|
||||
/// # Safety
|
||||
/// `ptr` 必须是 `Vm::new_string` 中 `Box::into_raw(Box::new(JsString))` 产生的
|
||||
/// 非空指针,仍存在于 `session_string_ptrs`,且恰好释放一次。
|
||||
unsafe fn drop_dead_session_string(ptr: *mut JsString) -> u64 {
|
||||
let bytes = (size_of::<JsString>() + (*ptr).len()) as u64;
|
||||
drop(Box::from_raw(ptr));
|
||||
@@ -291,8 +291,7 @@ impl SessionGc {
|
||||
if old_ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: old_ptr comes from session_arena promotions and still points into the old
|
||||
// session arena while sweep runs.
|
||||
// SAFETY: old_ptr 来自 session_arena 的晋升,sweep 运行期间仍指向旧 session arena。
|
||||
let is_live = unsafe { (*old_ptr).is_gc_marked() };
|
||||
if is_live {
|
||||
survivors += 1;
|
||||
@@ -320,7 +319,7 @@ impl SessionGc {
|
||||
}
|
||||
|
||||
for &dst in forwarding.values() {
|
||||
// SAFETY: all pointers in forwarding are newly allocated and initialized objects.
|
||||
// SAFETY: forwarding 中的指针全是新分配且已初始化的对象。
|
||||
let obj = unsafe { &mut *dst };
|
||||
obj.rewrite_object_values(|value| {
|
||||
if value.is_object() {
|
||||
@@ -378,11 +377,11 @@ impl SessionGc {
|
||||
freed_bytes
|
||||
}
|
||||
|
||||
/// Sweep session `JsString`s: keep the pointers recorded live during `mark()`, drop the rest
|
||||
/// via `Box::from_raw`. Live strings are NOT moved — Box addresses are stable — so no forwarding
|
||||
/// map and no root-pointer rewriting are needed. Run after the object sweep (which resets
|
||||
/// `session_bytes_allocated` to object-only), re-adding surviving string bytes. Returns the
|
||||
/// bytes freed.
|
||||
/// 清扫 session `JsString`:保留 `mark()` 阶段记为存活的部分,其余经
|
||||
/// `Box::from_raw` 释放。存活字符串不被搬移——Box 地址稳定——因此无需
|
||||
/// forwarding 表与根指针重写。在对象清扫之后运行(对象清扫会把
|
||||
/// `session_bytes_allocated` 重置为仅对象),再补回存活字符串字节。
|
||||
/// 返回释放的字节数。
|
||||
pub(crate) fn sweep_session_strings(&mut self, vm: &mut Vm) -> u64 {
|
||||
vm_debug!("[GC] sweep strings: {} string ptrs", vm.gc_state.session_string_ptrs.len());
|
||||
let old = std::mem::take(&mut vm.gc_state.session_string_ptrs);
|
||||
@@ -394,12 +393,12 @@ impl SessionGc {
|
||||
continue;
|
||||
}
|
||||
if self.live_strings.contains(&ptr) {
|
||||
// Survivor — address unchanged, no rewrite needed.
|
||||
// SAFETY: ptr is a live session-string box still owned by the VM.
|
||||
// 存活——地址不变,无需重写。
|
||||
// SAFETY: ptr 是仍归 VM 所有的存活 session 字符串 box。
|
||||
live_bytes += unsafe { size_of::<JsString>() + (*ptr).len() };
|
||||
live.push(ptr);
|
||||
} else {
|
||||
// SAFETY: ptr is in session_string_ptrs but not reachable; drop exactly once.
|
||||
// SAFETY: ptr 在 session_string_ptrs 中但不可达,恰好释放一次。
|
||||
freed += unsafe { Self::drop_dead_session_string(ptr) };
|
||||
}
|
||||
}
|
||||
@@ -521,12 +520,12 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
vm.iters.last_for_of_result = rewrite_forwarded_value(vm.iters.last_for_of_result, forwarding);
|
||||
// Converted immutables are non-session (scalars + perm-strings) — not rewritten.
|
||||
// 已转换的不可变常量不是 session 值(标量 + perm 字符串)——不重写。
|
||||
for iter in &mut vm.iters.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: for_in_iters stores live iterator pointers owned by the current VM epoch.
|
||||
// SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。
|
||||
unsafe {
|
||||
for (value, _si) in (*(*iter)).keys.iter_mut() {
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
@@ -535,7 +534,7 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
}
|
||||
let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject;
|
||||
if !global_ptr.is_null() {
|
||||
// SAFETY: KernelSession owns global_object for the VM lifetime.
|
||||
// SAFETY: KernelSession 在 VM 生命周期内拥有 global_object。
|
||||
unsafe {
|
||||
(*global_ptr).rewrite_object_values(|value| rewrite_forwarded_value(value, forwarding));
|
||||
}
|
||||
@@ -689,8 +688,8 @@ mod tests {
|
||||
let _ = gc.sweep(&mut vm);
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
// The reused forwarding map MUST be cleared after sweep, otherwise
|
||||
// promote would observe stale old->new entries pointing into the freed arena.
|
||||
// 复用的 forwarding 表必须在 sweep 后清空,否则 promote 会观察到指向已释放
|
||||
// arena 的过期 old->new 条目。
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
}
|
||||
|
||||
@@ -934,7 +933,7 @@ mod tests {
|
||||
let dead_ptr = dead.as_string_ptr_mut();
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&dead_ptr));
|
||||
|
||||
// No root references `dead` (it lives only on the Rust stack as a value wrapper).
|
||||
// 没有根引用 `dead`(它只作为值包装存在于 Rust 栈上)。
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&dead_ptr));
|
||||
@@ -950,7 +949,7 @@ mod tests {
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&live_ptr));
|
||||
// Live strings are never moved — the register still points at the same box.
|
||||
// 存活字符串永不搬移——寄存器仍指向同一 box。
|
||||
assert_eq!(vm.regs[0].as_string_ptr_mut(), live_ptr);
|
||||
assert_eq!(unsafe { (*live_ptr).as_str() }, "live-in-reg");
|
||||
}
|
||||
@@ -966,7 +965,7 @@ mod tests {
|
||||
}
|
||||
let obj_session = vm.promote_object(obj);
|
||||
|
||||
// The string is reachable ONLY through the live object's property, not via any register.
|
||||
// 该字符串仅通过存活对象的属性可达,不经过任何寄存器。
|
||||
vm.regs.fill(JsValue::undefined());
|
||||
vm.regs[0] = JsValue::from_js_object(obj_session);
|
||||
|
||||
@@ -981,13 +980,13 @@ mod tests {
|
||||
let mut vm = Vm::new();
|
||||
let perm = vm.perm_string("perm");
|
||||
let perm_ptr = perm.as_string_ptr_mut();
|
||||
// Permanent strings live in PermInterner, never in the session set.
|
||||
// 永久字符串位于 PermInterner,绝不在 session 集合中。
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
vm.regs[0] = perm;
|
||||
|
||||
collect(&mut vm);
|
||||
|
||||
// Never dropped by the session sweep (still readable, still absent from session set).
|
||||
// 绝不被 session 清扫释放(仍可读,仍不在 session 集合中)。
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
assert_eq!(unsafe { (*perm_ptr).as_str() }, "perm");
|
||||
}
|
||||
|
||||
+50
-51
@@ -22,11 +22,11 @@ use oxide_types::value::{JsValue, PTR_MASK};
|
||||
|
||||
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
||||
|
||||
/// Convert a `NativeFnPtr` to a callable `NativeFn`.
|
||||
/// 将 [`NativeFnPtr`] 转换为可调用的 [`NativeFn`]。
|
||||
///
|
||||
/// # Safety
|
||||
/// The pointer stored in `ptr` must have been created from a valid `NativeFn` fn-item.
|
||||
/// This is the single point in the codebase where `NativeFnPtr → NativeFn` coercion happens.
|
||||
/// `ptr` 必须由合法的 `NativeFn` 函数项产生。
|
||||
/// 这是代码库中唯一一处 `NativeFnPtr → NativeFn` 的强制转换点。
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn native_fn_ptr_to_fn(ptr: NativeFnPtr) -> NativeFn {
|
||||
std::mem::transmute::<*const (), NativeFn>(ptr.as_ptr())
|
||||
@@ -110,7 +110,7 @@ pub struct CallFrame {
|
||||
pub function_name: u32,
|
||||
pub caller_reg_limit: u8,
|
||||
pub saved_reg_offset: u32,
|
||||
/// push 时记录 spill_stack 长度(D-08 帧边界,SPILL/UNSPILL 基址)。
|
||||
/// 记录本帧 spill 栈起始长度,作为 SPILL/UNSPILL 的帧边界基址(push 时快照)。
|
||||
pub spill_offset: u32,
|
||||
pub saved_this: JsValue,
|
||||
pub saved_new_target: JsValue,
|
||||
@@ -125,8 +125,8 @@ pub struct CallFrame {
|
||||
///
|
||||
/// 每个 key 与它的 intern id 配对保存,使整型下标 key 无需重新 intern 即可排到字符串 key 之前。
|
||||
pub struct ForInIter<'bump> {
|
||||
/// Each key paired with its string-intern id, so for-in can sort
|
||||
/// integer-index keys ahead of string keys without re-interning.
|
||||
/// 每个 key 与其字符串 intern id 配对,使 for-in 排序时整型下标 key 无需
|
||||
/// 重新 intern 即可排到字符串 key 之前。
|
||||
pub keys: bumpalo::collections::Vec<'bump, (JsValue, u32)>,
|
||||
pub index: usize,
|
||||
}
|
||||
@@ -136,13 +136,13 @@ pub struct TryHandler {
|
||||
pub catch_pc: Option<usize>,
|
||||
pub finally_pc: Option<usize>,
|
||||
pub frame_depth: usize,
|
||||
/// for_of_iters length at try entry — bounds which iterators IteratorClose on unwind.
|
||||
/// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。
|
||||
pub for_of_depth: usize,
|
||||
}
|
||||
|
||||
/// Heap-allocated snapshot used by `call_bytecode_function_inline`.
|
||||
/// Keeping it on the heap prevents Rust stack overflow when JS code
|
||||
/// chains multiple sync bytecode calls (e.g. sort comparator, accessor).
|
||||
/// `call_bytecode_function_inline` 使用的堆分配快照。
|
||||
/// 放在堆上避免 JS 代码链式同步字节码调用(如 sort 比较器、accessor)时
|
||||
/// 耗尽 Rust 栈。
|
||||
pub(crate) struct InlineSyncState {
|
||||
pub(crate) regs: Box<[JsValue; 256]>,
|
||||
pub(crate) pc: usize,
|
||||
@@ -174,12 +174,12 @@ pub struct Vm {
|
||||
pub(crate) regs: [JsValue; 256],
|
||||
pub(crate) pc: usize,
|
||||
pub(crate) bytecode: Vec<opcode::Instr>,
|
||||
/// Per-run convert-once immutables cache. Index 0 = top module, sub_idx+1 = sub_modules[sub_idx].
|
||||
/// Each `OnceLock` holds that module's constants converted to `JsValue`s exactly once this run.
|
||||
/// Rebuilt every `run()`. Immutables are scalars + perm-strings — read-only, never GC roots.
|
||||
/// 每次 run 转换一次的不可变常量缓存。下标 0 = 顶层模块,sub_idx+1 = sub_modules[sub_idx]。
|
||||
/// 每个 `OnceLock` 保存该模块常量本次运行中只转换一次的 `JsValue` 结果,每次 `run()` 重建。
|
||||
/// 不可变常量是标量 + perm 字符串,只读,不作为 GC 根。
|
||||
pub(crate) immutables_cache: Vec<OnceLock<Vec<JsValue>>>,
|
||||
/// Read-only view into the currently-active module's converted immutables (inside immutables_cache).
|
||||
/// A fat `*const` because the cache Vec is VM-owned and run-stable (OnceLock filled once).
|
||||
/// 当前活动模块已转换不可变常量的只读视图(指向 immutables_cache 内部)。
|
||||
/// 用胖 `*const`:缓存 Vec 归 VM 所有且本次运行稳定(OnceLock 只填一次)。
|
||||
pub(crate) active_immutables: *const [JsValue],
|
||||
pub(crate) frames: SmallVec<[CallFrame; 16]>,
|
||||
pub(crate) kernel_core: Arc<KernelCore>,
|
||||
@@ -190,40 +190,40 @@ pub struct Vm {
|
||||
pub(crate) sub_modules: Arc<Vec<CompiledModule>>,
|
||||
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
|
||||
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
|
||||
/// Shared register save-stack. Each active `CallFrame` saved its caller's live
|
||||
/// registers (`regs[..caller_reg_limit]`) here at `saved_reg_offset`; restore copies
|
||||
/// them back and truncates. Capacity is retained across calls — zero per-call heap alloc.
|
||||
/// 共享寄存器保存栈。每个活动 `CallFrame` 在 push 时把调用方活跃寄存器
|
||||
/// (`regs[..caller_reg_limit]`)按 `saved_reg_offset` 存到这里;恢复时复制回
|
||||
/// 并截断。容量跨调用保留,避免每次调用堆分配。
|
||||
pub(crate) save_stack: Vec<JsValue>,
|
||||
/// VM 级 spill 栈(D-08,仿 save_stack 帧边界)。`CallFrame.spill_offset` 定位本帧区,
|
||||
/// 调用子函数时从边界后分配,restore_frame 截断恢复。
|
||||
/// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配,
|
||||
/// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。
|
||||
pub(crate) spill_stack: Vec<JsValue>,
|
||||
pub(crate) try_stack: Vec<TryHandler>,
|
||||
pub(crate) exception_value: Option<JsValue>,
|
||||
/// Side-channel carrying the JsValue thrown by a sync call whose error was flattened
|
||||
/// to a String by `call_function_sync`/`unwind`, so for-of can re-throw the original
|
||||
/// value. Plain field (NOT in InlineSyncState) so it survives the inline-call restore.
|
||||
/// 同步调用抛出的原始 JsValue 侧通道:`call_function_sync`/`unwind` 把错误展平为
|
||||
/// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中)
|
||||
/// 以便跨内联调用的恢复过程存活。
|
||||
pub(crate) last_uncaught_value: Option<JsValue>,
|
||||
pub(crate) pending_exception: Option<JsValue>,
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
pub(crate) root_reg_limit: u8,
|
||||
pub(crate) active_reg_limit: u8,
|
||||
pub(crate) native_call_depth: usize,
|
||||
/// Set to Some(target_reg) when `ordinary_get` pushes a bytecode accessor frame.
|
||||
/// The dispatch loop checks this flag and skips writing `regs[target_reg]` from the
|
||||
/// call result — the value will be delivered by the RETURN handler instead.
|
||||
/// `ordinary_get` 压入字节码 accessor 帧时设为 Some(target_reg)。
|
||||
/// 调度循环检查该标志,跳过用调用结果写 `regs[target_reg]` —— 值改由 RETURN
|
||||
/// 处理器交付。
|
||||
pub(crate) accessor_frame_target_reg: Option<u8>,
|
||||
/// Grouped session-arena / GC bookkeeping.
|
||||
/// 分组保存 session arena / GC 簿记状态。
|
||||
pub(crate) gc_state: GcState,
|
||||
/// Grouped `Symbol` interning state.
|
||||
/// 分组保存 `Symbol` intern 状态。
|
||||
pub(crate) symbols: SymbolState,
|
||||
/// Grouped live `for-in` / `for-of` iterator state.
|
||||
/// 分组保存活跃的 for-in / for-of 迭代器状态。
|
||||
pub(crate) iters: IterState,
|
||||
/// Grouped inline-cache and instruction counters.
|
||||
/// 分组保存 inline cache 与指令计数器。
|
||||
pub(crate) profiling: ProfilingState,
|
||||
pub(crate) sub_module_stack: Vec<Arc<Vec<CompiledModule>>>,
|
||||
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||||
pub(crate) temp_immutables: Vec<Vec<JsValue>>,
|
||||
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
|
||||
/// 可复用字符串缓冲区,避免每次 `+` 拼接都分配。
|
||||
pub(crate) string_buf: String,
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ impl Vm {
|
||||
if obj_ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
// SAFETY: obj_ptr is non-null and points to a `JsObject` owned by this session.
|
||||
// SAFETY: obj_ptr 非空且指向本 session 拥有的 JsObject。
|
||||
unsafe { (*obj_ptr).is_session_epoch() }
|
||||
}
|
||||
|
||||
@@ -352,25 +352,25 @@ impl Vm {
|
||||
ptr
|
||||
}
|
||||
|
||||
/// Read-only view into the active module's converted immutables. Empty before any `run()`.
|
||||
/// 活动模块已转换不可变常量的只读视图;任何 `run()` 之前为空。
|
||||
#[inline(always)]
|
||||
pub(crate) fn immutables(&self) -> &[JsValue] {
|
||||
if self.active_immutables.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
// SAFETY: active_immutables points into a OnceLock<Vec<JsValue>> inside immutables_cache,
|
||||
// which the VM owns; the Vec is filled once and never reallocated for the run's lifetime.
|
||||
// SAFETY: active_immutables 指向 VM 拥有的 immutables_cache 内的 OnceLock<Vec<JsValue>>,
|
||||
// Vec 只填充一次,本次运行期间不再重分配。
|
||||
unsafe { &*self.active_immutables }
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate module `cache_idx`'s immutables (0 = top, sub_idx+1 = sub_modules[sub_idx]),
|
||||
/// converting them once into `immutables_cache[cache_idx]` and pointing `active_immutables` at
|
||||
/// the cached Vec. `constants` is passed by the caller (it already holds `&module.constants`).
|
||||
/// 激活模块 `cache_idx` 的不可变常量(0 = 顶层,sub_idx+1 = sub_modules[sub_idx]),
|
||||
/// 只转换一次存入 `immutables_cache[cache_idx]`,并把 `active_immutables` 指向该 Vec。
|
||||
/// `constants` 由调用方传入(它已持有 `&module.constants`)。
|
||||
pub(crate) fn activate_immutables(&mut self, cache_idx: usize, constants: &[Constant]) {
|
||||
// Raw-ptr the cache slot so `get_or_init` (which borrows immutables_cache) and the &self
|
||||
// convert_immutables closure don't conflict with the subsequent self.active_immutables write.
|
||||
// Sound: immutables_cache is VM-owned, read-only, and run-stable.
|
||||
// 用裸指针访问缓存槽,避免 get_or_init(借用 immutables_cache)与 &self 的
|
||||
// convert_immutables 闭包和随后对 active_immutables 的写入发生借用冲突。
|
||||
// 成立前提:immutables_cache 归 VM 所有、只读、本次运行稳定。
|
||||
let slot: *const OnceLock<Vec<JsValue>> = &self.immutables_cache[cache_idx];
|
||||
let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants));
|
||||
self.active_immutables = vec.as_slice() as *const [JsValue];
|
||||
@@ -413,7 +413,7 @@ impl Vm {
|
||||
f(v);
|
||||
}
|
||||
f(self.iters.last_for_of_result);
|
||||
// Converted immutables (scalars + perm-strings) are NOT session GC roots — not scanned.
|
||||
// 已转换的不可变常量(标量 + perm 字符串)不作为 session GC 根,不参与扫描。
|
||||
for iter in &self.iters.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
@@ -569,7 +569,7 @@ impl Vm {
|
||||
if !val.is_string() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: val is a string value; its JsString pointer is alive for its lifetime.
|
||||
// SAFETY: val 是字符串值,其 JsString 指针在生命周期内有效。
|
||||
Some(unsafe { (*val.as_string_ptr()).data.clone() })
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ impl Vm {
|
||||
|
||||
pub(crate) fn property_key_si(&mut self, val: JsValue) -> u32 {
|
||||
if val.is_string() {
|
||||
// SAFETY: val is a string value; bridge its content to a permanent key id.
|
||||
// SAFETY: val 是字符串值,把其内容桥接为永久 key id。
|
||||
let s = unsafe { &(*val.as_string_ptr()).data };
|
||||
return self.kernel_core.perm_interner().intern(s).0;
|
||||
}
|
||||
@@ -702,8 +702,8 @@ impl Vm {
|
||||
}
|
||||
let saved_regs = self.regs;
|
||||
let arg_regs = self.pack_sync_native_call_args(receiver, callee, args);
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
// SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置;
|
||||
// native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) };
|
||||
self.native_call_depth += 1;
|
||||
let result = func(self, &arg_regs);
|
||||
@@ -719,10 +719,9 @@ impl Vm {
|
||||
};
|
||||
}
|
||||
|
||||
// Run bytecode function inline on self (same epoch) to prevent use-after-free.
|
||||
// A separate sub-VM would own a different epoch; returning a JsValue that contains
|
||||
// a pointer into the sub-VM epoch and then dropping the sub-VM causes a dangling
|
||||
// pointer / access violation in release builds.
|
||||
// 字节码函数在自身(同一 epoch)内联执行,防止 use-after-free。
|
||||
// 独立子 VM 拥有不同 epoch:返回含子 VM epoch 指针的 JsValue 后再销毁子 VM,
|
||||
// 会在 release 构建中产生悬垂指针 / 访问违规。
|
||||
self.call_bytecode_function_inline(callee, callee_obj, receiver, args)
|
||||
}
|
||||
|
||||
@@ -1448,7 +1447,7 @@ mod tests {
|
||||
let proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject;
|
||||
let mut obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||||
obj.set_function(true);
|
||||
// SAFETY: f is a NativeFn fn-item; valid to store as NativeFnPtr.
|
||||
// SAFETY: f 是 NativeFn 函数项,可作为 NativeFnPtr 存储。
|
||||
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) }));
|
||||
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ impl Vm {
|
||||
let reg = (seg & 0x7F) as u8;
|
||||
let val = self.regs[reg as usize];
|
||||
let s = if val.is_string() {
|
||||
// SAFETY: val is a string value.
|
||||
// SAFETY: val 是字符串值。
|
||||
unsafe { (*val.as_string_ptr()).data.clone() }
|
||||
} else {
|
||||
format!("{}", val)
|
||||
@@ -191,7 +191,7 @@ impl Vm {
|
||||
if const_idx < imm.len() {
|
||||
let val = imm[const_idx];
|
||||
if val.is_string() {
|
||||
// SAFETY: val is a string value.
|
||||
// SAFETY: val 是字符串值。
|
||||
let s = unsafe { (*val.as_string_ptr()).data.clone() };
|
||||
result.push_str(&s);
|
||||
}
|
||||
@@ -285,7 +285,7 @@ impl Vm {
|
||||
vm_trace!("FOR_IN_INIT r{}={:?}", a, self.regs[a]);
|
||||
let obj_val = self.regs[a];
|
||||
if obj_val.is_null() || obj_val.is_undefined() {
|
||||
// null/undefined enumerate nothing — an empty for-in, not a TypeError.
|
||||
// null/undefined 枚举不到任何键——是空 for-in,而非 TypeError。
|
||||
let keys_vec: bumpalo::collections::Vec<(JsValue, u32)> =
|
||||
bumpalo::collections::Vec::new_in(self.epoch.bump());
|
||||
let iter = self.epoch.alloc(ForInIter { keys: keys_vec, index: 0 });
|
||||
@@ -293,7 +293,7 @@ impl Vm {
|
||||
return Ok(());
|
||||
}
|
||||
if !obj_val.is_object() {
|
||||
// ToObject coercion for other primitives is not implemented yet — TypeError is correct until it lands.
|
||||
// 未支持:其它基本类型的 ToObject 强转尚未实现,在此之前抛 TypeError 是正确行为。
|
||||
return self.raise_type_error("for-in right-hand side is not an object");
|
||||
}
|
||||
|
||||
@@ -302,9 +302,8 @@ impl Vm {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut current = obj_val;
|
||||
|
||||
// Arrays store integer-indexed elements in prop_vec, which is not part of
|
||||
// the shape chain. Enumerate them (ES: array indices are enumerable string
|
||||
// keys, ordered ascending before other own keys).
|
||||
// 数组把整型下标元素存在 prop_vec,而 prop_vec 不属于 shape 链。
|
||||
// 单独枚举它们(ES:数组下标是可枚举字符串键,按升序排在其它自有键之前)。
|
||||
if current.is_object() {
|
||||
let arr = unsafe { &*current.as_js_object_ptr() };
|
||||
if arr.is_array() {
|
||||
@@ -315,10 +314,7 @@ impl Vm {
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable());
|
||||
if is_enum {
|
||||
let idx = self.kernel_core.perm_interner().intern(&i.to_string()).0;
|
||||
keys_vec.push((
|
||||
JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)),
|
||||
idx,
|
||||
));
|
||||
keys_vec.push((JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)), idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,14 +361,13 @@ impl Vm {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// The shape chain is walked leaf->root (reverse of insertion order);
|
||||
// flip this object's slice back to insertion order before its proto.
|
||||
// shape 链按叶→根遍历(与插入序相反);在接上原型前把本对象的切片
|
||||
// 翻转为插入序。
|
||||
keys_vec[obj_start..].reverse();
|
||||
current = cur.proto();
|
||||
}
|
||||
|
||||
// ES enumeration order: integer-index keys ascending, then the rest in
|
||||
// insertion order. Stable sort preserves insertion order among non-index keys.
|
||||
// ES 枚举序:整型下标键升序,其余按插入序。稳定排序保持非下标键间的插入序。
|
||||
keys_vec.sort_by(|(_, a_si), (_, b_si)| {
|
||||
match (self.array_index_from_property_key(*a_si), self.array_index_from_property_key(*b_si)) {
|
||||
(Some(ai), Some(bi)) => ai.cmp(&bi),
|
||||
@@ -488,9 +483,9 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// next()/value access threw: route through unwind so a surrounding try/catch can catch
|
||||
/// it, re-throwing the ORIGINAL value when available. Per ECMA-262 the iterator is NOT
|
||||
/// closed via return() on a next()-throw — pop it so the unwinding IteratorClose pass skips it.
|
||||
/// next()/value 访问抛出:经 unwind 走异常展开,使外围 try/catch 能捕获,并尽可能
|
||||
/// 重新抛出原始值。按 ECMA-262,next() 抛出时不会经 return() 关闭迭代器——
|
||||
/// 先弹出它,使展开时的 IteratorClose 遍历跳过该迭代器。
|
||||
fn throw_for_of_error(&mut self, msg: String) -> Result<(), String> {
|
||||
self.iters.pop_for_of();
|
||||
let exc = match self.last_uncaught_value.take() {
|
||||
@@ -507,14 +502,16 @@ impl Vm {
|
||||
let Some(iterator) = self.iters.pop_for_of() else {
|
||||
return Ok(());
|
||||
};
|
||||
// Normal / break / return exit: no prior abrupt completion, so return()'s own throw propagates.
|
||||
// 正常 / break / return 退出:此前无进行中的突然完成,return() 自身的抛出直接传播。
|
||||
self.close_for_of_iterator(iterator, false)
|
||||
}
|
||||
|
||||
/// Call `iterator.return()` (IteratorClose). When `suppress_return_error` is true the call
|
||||
/// is being made because an enclosing abrupt completion is unwinding: return()'s own result
|
||||
/// is discarded and the in-flight exception is preserved across the call. When false (normal
|
||||
/// for-of exit) return()'s error propagates.
|
||||
/// 调用 `iterator.return()`(IteratorClose)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `suppress_return_error` 为 true 时表示正在展开某个外围突然完成:return() 的
|
||||
/// 自身结果被丢弃,并保留在途异常跨调用存活。
|
||||
/// - 为 false 时(正常 for-of 退出)return() 的错误向外传播。
|
||||
pub(crate) fn close_for_of_iterator(
|
||||
&mut self, iterator: JsValue, suppress_return_error: bool,
|
||||
) -> Result<(), String> {
|
||||
@@ -549,9 +546,9 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// IteratorClose every active for-of iterator above `depth`, used by `unwind()` to close
|
||||
/// loops abandoned by a throw. Pops before closing so a re-entrant call can't re-close,
|
||||
/// and so the next()-throw path (which already popped its own iterator) is skipped.
|
||||
/// 对 `depth` 以上的所有活跃 for-of 迭代器执行 IteratorClose,供 `unwind()` 关闭
|
||||
/// 被异常中断的循环。先弹出再关闭,防止重入调用重复关闭,同时跳过 next()-throw
|
||||
/// 路径(它已弹出自己的迭代器)。
|
||||
pub(crate) fn close_for_of_above(&mut self, depth: usize) {
|
||||
while self.iters.for_of_iters.len() > depth {
|
||||
let Some(iterator) = self.iters.pop_for_of() else {
|
||||
@@ -574,7 +571,7 @@ impl Vm {
|
||||
.get(excluded_idx)
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
// SAFETY: v is a string constant value.
|
||||
// SAFETY: v 是字符串常量值。
|
||||
Some(unsafe { (*v.as_string_ptr()).data.clone() })
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -124,7 +124,7 @@ impl Vm {
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false)
|
||||
}
|
||||
|
||||
/// Dispatch-time entry: caller (dispatch_set_prop etc.) already promoted the value.
|
||||
/// 分发期入口:调用方(dispatch_set_prop 等)已对值做过 promote。
|
||||
pub(crate) fn ordinary_set_dispatch(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
|
||||
) -> Result<(), String> {
|
||||
@@ -235,17 +235,17 @@ impl Vm {
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Perform ordinary_get and write back IC with proto chain depth.
|
||||
/// 执行 ordinary_get 并把 IC 按原型链深度写回。
|
||||
fn proto_chain_ic_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result<JsValue, String> {
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, receiver)?;
|
||||
// Fast path: own property (depth=0).
|
||||
// 快路径:自身属性(depth=0)。
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if !obj.is_accessor_meta(pos) {
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
|
||||
}
|
||||
return Ok(resolved);
|
||||
}
|
||||
// Walk proto chain for inherited properties.
|
||||
// 沿原型链查找继承属性。
|
||||
let mut cursor = obj.proto().as_js_object_ptr();
|
||||
let mut depth = 1u8;
|
||||
while !cursor.is_null() {
|
||||
|
||||
@@ -138,7 +138,7 @@ impl Vm {
|
||||
let len = frame.caller_reg_limit as usize;
|
||||
self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]);
|
||||
self.save_stack.truncate(offset);
|
||||
// spill 帧边界截断(D-08,仿 save_stack 窗口):子函数 spill 数据在帧恢复后丢弃
|
||||
// spill 帧边界截断:子函数写入的 spill 数据在帧恢复后丢弃。
|
||||
self.spill_stack.truncate(frame.spill_offset as usize);
|
||||
self.regs[254] = frame.saved_this;
|
||||
self.regs[255] = frame.saved_new_target;
|
||||
@@ -192,7 +192,7 @@ impl Vm {
|
||||
self.restore_frame(frame);
|
||||
}
|
||||
}
|
||||
// IteratorClose any for-of loops abandoned inside this handler's scope.
|
||||
// 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。
|
||||
self.close_for_of_above(handler.for_of_depth);
|
||||
if let Some(finally_pc) = handler.finally_pc {
|
||||
vm_trace!("unwind: entering finally at pc={}", finally_pc);
|
||||
@@ -216,8 +216,8 @@ impl Vm {
|
||||
self.restore_frame(frame);
|
||||
}
|
||||
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
|
||||
// Preserve the escaping JsValue so a for-of next()-throw can re-throw the original
|
||||
// value instead of the flattened string (consumed by dispatch_for_of_done/next).
|
||||
// 保留逃逸的 JsValue,使 for-of 的 next() 抛出时可重新抛出原值而非展平后的
|
||||
// 字符串(由 dispatch_for_of_done/next 消费)。
|
||||
self.last_uncaught_value = Some(exc);
|
||||
let kind_str = self.pending_error_kind.take().unwrap_or("Error");
|
||||
let exc_text = self.error_text(exc);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
//! Grouped `Vm` state sub-structs.
|
||||
//! `Vm` 的状态子结构分组。
|
||||
//!
|
||||
//! `Vm` owns these as fields (`gc_state`, `symbols`, `iters`, `profiling`) so
|
||||
//! that workers refactoring one subsystem touch one struct instead of the
|
||||
//! central `Vm` god-struct. Per the project's parallel-dev decisions:
|
||||
//! Symbol/Iter/Profiling are full sinks (their logic is self-contained), while
|
||||
//! `GcState` is field-classification only — GC `mark`/`sweep`/`alloc_object`
|
||||
//! scan roots across *all* sub-structs and stay as `&mut Vm` methods.
|
||||
//! `Vm` 以字段持有这些子结构(`gc_state`、`symbols`、`iters`、`profiling`),
|
||||
//! 使各子系统维护者只需改动一个结构而非庞大的 `Vm` 主结构。约定:
|
||||
//! Symbol/Iter/Profiling 为完整子模块(逻辑自包含);`GcState` 仅做字段分类——
|
||||
//! GC 的 mark/sweep/alloc_object 需要扫描跨全部子结构的根(regs、frames、
|
||||
//! 迭代器等),仍以 `&mut Vm` 方法驻留。
|
||||
//!
|
||||
//! This file defines fields only; methods live with `Vm`.
|
||||
//! 本文件只定义字段;方法随 `Vm` 存放。
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
@@ -20,11 +19,11 @@ use oxide_types::value::JsValue;
|
||||
use crate::session_gc::SessionGc;
|
||||
use crate::vm::ForInIter;
|
||||
|
||||
/// Session-arena and garbage-collection bookkeeping.
|
||||
/// session arena 与 GC 簿记。
|
||||
///
|
||||
/// Field-classify only. `mark`/`sweep`/`rewrite_vm_roots` live on `Vm` because
|
||||
/// GC scans all sub-struct roots (regs, frames, for_in_iters, for_of_iters,
|
||||
/// exception_value, etc.) — they cannot be confined to GcState.
|
||||
/// 仅做字段分类。mark/sweep/rewrite_vm_roots 驻留在 `Vm` 上:GC 需扫描所有
|
||||
/// 子结构的根(regs、frames、for_in_iters、for_of_iters、exception_value 等),
|
||||
/// 无法限制在 GcState 内。
|
||||
pub(crate) struct GcState {
|
||||
pub(crate) session_epoch: bumpalo::Bump,
|
||||
pub(crate) session_gc: SessionGc,
|
||||
@@ -41,7 +40,7 @@ impl GcState {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Symbol` interning state.
|
||||
/// Symbol 的 intern 状态。
|
||||
pub(crate) struct SymbolState {
|
||||
pub(crate) symbol_counter: u32,
|
||||
pub(crate) symbol_descriptions: Vec<String>,
|
||||
@@ -83,7 +82,7 @@ impl SymbolState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Live iterator state for `for-in` / `for-of`.
|
||||
/// for-in / for-of 的活跃迭代器状态。
|
||||
pub(crate) struct IterState {
|
||||
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) for_of_iters: Vec<JsValue>,
|
||||
@@ -134,7 +133,7 @@ impl IterState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inline-cache hit/miss and instruction counters.
|
||||
/// inline cache 命中/未命中计数与指令计数。
|
||||
pub(crate) struct ProfilingState {
|
||||
pub(crate) ic_hits: Cell<u64>,
|
||||
pub(crate) ic_misses: Cell<u64>,
|
||||
|
||||
@@ -203,11 +203,11 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn clear_execution_state(&mut self) {
|
||||
// Reset contract:
|
||||
// - Clears register file, pc, frame/iterator stacks, saved execution stacks,
|
||||
// try handlers, pending exceptions, and native call depth.
|
||||
// - Leaves kernel-owned shared state intact.
|
||||
// - `reset()` additionally clears bytecode/constants and resets epoch ownership.
|
||||
// 重置契约:
|
||||
// - 清空寄存器文件、pc、帧/迭代器栈、保存的执行栈、try 处理器、
|
||||
// 待处理异常与 native 调用深度。
|
||||
// - 保留 kernel 共享状态不变。
|
||||
// - `reset()` 额外清空 bytecode/constants 并重置 epoch 归属。
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
self.pc = 0;
|
||||
self.frames.clear();
|
||||
@@ -244,7 +244,7 @@ impl Vm {
|
||||
self.new_string_owned(s.to_string())
|
||||
}
|
||||
|
||||
/// Same as `new_string` but takes `String` by move, avoiding a clone.
|
||||
/// 同 `new_string`,但以 move 接收 `String`,避免一次克隆。
|
||||
pub fn new_string_owned(&mut self, s: String) -> JsValue {
|
||||
let len = s.len();
|
||||
let ptr = Box::into_raw(Box::new(JsString::new(s)));
|
||||
@@ -258,34 +258,30 @@ impl Vm {
|
||||
self.kernel_core.perm_interner().intern(s).0
|
||||
}
|
||||
|
||||
/// Intern a compile-time string literal as a permanent, process-lifetime
|
||||
/// `JsString` value shared across sessions. Source literals and RegExp
|
||||
/// source/flags recur (templated/repetitive code) and are immutable, so
|
||||
/// sharing them via `PermInterner` restores cross-session string reuse —
|
||||
/// without interning transient computed values, which stay session-heap
|
||||
/// (`new_string`) so they remain collectable.
|
||||
/// 把编译期字符串字面量 intern 为永久、进程生命周期内、跨 session 共享的
|
||||
/// `JsString` 值。源码字面量与 RegExp 的 source/flags 会反复出现(模板化/
|
||||
/// 重复代码)且不可变,经 `PermInterner` 共享可恢复跨 session 字符串复用——
|
||||
/// 不 intern 瞬态计算值(它们走 session 堆 `new_string`,保持可回收)。
|
||||
pub fn perm_string(&self, s: &str) -> JsValue {
|
||||
let id = self.kernel_core.perm_interner().intern(s).0;
|
||||
JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(id))
|
||||
}
|
||||
|
||||
/// Drop all session-heap `JsString` values. Called only on full isolation reset
|
||||
/// (`full_reset` / `clear_full_reset_state`), where no surviving session object
|
||||
/// can reference them. The lighter `reset()` deliberately keeps them alive,
|
||||
/// mirroring session-object survival across evals.
|
||||
/// 释放全部 session 堆 `JsString`。仅在完全隔离重置
|
||||
/// (`full_reset` / `clear_full_reset_state`)时调用,此时没有存活的 session 对象
|
||||
/// 会引用它们。较轻量的 `reset()` 刻意保留它们,与 session 对象跨 eval 存活一致。
|
||||
fn free_session_string_heap_data(&mut self) {
|
||||
for ptr in self.gc_state.session_string_ptrs.drain(..) {
|
||||
// SAFETY: each ptr came from Box::into_raw(Box::new(JsString)) in new_string
|
||||
// and is dropped exactly once here.
|
||||
// SAFETY: 每个指针来自 new_string 中的 Box::into_raw(Box::new(JsString)),
|
||||
// 且只在这里恰好释放一次。
|
||||
unsafe {
|
||||
drop(Box::from_raw(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a function JsObject for a BytecodeFunc constant.
|
||||
/// When `is_arrow` is true, captures the current `this` (regs[254])
|
||||
/// for lexical this binding at call time.
|
||||
/// 为 BytecodeFunc 常量创建函数 JsObject。
|
||||
/// 当 `is_arrow` 为 true 时,捕获当前 `this`(regs[254]),供调用时词法 this 绑定。
|
||||
pub(crate) fn create_function_object(
|
||||
&mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool,
|
||||
needs_home_object: bool,
|
||||
@@ -364,9 +360,9 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a module's immutable constant pool to `JsValue`s. Infallible — the pool holds only
|
||||
/// scalars + permanent-interner string values after CreateClosure/CreateRegExp pulled functions
|
||||
/// and regexes out of the pool. `&self` so it can run inside `OnceLock::get_or_init`.
|
||||
/// 把模块的不可变常量池转换为 `JsValue`。不可能失败——CreateClosure/CreateRegExp
|
||||
/// 已把函数与正则移出常量池,池中只剩标量 + 永久 intern 字符串值。`&self`
|
||||
/// 使其可运行于 `OnceLock::get_or_init` 内部。
|
||||
pub(crate) fn convert_immutables(&self, constants: &[Constant]) -> Vec<JsValue> {
|
||||
constants.iter().map(|c| self.convert_constant(c)).collect()
|
||||
}
|
||||
@@ -491,13 +487,13 @@ mod tests {
|
||||
#[test]
|
||||
fn immutables_cache_filled_once_per_module() {
|
||||
let mut vm = Vm::new();
|
||||
// `f` recurses (same sub-module entered 4x). Its immutables convert once via OnceLock.
|
||||
// `f` 递归(同一子模块进入 4 次),其不可变常量经 OnceLock 只转换一次。
|
||||
let result = run_source(&mut vm, "function f(n){ if(n<=0){ return 'done'; } return f(n-1); } f(3)");
|
||||
assert!(result.is_string());
|
||||
assert_eq!(vm.lookup_str(result).as_deref(), Some("done"));
|
||||
// Cache = top module + 1 sub-module (f); the sub-module slot was initialized by the calls.
|
||||
// 缓存 = 顶层模块 + 1 个子模块(f);子模块槽由这些调用初始化。
|
||||
assert_eq!(vm.immutables_cache.len(), 2);
|
||||
// Sub-module constants now use temp_immutables (avoiding cache index collision)
|
||||
// 子模块常量改走 temp_immutables(避免缓存下标冲突)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -112,9 +112,8 @@ fn multiple_arrows_in_same_scope() {
|
||||
assert_eq!(eval("var a = () => 1, b = () => 2; a() + b()"), "3");
|
||||
}
|
||||
|
||||
// NOTE: Arrow functions capturing lexical `this` from enclosing functions
|
||||
// require nested function call support (sub_module flattening), which is
|
||||
// a pre-existing limitation. These tests will be enabled in a future phase:
|
||||
// 未支持:箭头函数从外围函数捕获词法 `this` 依赖嵌套函数调用(子模块展平),
|
||||
// 是既有能力限制。以下测试待能力补齐后启用:
|
||||
//
|
||||
// fn arrow_captures_enclosing_this() {
|
||||
// // const o = {x:10, f:function(){ return (() => this.x)(); }}; o.f()
|
||||
@@ -127,4 +126,3 @@ fn multiple_arrows_in_same_scope() {
|
||||
fn closure_captures_outer_let() {
|
||||
assert_eq!(eval("var f; { let x = 7; f = () => x; } f()"), "7");
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ fn date_parse_invalid() {
|
||||
assert!(r.as_double().is_nan());
|
||||
}
|
||||
|
||||
// -- new Date() constructor via JS --
|
||||
// ── 经 JS 调用 new Date() 构造器 ──
|
||||
|
||||
#[test]
|
||||
fn date_new_multi_arg() {
|
||||
@@ -100,7 +100,7 @@ fn date_value_of() {
|
||||
#[test]
|
||||
fn date_to_iso_string() {
|
||||
let mut vm = Vm::new();
|
||||
// Parse an explicit ISO UTC string to avoid timezone dependency
|
||||
// 解析显式 ISO UTC 字符串,避免时区依赖。
|
||||
let r = eval(&mut vm, "new Date('2020-01-01T00:00:00Z').toISOString()").unwrap();
|
||||
let s = str_val(&vm, r);
|
||||
assert!(s.starts_with("2020-01-01"), "got: {s}");
|
||||
@@ -109,7 +109,7 @@ fn date_to_iso_string() {
|
||||
#[test]
|
||||
fn date_to_json() {
|
||||
let mut vm = Vm::new();
|
||||
// Parse an explicit ISO UTC string to avoid timezone dependency
|
||||
// 解析显式 ISO UTC 字符串,避免时区依赖。
|
||||
let r = eval(&mut vm, "new Date('2020-01-01T00:00:00Z').toJSON()").unwrap();
|
||||
let s = str_val(&vm, r);
|
||||
assert!(s.starts_with("2020-01-01"), "got: {s}");
|
||||
@@ -136,7 +136,7 @@ fn date_objects_use_bounded_numeric_coercion() {
|
||||
assert_eq!(r.as_double(), 0.0);
|
||||
}
|
||||
|
||||
// -- Plan 01 timezone-correct tests --
|
||||
// ── 时区正确性测试 ──
|
||||
|
||||
#[test]
|
||||
fn date_local_getters_differ_from_utc() {
|
||||
@@ -145,7 +145,7 @@ fn date_local_getters_differ_from_utc() {
|
||||
let local_hour = r.as_double();
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 6, 15, 12, 0, 0); d.getUTCHours()").unwrap();
|
||||
let utc_hour = r.as_double();
|
||||
// In UTC+0 they are equal (correct), otherwise they differ
|
||||
// 在 UTC+0 下二者相等(正确),否则不相等。
|
||||
if utc_hour != local_hour {
|
||||
assert_ne!(local_hour, utc_hour, "in non-UTC+0, local vs UTC hours should differ");
|
||||
}
|
||||
@@ -155,7 +155,7 @@ fn date_local_getters_differ_from_utc() {
|
||||
#[test]
|
||||
fn date_constructor_uses_local_timezone() {
|
||||
let mut vm = Vm::new();
|
||||
// getTimezoneOffset = UTC - local; UTC hour = local + offset/60
|
||||
// getTimezoneOffset = UTC - local;UTC 小时 = local + offset/60
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 0, 1, 12, 0, 0); d.getUTCHours()").unwrap();
|
||||
let utc_hour = r.as_double();
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 0, 1, 12, 0, 0); d.getTimezoneOffset()").unwrap();
|
||||
@@ -167,7 +167,7 @@ fn date_constructor_uses_local_timezone() {
|
||||
#[test]
|
||||
fn date_constructor_defaults_from_current_time() {
|
||||
let mut vm = Vm::new();
|
||||
// D-02: missing args default to current time components
|
||||
// 缺省参数回退到当前时间分量。
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 5); d.getMonth()").unwrap();
|
||||
assert_eq!(r.as_double(), 5.0);
|
||||
let r = eval(&mut vm, "new Date(2020, 5).getDate()").unwrap();
|
||||
@@ -177,7 +177,7 @@ fn date_constructor_defaults_from_current_time() {
|
||||
#[test]
|
||||
fn date_constructor_truncation() {
|
||||
let mut vm = Vm::new();
|
||||
// D-03: ToInteger truncation
|
||||
// 年月参数按 ToInteger 截断。
|
||||
let r = eval(&mut vm, "new Date(2024.7, 5.9).getFullYear()").unwrap();
|
||||
assert_eq!(r.as_double(), 2024.0);
|
||||
let r = eval(&mut vm, "new Date(2024.7, 5.9).getMonth()").unwrap();
|
||||
@@ -187,7 +187,7 @@ fn date_constructor_truncation() {
|
||||
#[test]
|
||||
fn date_setters_return_timestamp() {
|
||||
let mut vm = Vm::new();
|
||||
// D-05: setters return modified timestamp
|
||||
// setter 返回修改后的时间戳。
|
||||
let r = eval(&mut vm, "var d = new Date(0); var ts = d.setHours(12); ts == d.getTime() ? 1 : 0").unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
@@ -195,12 +195,12 @@ fn date_setters_return_timestamp() {
|
||||
#[test]
|
||||
fn date_invalid_returns_nan() {
|
||||
let mut vm = Vm::new();
|
||||
// D-06: Invalid Date getters/setters return NaN
|
||||
// Invalid Date 的 getter/setter 返回 NaN。
|
||||
let r = eval(&mut vm, "new Date(NaN).getFullYear()").unwrap();
|
||||
assert!(r.as_double().is_nan());
|
||||
let r = eval(&mut vm, "new Date(NaN).setHours(0)").unwrap();
|
||||
assert!(r.as_double().is_nan());
|
||||
// D-04: Invalid Date constructor
|
||||
// Invalid Date 构造(NaN 参数)。
|
||||
let r = eval(&mut vm, "new Date(NaN, 0).getTime()").unwrap();
|
||||
assert!(r.as_double().is_nan());
|
||||
}
|
||||
@@ -208,7 +208,7 @@ fn date_invalid_returns_nan() {
|
||||
#[test]
|
||||
fn date_get_timezone_offset_uses_date_timestamp() {
|
||||
let mut vm = Vm::new();
|
||||
// D-09/D-10: offset uses date's timestamp, not now()
|
||||
// 偏移基于日期的自身时间戳,而非当前时间。
|
||||
let r = eval(&mut vm, "new Date(0).getTimezoneOffset()").unwrap();
|
||||
assert!(r.as_double().is_finite());
|
||||
}
|
||||
@@ -216,7 +216,7 @@ fn date_get_timezone_offset_uses_date_timestamp() {
|
||||
#[test]
|
||||
fn date_setters_optional_chain() {
|
||||
let mut vm = Vm::new();
|
||||
// D-08: setFullYear supports optional month/day params
|
||||
// setFullYear 支持可选的月/日参数。
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 0, 1); d.setFullYear(2025, 11, 25); d.getFullYear()").unwrap();
|
||||
assert_eq!(r.as_double(), 2025.0);
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 0, 1); d.setFullYear(2025, 11, 25); d.getMonth()").unwrap();
|
||||
@@ -228,12 +228,12 @@ fn date_setters_optional_chain() {
|
||||
#[test]
|
||||
fn date_local_setters_preserve_utc_offset() {
|
||||
let mut vm = Vm::new();
|
||||
// setHours changes local hour; verify local hour value
|
||||
// setHours 改变本地小时,验证本地小时值。
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 0, 1, 0, 0, 0); d.setHours(12); d.getHours()").unwrap();
|
||||
assert_eq!(r.as_double(), 12.0, "setHours(12) should make getHours() return 12");
|
||||
}
|
||||
|
||||
// -- Plan 02 new method tests --
|
||||
// ── 后续新增方法测试 ──
|
||||
|
||||
#[test]
|
||||
fn date_set_utc_full_year_works() {
|
||||
@@ -313,7 +313,7 @@ fn date_get_year_deprecated() {
|
||||
#[test]
|
||||
fn date_set_year_deprecated() {
|
||||
let mut vm = Vm::new();
|
||||
// setYear(0-99) adds 1900; setYear(20) → fullYear 1920
|
||||
// setYear(0-99) 加 1900;setYear(20) → fullYear 1920。
|
||||
let r = eval(&mut vm, "var d = new Date(2020, 0, 1); d.setYear(20); d.getFullYear()").unwrap();
|
||||
assert_eq!(r.as_double(), 1920.0);
|
||||
}
|
||||
@@ -347,8 +347,8 @@ fn date_set_utc_invalid_returns_nan() {
|
||||
assert!(r.as_double().is_nan());
|
||||
}
|
||||
|
||||
// setYear with no argument must not index a missing argument register (previously panicked);
|
||||
// a missing / NaN year sets the date value to NaN and returns NaN (B.2.4.2).
|
||||
// setYear 无参数时不得索引缺失的参数寄存器(此前会 panic);
|
||||
// 缺失 / NaN 的年份把日期值置为 NaN 并返回 NaN(B.2.4.2)。
|
||||
#[test]
|
||||
fn date_set_year_no_arg_returns_nan() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -384,8 +384,8 @@ fn date_set_year_four_digit_kept() {
|
||||
assert_eq!(r.as_double() as i64, 2020);
|
||||
}
|
||||
|
||||
// UTC setters called with no argument previously indexed a missing argument register
|
||||
// (panic / process abort). A missing primary argument now yields NaN and sets the date NaN.
|
||||
// UTC setter 无参数调用此前会索引缺失的参数寄存器(panic / 进程中止)。
|
||||
// 缺失的主参数现在产出 NaN 并把日期置为 NaN。
|
||||
#[test]
|
||||
fn date_set_utc_setters_no_arg_return_nan() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
@@ -144,7 +144,7 @@ fn error_subtype_constructors_produce_named_objects() {
|
||||
assert_eq!(format!("{}", eval("new SyntaxError('boom').name == 'SyntaxError'").unwrap()), "true");
|
||||
}
|
||||
|
||||
// New tests for prototype properties + constructor fixes
|
||||
// ── 原型属性与构造器修复测试 ──
|
||||
|
||||
#[test]
|
||||
fn error_prototype_has_name() {
|
||||
@@ -276,7 +276,7 @@ fn create_type_error_no_own_name() {
|
||||
assert_eq!(obj.prop_count(), 1);
|
||||
}
|
||||
|
||||
// Plan 02: toString tests
|
||||
// ── toString 测试 ──
|
||||
|
||||
#[test]
|
||||
fn error_to_string_basic() {
|
||||
@@ -319,7 +319,7 @@ fn error_to_string_non_object_throws() {
|
||||
}
|
||||
}
|
||||
|
||||
// format_error_message tests
|
||||
// ── format_error_message 测试 ──
|
||||
|
||||
#[test]
|
||||
fn format_error_message_both() {
|
||||
@@ -339,7 +339,7 @@ fn format_error_message_empty_name() {
|
||||
assert_eq!(result, "msg");
|
||||
}
|
||||
|
||||
// stack tests
|
||||
// ── stack 测试 ──
|
||||
|
||||
#[test]
|
||||
fn error_stack_is_string() {
|
||||
|
||||
@@ -95,9 +95,9 @@ fn function_to_string_non_function_throws() {
|
||||
|
||||
#[test]
|
||||
fn function_call_returns_object_stays_valid() {
|
||||
// Regression: call_function_sync used to run bytecode in a sub-VM with a separate
|
||||
// epoch; objects allocated in the sub-VM epoch became dangling after sub-VM drop.
|
||||
// This test forces the returned object to be dereferenced after the call returns.
|
||||
// 回归:call_function_sync 曾在一个带独立 epoch 的子 VM 中运行字节码;
|
||||
// 子 VM epoch 中分配的对象在子 VM 销毁后成为悬垂指针。
|
||||
// 本测试强制在调用返回后解引用返回对象。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "function f() { return {x: 42}; } f.call(null).x").unwrap();
|
||||
assert_eq!(result, JsValue::int(42));
|
||||
@@ -112,8 +112,8 @@ fn function_apply_returns_object_stays_valid() {
|
||||
|
||||
#[test]
|
||||
fn getter_returns_object_stays_valid() {
|
||||
// Same class of bug: ordinary_get sync-path (target_reg=None) for bytecode
|
||||
// accessor ran in sub-VM; returned object was freed on sub-VM drop.
|
||||
// 同类 bug:字节码 accessor 经 ordinary_get 同步路径(target_reg=None)在子 VM
|
||||
// 中运行,返回对象在子 VM 销毁后被释放。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var o = { get p() { return {z: 7}; } }; o.p.z").unwrap();
|
||||
assert_eq!(result, JsValue::int(7));
|
||||
|
||||
@@ -65,7 +65,7 @@ fn tmap_size_and_clear() {
|
||||
assert_eq!(map_size(&mut vm, &[0]).unwrap().as_double(), 0.0);
|
||||
}
|
||||
|
||||
// -- JS eval tests using new keyword (all in one eval per test) --
|
||||
// ── 经 JS 使用 new 关键字的 eval 测试(每个测试一次 eval)──
|
||||
|
||||
#[test]
|
||||
fn map_new_set_get() {
|
||||
|
||||
@@ -166,7 +166,7 @@ fn object_proto_has_own_property_call_works() {
|
||||
#[test]
|
||||
fn object_proto_property_is_enumerable_call_works() {
|
||||
let (_vm, result) = eval("Object.prototype.propertyIsEnumerable.call(Object, 'assign')").unwrap();
|
||||
// Built-in methods are non-enumerable per the ES spec.
|
||||
// 内置方法按 ES 规范不可枚举。
|
||||
assert_eq!(result, JsValue::bool(false));
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ fn string_call_conversion_stays_primitive() {
|
||||
assert_eq!(to_str(&vm, v), "123");
|
||||
}
|
||||
|
||||
// ── Plan 01: replace tests ──
|
||||
// ── replace 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_replace_non_global_first_only() {
|
||||
@@ -305,7 +305,7 @@ fn string_replace_string_pattern() {
|
||||
assert_eq!(to_str(&vm, s), "heLlo");
|
||||
}
|
||||
|
||||
// ── Plan 02: split tests ──
|
||||
// ── split 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_split_regex_capture_groups() {
|
||||
@@ -348,7 +348,7 @@ fn string_split_string_separator() {
|
||||
assert_eq!(obj.prop_count(), 3);
|
||||
}
|
||||
|
||||
// ── Plan 03: match tests ──
|
||||
// ── match 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_match_non_global_captures() {
|
||||
@@ -390,7 +390,7 @@ fn string_match_string_pattern() {
|
||||
assert_eq!(to_str(&vm, obj.get_prop_at(0)), "ll");
|
||||
}
|
||||
|
||||
// ── Plan 04: matchAll tests ──
|
||||
// ── matchAll 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_match_all_returns_iterator() {
|
||||
@@ -420,7 +420,7 @@ fn string_match_all_next_exhausted() {
|
||||
assert!(done);
|
||||
}
|
||||
|
||||
// ── Plan 05: substring tests ──
|
||||
// ── substring 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_substring_nan_index() {
|
||||
@@ -443,7 +443,7 @@ fn string_substring_swap() {
|
||||
assert_eq!(to_str(&vm, s), "el");
|
||||
}
|
||||
|
||||
// ── Plan 06: substr + at tests ──
|
||||
// ── substr + at 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_substr_positive() {
|
||||
@@ -487,7 +487,7 @@ fn string_at_out_of_range() {
|
||||
assert!(s.is_undefined());
|
||||
}
|
||||
|
||||
// ── Plan 07: lastIndexOf tests ──
|
||||
// ── lastIndexOf 测试 ──
|
||||
|
||||
#[test]
|
||||
fn string_last_index_of_basic() {
|
||||
|
||||
@@ -113,32 +113,31 @@ fn primitive_property_write_throws_clean_type_error() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Phase 14.0.1: type-conversion infrastructure (ENG-13) ---
|
||||
// SC-1..SC-8 from ROADMAP. eval() returns the Display of the final value;
|
||||
// every assertion below targets a bool/number result to avoid string-quoting
|
||||
// ambiguity.
|
||||
// --- 类型转换基础设施测试 ---
|
||||
// eval() 返回最终值的 Display;以下断言都针对 bool/number 结果,
|
||||
// 避免字符串引用的歧义。
|
||||
|
||||
// SC-1 ToPrimitive: boxed Number unwraps via valueOf (was "NaN" before).
|
||||
// ToPrimitive:装箱 Number 经 valueOf 解包。
|
||||
#[test]
|
||||
fn to_primitive_number_boxed() {
|
||||
assert_eq!(eval("+new Number(42)"), "42");
|
||||
}
|
||||
|
||||
// SC-1 ToPrimitive: boxed String coerces to its primitive in == (object vs string).
|
||||
// ToPrimitive:装箱 String 在 == 中强转为原始值(对象 vs 字符串)。
|
||||
#[test]
|
||||
fn to_primitive_string_boxed() {
|
||||
assert_eq!(eval("new String('x') == 'x'"), "true");
|
||||
}
|
||||
|
||||
// SC-1 ToPrimitive: obj[Symbol.toPrimitive] is consulted (callback runs, returns 7).
|
||||
// ToPrimitive:优先调用 obj[Symbol.toPrimitive](回调执行并返回 7)。
|
||||
#[test]
|
||||
fn to_primitive_symbol_hint_invoked() {
|
||||
assert_eq!(eval("var o = {}; o[Symbol.toPrimitive] = function (h) { return 7; }; +o"), "7");
|
||||
}
|
||||
|
||||
// SC-2 ToNumber: to_number_full throws TypeError on a Symbol (per D-02).
|
||||
// Note: the unary-plus operator path uses coerce_number_bounded (no symbol
|
||||
// throw) so `+Symbol()` yields NaN at the operator level; see test below.
|
||||
// ToNumber:to_number_full 对 Symbol 抛 TypeError。
|
||||
// 注意:一元加号路径用 coerce_number_bounded(对 Symbol 不抛),因此运算符层的
|
||||
// +Symbol() 得到 NaN;见下个测试。
|
||||
#[test]
|
||||
fn to_number_full_symbol_throws() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -148,73 +147,73 @@ fn to_number_full_symbol_throws() {
|
||||
assert!(result.unwrap_err().contains("Symbol"), "expected a Symbol TypeError message");
|
||||
}
|
||||
|
||||
// SC-2/SC-7 operator-level reality: +Symbol is NaN (symbol-throw not wired into arithmetic).
|
||||
// 运算符层实况:+Symbol 为 NaN(Symbol 抛出未接入算术路径)。
|
||||
#[test]
|
||||
fn unary_plus_symbol_is_nan() {
|
||||
assert_eq!(eval("+Symbol('x')"), "NaN");
|
||||
}
|
||||
|
||||
// SC-3 Abstract Equality: string vs number coerces.
|
||||
// 抽象相等:字符串与数字互相强转。
|
||||
#[test]
|
||||
fn abstract_eq_string_number() {
|
||||
assert_eq!(eval("'1' == 1"), "true");
|
||||
}
|
||||
|
||||
// SC-3 Abstract Equality: bool->number recursion + string->number.
|
||||
// 抽象相等:布尔转数字的递归 + 字符串转数字。
|
||||
#[test]
|
||||
fn abstract_eq_bool_number() {
|
||||
assert_eq!(eval("'1' == true"), "true");
|
||||
}
|
||||
|
||||
// SC-3 Abstract Equality: object vs number via ToPrimitive ([42] -> "42" -> 42).
|
||||
// 抽象相等:对象 vs 数字经 ToPrimitive([42] -> "42" -> 42)。
|
||||
#[test]
|
||||
fn abstract_eq_object_primitive() {
|
||||
assert_eq!(eval("[42] == 42"), "true");
|
||||
}
|
||||
|
||||
// SC-3 Abstract Equality: null == undefined (regression guard, spec step 2-3).
|
||||
// 抽象相等:null == undefined(回归锚,规范第 2-3 步)。
|
||||
#[test]
|
||||
fn abstract_eq_null_undefined() {
|
||||
assert_eq!(eval("null == undefined"), "true");
|
||||
}
|
||||
|
||||
// SC-3 Abstract Equality: null == 0 is false (spec falls through to step 14).
|
||||
// 抽象相等:null == 0 为 false(规范落到第 14 步)。
|
||||
#[test]
|
||||
fn abstract_eq_null_zero_mismatch() {
|
||||
assert_eq!(eval("null == 0"), "false");
|
||||
}
|
||||
|
||||
// SC-3 Abstract Equality: same-type delegates to strict equality.
|
||||
// 抽象相等:同类型委托给严格相等。
|
||||
#[test]
|
||||
fn abstract_eq_same_type_string() {
|
||||
assert_eq!(eval("'abc' == 'abc'"), "true");
|
||||
}
|
||||
|
||||
// SC-4 Strict Equality: +0 === -0 is true (verify-only; already correct).
|
||||
// 严格相等:+0 === -0 为 true(仅验证)。
|
||||
#[test]
|
||||
fn strict_eq_signed_zero_eval() {
|
||||
assert_eq!(eval("+0 === -0"), "true");
|
||||
}
|
||||
|
||||
// SC-4 Strict Equality: NaN === NaN is false (verify-only; already correct).
|
||||
// 严格相等:NaN === NaN 为 false(仅验证)。
|
||||
#[test]
|
||||
fn strict_eq_nan_eval() {
|
||||
assert_eq!(eval("NaN === NaN"), "false");
|
||||
}
|
||||
|
||||
// SC-5 Arithmetic +: object operand coerces via valueOf.
|
||||
// 算术 +:对象操作数经 valueOf 强转。
|
||||
#[test]
|
||||
fn add_object_valueof_coercion() {
|
||||
assert_eq!(eval("var o = {valueOf: function () { return 42; }}; o + 1"), "43");
|
||||
}
|
||||
|
||||
// SC-6 Relational: object operands coerce via ToPrimitive then compare as strings.
|
||||
// 关系比较:对象操作数经 ToPrimitive 后按字符串比较。
|
||||
#[test]
|
||||
fn relational_object_coercion() {
|
||||
assert_eq!(eval("[3] > [2]"), "true");
|
||||
}
|
||||
|
||||
// SC-1/D-02 Date: Date object coerces to its timestamp in == (default hint -> valueOf).
|
||||
// Date 对象在 == 中强转为自身时间戳(默认 hint -> valueOf)。
|
||||
#[test]
|
||||
fn date_object_equals_timestamp() {
|
||||
assert_eq!(eval("new Date(0) == 0"), "true");
|
||||
|
||||
@@ -19,8 +19,7 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Destructured parameter followed by a loop body: the counter must count the parameter
|
||||
// destructuring prologue or the loop's back-jump lands at the wrong PC.
|
||||
// 解构参数后跟循环体:计数器必须计入参数解构 prologue,否则循环回跳落到错误 PC。
|
||||
#[test]
|
||||
fn param_array_pattern_with_loop_body() {
|
||||
assert_eq!(eval("function f([a]){var s=0; while(a>0){s=s+1;a=a-1;} return s;} f([3])"), "3");
|
||||
@@ -31,32 +30,31 @@ fn param_array_pattern_default_element() {
|
||||
assert_eq!(eval("function f([a=5]){return a;} f([])"), "5");
|
||||
}
|
||||
|
||||
// Array assignment target with a default element followed by a loop.
|
||||
// 含默认元素的数组赋值目标后跟循环。
|
||||
#[test]
|
||||
fn array_assignment_default_element_then_loop() {
|
||||
assert_eq!(eval("var a,c=0; [a=5]=[]; while(c<a){c=c+1;} c"), "5");
|
||||
}
|
||||
|
||||
// Nested array assignment target element.
|
||||
// 嵌套数组赋值目标元素。
|
||||
#[test]
|
||||
fn array_assignment_nested_element() {
|
||||
assert_eq!(eval("var a,b; [[a],b]=[[1],2]; a*10+b"), "12");
|
||||
}
|
||||
|
||||
// Destructuring catch param followed by a loop: the counter must not count a STORE_VAR the
|
||||
// emitter does not emit for a pattern catch binding.
|
||||
// 解构 catch 参数后跟循环:计数器不得计入模式 catch 绑定未发射的 STORE_VAR。
|
||||
#[test]
|
||||
fn destructuring_catch_param_then_loop() {
|
||||
assert_eq!(eval("var c=0; try{throw 1;}catch({e}){} while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
// Regression: for-init declaration without an initializer followed by loop iterations.
|
||||
// 回归:for-init 声明无初始化器后跟循环迭代。
|
||||
#[test]
|
||||
fn for_init_no_initializer_declarator() {
|
||||
assert_eq!(eval("var c=0; for(var i; c<3; c=c+1){} c"), "3");
|
||||
}
|
||||
|
||||
// Identifier element array assignment stays correct (simple path unchanged).
|
||||
// 标识符元素数组赋值保持正确(简单路径未变)。
|
||||
#[test]
|
||||
fn array_assignment_identifier_elements() {
|
||||
assert_eq!(eval("var a,b; [a,b]=[7,8]; a*10+b"), "78");
|
||||
|
||||
@@ -19,8 +19,8 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// A class body must not drift the instruction count of code that follows it. Each repro
|
||||
// places a bounded loop after the class so a wrong count corrupts the loop's jump target.
|
||||
// 类体不得漂移其后代码的指令计数。每个复现都在类后放置有界循环,
|
||||
// 计数错误会破坏循环跳转目标。
|
||||
|
||||
#[test]
|
||||
fn non_static_private_method_then_loop() {
|
||||
@@ -32,8 +32,7 @@ fn static_private_method_then_loop() {
|
||||
assert_eq!(eval("class C{static #m(){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
// Multi-word computed key — a single-identifier key happens to be one word and would not
|
||||
// expose the drift.
|
||||
// 多字计算键——单标识符键恰好是单字,暴露不出漂移。
|
||||
#[test]
|
||||
fn computed_method_key_then_loop() {
|
||||
assert_eq!(eval("class C{[\"x\"+\"y\"](){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
@@ -44,8 +43,8 @@ fn computed_static_field_key_then_loop() {
|
||||
assert_eq!(eval("class C{static [\"a\"+\"b\"]=1;} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
// Derived constructor with an instance field and a loop after super(): the field code is
|
||||
// injected after SUPER_CALL, so the loop's jump target must account for it.
|
||||
// 含实例字段的派生构造函数在 super() 后跟循环:字段代码注入在 SUPER_CALL 之后,
|
||||
// 循环跳转目标必须计入它。
|
||||
#[test]
|
||||
fn derived_ctor_field_then_post_super_loop() {
|
||||
assert_eq!(
|
||||
@@ -67,7 +66,7 @@ fn derived_ctor_statement_before_super() {
|
||||
);
|
||||
}
|
||||
|
||||
// Non-derived class with an instance field and a ctor loop stays correct (unchanged path).
|
||||
// 非派生类含实例字段与构造循环保持正确(路径未变)。
|
||||
#[test]
|
||||
fn non_derived_ctor_field_then_loop() {
|
||||
assert_eq!(
|
||||
@@ -76,7 +75,7 @@ fn non_derived_ctor_field_then_loop() {
|
||||
);
|
||||
}
|
||||
|
||||
// Behavioral regressions: methods/getters/private calls still work.
|
||||
// 行为回归:方法/getter/私有调用仍正常。
|
||||
#[test]
|
||||
fn plain_method_call_regression() {
|
||||
assert_eq!(eval("class C{m(){return 9;}} new C().m()"), "9");
|
||||
|
||||
@@ -19,8 +19,8 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Non-simple `??` (side-effecting operands) followed by a label-bearing construct must not
|
||||
// shift the downstream label id (which produced `Label ... not found` at emit time).
|
||||
// 非简单 `??`(带副作用操作数)后跟带标签的构造不得把下游 label id 移位
|
||||
// (否则 emit 期报 `Label ... not found`)。
|
||||
|
||||
#[test]
|
||||
fn coalesce_nonsimple_then_if() {
|
||||
@@ -38,10 +38,9 @@ fn coalesce_nonsimple_then_while() {
|
||||
);
|
||||
}
|
||||
|
||||
// A jump-bearing construct (if/loop) followed by a hoisted function declaration:
|
||||
// the count pass must walk in the same function-hoisting order as the emit pass,
|
||||
// otherwise the construct's jump targets drift past the hoisted body and the
|
||||
// program loops forever at run time.
|
||||
// 含跳转的构造(if/loop)后跟被提升的函数声明:
|
||||
// 计数 pass 必须与 emit pass 采用相同的函数提升顺序,
|
||||
// 否则构造的跳转目标越过提升的函数体,程序运行时无限循环。
|
||||
|
||||
#[test]
|
||||
fn if_then_hoisted_function() {
|
||||
@@ -58,9 +57,9 @@ fn while_then_hoisted_function() {
|
||||
assert_eq!(eval("var n=0; while(n<3){n=n+1;} function f(){return 100} n + f()"), "103");
|
||||
}
|
||||
|
||||
// A hoisted function declaration is emitted before the `var` statements it closes over,
|
||||
// so the count pass must register top-level `var` names too (not just function names);
|
||||
// otherwise the hoisted body cannot resolve the outer var ("Identifier not defined").
|
||||
// 提升的函数声明先于它闭包的 `var` 语句发射,
|
||||
// 因此计数 pass 必须同时登记顶层 `var` 名(不只是函数名);
|
||||
// 否则提升的函数体无法解析外层 var("Identifier not defined")。
|
||||
|
||||
#[test]
|
||||
fn hoisted_function_reads_outer_var() {
|
||||
@@ -82,15 +81,14 @@ fn coalesce_nonsimple_then_for() {
|
||||
|
||||
#[test]
|
||||
fn coalesce_nonsimple_short_circuits_on_value() {
|
||||
// 0 is not nullish, so `a() ?? b()` is 0 and the loop never runs.
|
||||
// 0 不是 nullish,因此 `a() ?? b()` 为 0,循环不会运行。
|
||||
assert_eq!(
|
||||
eval("function a(){return 0;} function b(){return 9;} var v=a()??b(); var c=0; while(c<v){c=c+1;} c"),
|
||||
"0"
|
||||
);
|
||||
}
|
||||
|
||||
// Static member compound assignment must not over-count vs the emitter (the extra +1 drifted
|
||||
// the following loop's jump target).
|
||||
// 静态成员复合赋值不得相对 emitter 多计(多出的 +1 会漂移其后循环的跳转目标)。
|
||||
|
||||
#[test]
|
||||
fn static_member_compound_assign_then_loop() {
|
||||
@@ -102,7 +100,7 @@ fn static_member_compound_assign_in_loop() {
|
||||
assert_eq!(eval("var o={x:0}; var c=0; while(o.x<3){o.x+=1; c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
// Regressions: simple coalesce, nested coalesce, And/Or, compound member value, plain assign.
|
||||
// 回归:简单 coalesce、嵌套 coalesce、And/Or、复合成员值、普通赋值。
|
||||
#[test]
|
||||
fn simple_coalesce_regression() {
|
||||
assert_eq!(eval("var x = null ?? 5; x"), "5");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! B011 const guard 回归测试:const 声明路径不依赖寄存器槽新鲜度。
|
||||
//! const guard 回归测试:const 声明路径不依赖寄存器槽新鲜度。
|
||||
//!
|
||||
//! 背景:`dispatch_store_var` 的 const guard(b!=0)读 `regs[rd]` 判断目标槽是否已初始化。
|
||||
//! 声明路径带 b=1 时,checkpoint/寄存器复用残留陈旧值会误抛 "Assignment to constant variable"。
|
||||
//! Phase 5 RegAlloc 复用寄存器后此 bug 必现——本测试锁死声明路径恒 b=0 的行为。
|
||||
//! 声明路径带 b=1 时,checkpoint/寄存器复用残留的陈旧值会误抛 "Assignment to constant variable"。
|
||||
//! 寄存器分配复用寄存器后此问题必现——本测试锁死声明路径恒 b=0 的行为。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
@@ -25,7 +25,7 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// B011 复现样例:switch 语句(checkpoint 复用槽)后跟 const 声明。
|
||||
// 复现样例:switch 语句(checkpoint 复用槽)后跟 const 声明。
|
||||
// 修复前误抛 "Assignment to constant variable",修复后输出 5。
|
||||
#[test]
|
||||
fn switch_then_const_declaration_no_false_positive() {
|
||||
|
||||
@@ -22,35 +22,35 @@ fn assert_err_contains(result: Result<JsValue, String>, expected: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Partial writable update preserves existing value --
|
||||
// ── 部分 writable 更新保留既有值 ──
|
||||
#[test]
|
||||
fn define_property_partial_writable_preserves_value() {
|
||||
let r = eval_str("var obj={}; obj.x=42; Object.defineProperty(obj,'x',{writable:false}); obj.x").unwrap();
|
||||
assert!(r.is_int() && r.as_int() == 42, "value should be preserved at 42, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Mixed descriptor (value+get) throws TypeError --
|
||||
// ── 混合描述符(value+get)抛 TypeError ──
|
||||
#[test]
|
||||
fn define_property_mixed_value_get_throws() {
|
||||
let result = eval_str("var obj={}; Object.defineProperty(obj,'x',{value:1,get:function(){}});");
|
||||
assert_err_contains(result, "mix");
|
||||
}
|
||||
|
||||
// -- Mixed descriptor (writable+set) throws TypeError --
|
||||
// ── 混合描述符(writable+set)抛 TypeError ──
|
||||
#[test]
|
||||
fn define_property_mixed_writable_set_throws() {
|
||||
let result = eval_str("var obj={}; Object.defineProperty(obj,'x',{writable:true,set:function(){}});");
|
||||
assert_err_contains(result, "mix");
|
||||
}
|
||||
|
||||
// -- Partial enumerable-only update preserves value --
|
||||
// ── 仅 enumerable 的部分更新保留既有值 ──
|
||||
#[test]
|
||||
fn define_property_partial_enumerable_only_preserves_value() {
|
||||
let r = eval_str("var obj={}; obj.x=42; Object.defineProperty(obj,'x',{enumerable:true}); obj.x").unwrap();
|
||||
assert!(r.is_int() && r.as_int() == 42, "value should be preserved at 42, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- getOwnPropertyDescriptor returns data fields (no get/set) --
|
||||
// ── getOwnPropertyDescriptor 返回数据字段(无 get/set)──
|
||||
#[test]
|
||||
fn get_own_property_descriptor_data_fields() {
|
||||
let r = eval_str(
|
||||
@@ -70,7 +70,7 @@ fn get_own_property_descriptor_data_fields() {
|
||||
assert!(obj.get_prop_at(5).is_undefined(), "set should be undefined");
|
||||
}
|
||||
|
||||
// -- getOwnPropertyDescriptor returns accessor fields (no value/writable) --
|
||||
// ── getOwnPropertyDescriptor 返回访问器字段(无 value/writable)──
|
||||
#[test]
|
||||
fn get_own_property_descriptor_accessor_fields() {
|
||||
let r = eval_str(
|
||||
@@ -92,7 +92,7 @@ fn get_own_property_descriptor_accessor_fields() {
|
||||
);
|
||||
}
|
||||
|
||||
// -- New property without value or accessor defaults to data descriptor (ES spec behavior) --
|
||||
// ── 无 value/accessor 的新属性默认为数据描述符(ES 规范行为)──
|
||||
#[test]
|
||||
fn define_property_new_attributes_only_defaults_value_undefined() {
|
||||
let r = eval_str("var obj={}; Object.defineProperty(obj,'x',{enumerable:true}); obj.x").unwrap();
|
||||
@@ -103,7 +103,7 @@ fn define_property_new_attributes_only_defaults_value_undefined() {
|
||||
);
|
||||
}
|
||||
|
||||
// -- Partial configurable on existing property preserves value --
|
||||
// ── 既有属性的部分 configurable 更新保留既有值 ──
|
||||
#[test]
|
||||
fn define_property_partial_configurable_preserves_value() {
|
||||
let r = eval_str("var obj={}; obj.x=1; Object.defineProperty(obj,'x',{configurable:false}); [obj.x]").unwrap();
|
||||
|
||||
@@ -88,10 +88,9 @@ fn function_and_method_parameter_destructuring() {
|
||||
assert_num(result, 10.0);
|
||||
}
|
||||
|
||||
// Regression: a for-of whose assignment target has a NESTED rest pattern (the rest
|
||||
// element is itself a pattern, e.g. `[...[x]]`) used to loop forever / error, because the
|
||||
// counter undercounted the rest target's instructions vs the emitter, corrupting the loop's
|
||||
// jump offsets. (Object rest with a nested target is a syntax error per spec, so untested.)
|
||||
// 回归:赋值目标含嵌套 rest 模式(rest 元素本身是模式,如 `[...[x]]`)的 for-of
|
||||
// 曾无限循环 / 报错,因为计数器相对 emitter 少计了 rest 目标的指令,破坏了循环跳转
|
||||
// 偏移。(对象 rest 嵌套目标按规范是语法错误,故不测。)
|
||||
#[test]
|
||||
fn for_of_assignment_nested_rest_target_runs_once() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -109,7 +108,7 @@ fn for_of_assignment_nested_rest_empty_body_binds_inner() {
|
||||
#[test]
|
||||
fn for_of_assignment_nested_rest_with_leading_element() {
|
||||
let mut vm = Vm::new();
|
||||
// outer iterates once; a=1, rest=[2,3,4], then [x,y] from rest -> x=2, y=3
|
||||
// 外层迭代一次;a=1,rest=[2,3,4],再从 rest 解构 [x,y] -> x=2, y=3
|
||||
let result = eval(&mut vm, "var a,x,y; for([a,...[x,y]] of [[1,2,3,4]]){} a*100+x*10+y").unwrap();
|
||||
assert_num(result, 123.0);
|
||||
}
|
||||
|
||||
@@ -44,15 +44,14 @@ fn for_in_null_does_not_execute_body() {
|
||||
|
||||
#[test]
|
||||
fn for_in_null_does_not_throw() {
|
||||
// Must NOT produce a "vm error: ..." — a quiet empty loop, then a sentinel value.
|
||||
// 不得产生 "vm error: ..."——静默空循环,然后哨兵值。
|
||||
assert_eq!(eval("for(var k in null){} 7"), "7", "for-in null must not throw");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_number_primitive_currently_throws() {
|
||||
// Interim behavior: ToObject coercion for non-null/undefined primitives is not
|
||||
// implemented yet, so a number right-hand side still throws TypeError. When
|
||||
// ToObject lands this becomes an empty loop and this assertion should change.
|
||||
// 过渡行为:非 null/undefined 基本类型的 ToObject 强转尚未实现,
|
||||
// 因此数字右侧仍抛 TypeError。ToObject 落地后它应变成空循环,届时本断言需变更。
|
||||
let out = eval("for(var k in 42){}");
|
||||
assert!(out.contains("TypeError"), "expected TypeError for number primitive, got: {out}");
|
||||
}
|
||||
|
||||
@@ -19,10 +19,9 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Order is asserted via a position map built inside JS, so the comparison
|
||||
// returns a boolean (JsValue Display only reveals number/bool, not string
|
||||
// contents). This is also tolerant of any inherited enumerable keys the
|
||||
// engine appends after the object's own keys.
|
||||
// 顺序经 JS 内部构建的位置映射断言,比较返回布尔(JsValue 的 Display 只暴露
|
||||
// number/bool,不暴露字符串内容)。该写法也容忍引擎在对象自有键之后追加的
|
||||
// 任意继承可枚举键。
|
||||
|
||||
#[test]
|
||||
fn for_in_ordering_integer_indices_first() {
|
||||
|
||||
@@ -19,10 +19,10 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec (ECMA-262 §13.7.5): next()-throw propagates the ORIGINAL value WITHOUT calling
|
||||
// return(); IteratorClose (return()) runs only on BODY abrupt completion (throw/break/return).
|
||||
// return() is observed via globalThis (a function CAN read/write globalThis properties;
|
||||
// it cannot write a plain outer `var` in this engine).
|
||||
// 规范(ECMA-262 §13.7.5):next()-throw 传播原始值且不调用 return();
|
||||
// IteratorClose(return())只在循环体突然完成(throw/break/return)时执行。
|
||||
// return() 经 globalThis 观测(函数可读写 globalThis 属性;
|
||||
// 本引擎中它不能写入外层普通 `var`)。
|
||||
|
||||
#[test]
|
||||
fn for_of_next_throw_is_catchable_with_original_type() {
|
||||
|
||||
@@ -19,10 +19,10 @@ fn eval(source: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Assertions stay inside JS and resolve to a boolean/number, because JsValue Display
|
||||
// reveals numbers/bools but not string/object contents.
|
||||
// 断言放在 JS 内部并解析为布尔/数字,因为 JsValue 的 Display 只暴露
|
||||
// 数字/布尔,不暴露字符串/对象内容。
|
||||
|
||||
// ---- for-of loop over Map/Set (default iterators) ----
|
||||
// ── 对 Map/Set(默认迭代器)的 for-of ──
|
||||
|
||||
#[test]
|
||||
fn for_of_map_yields_key_value_entries() {
|
||||
@@ -52,7 +52,7 @@ fn for_of_empty_set_runs_zero_times() {
|
||||
assert_eq!(eval("var s=new Set();var r=0;for(var v of s){r=r+1;}r===0"), "true");
|
||||
}
|
||||
|
||||
// ---- Map.prototype.{entries,values,keys} ----
|
||||
// ── Map.prototype.{entries,values,keys} ──
|
||||
|
||||
#[test]
|
||||
fn map_values_iterator_yields_values() {
|
||||
@@ -80,7 +80,7 @@ fn map_iterator_reports_done_at_end() {
|
||||
assert_eq!(eval("var m=new Map();var it=m.values();it.next().done===true"), "true");
|
||||
}
|
||||
|
||||
// ---- Set.prototype.{entries,values,keys} ----
|
||||
// ── Set.prototype.{entries,values,keys} ──
|
||||
|
||||
#[test]
|
||||
fn set_values_iterator_yields_values() {
|
||||
|
||||
@@ -127,7 +127,7 @@ fn fd_calls_builtin_two_args() {
|
||||
assert!((result.as_double() - 9.0).abs() < 0.0001);
|
||||
}
|
||||
|
||||
// --- Multiple FDs, first FD calls second ---
|
||||
// ── 多个函数声明,首个调用第二个 ──
|
||||
|
||||
#[test]
|
||||
fn fd_chain_call() {
|
||||
@@ -136,7 +136,7 @@ fn fd_chain_call() {
|
||||
assert_eq!(result.as_int(), 10);
|
||||
}
|
||||
|
||||
// --- new Xxx() in function ---
|
||||
// ── 函数内的 new Xxx() ──
|
||||
|
||||
#[test]
|
||||
fn fd_returns_new_object() {
|
||||
@@ -152,7 +152,7 @@ fn fd_returns_new_array() {
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
// --- this expression ---
|
||||
// ── this 表达式 ──
|
||||
|
||||
#[test]
|
||||
fn this_in_function_reads_value() {
|
||||
|
||||
@@ -22,7 +22,7 @@ fn assert_err_contains(result: Result<JsValue, String>, expected: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Delete configurable property succeeds --
|
||||
// ── 删除可配置属性成功 ──
|
||||
#[test]
|
||||
fn delete_configurable_property_succeeds_and_returns_true() {
|
||||
let r = eval_many(&[
|
||||
@@ -34,7 +34,7 @@ fn delete_configurable_property_succeeds_and_returns_true() {
|
||||
assert!(r.is_bool() && r.as_bool(), "delete configurable should return true, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Delete non-configurable property throws --
|
||||
// ── 删除不可配置属性抛错 ──
|
||||
#[test]
|
||||
fn delete_non_configurable_throws_type_error() {
|
||||
let result = eval_many(&[
|
||||
@@ -45,21 +45,21 @@ fn delete_non_configurable_throws_type_error() {
|
||||
assert_err_contains(result, "cannot delete non-configurable property");
|
||||
}
|
||||
|
||||
// -- Delete non-existent property returns true --
|
||||
// ── 删除不存在的属性返回 true ──
|
||||
#[test]
|
||||
fn delete_non_existent_returns_true() {
|
||||
let r = eval_many(&["var obj = {}", "delete obj.x"]).unwrap();
|
||||
assert!(r.is_bool() && r.as_bool(), "delete non-existent should return true, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Delete default (no explicit defineProperty) configurable succeeds --
|
||||
// ── 删除默认(无显式 defineProperty)可配置属性成功 ──
|
||||
#[test]
|
||||
fn delete_default_configurable_succeeds() {
|
||||
let r = eval_many(&["var obj = {}", "obj.x = 1", "delete obj.x"]).unwrap();
|
||||
assert!(r.is_bool() && r.as_bool(), "delete default configurable should return true, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Delete non-configurable proto property on child: child doesn't own it, so returns true --
|
||||
// ── 子对象删除原型上不可配置的属性:子对象不拥有它,因此返回 true ──
|
||||
#[test]
|
||||
fn delete_non_configurable_proto_property_on_child_returns_true() {
|
||||
let r = eval_many(&[
|
||||
|
||||
@@ -22,7 +22,7 @@ fn keys_to_strings(result: JsValue) -> Vec<String> {
|
||||
for i in 0..len {
|
||||
let v = obj.get_prop_at(i);
|
||||
if v.is_string() {
|
||||
// SAFETY: string value pointing into VM arena (caller keeps vm alive)
|
||||
// SAFETY: 字符串值指向 VM arena(调用方保持 vm 存活)。
|
||||
let s = unsafe { &*v.as_string_ptr() };
|
||||
keys.push(s.data.clone());
|
||||
}
|
||||
@@ -30,7 +30,7 @@ fn keys_to_strings(result: JsValue) -> Vec<String> {
|
||||
keys
|
||||
}
|
||||
|
||||
// -- Integer keys in ascending order --
|
||||
// ── 整型键按升序 ──
|
||||
#[test]
|
||||
fn keys_integer_order_ascending() {
|
||||
let r = eval_str("var obj={}; obj[3]='c'; obj[1]='a'; obj[2]='b'; Object.keys(obj).length").unwrap();
|
||||
@@ -40,7 +40,7 @@ fn keys_integer_order_ascending() {
|
||||
assert_eq!(keys, vec!["1", "2", "3"], "integer keys should be ascending, got {:?}", keys);
|
||||
}
|
||||
|
||||
// -- Mixed keys: integers first, then strings in creation order --
|
||||
// ── 混合键:整型优先,字符串按创建序 ──
|
||||
#[test]
|
||||
fn keys_mixed_order_integers_first() {
|
||||
let (_vm, r) = eval("var obj={}; obj[2]='b'; obj['1']='a'; obj['a']=1; Object.keys(obj)").unwrap();
|
||||
@@ -58,7 +58,7 @@ fn keys_mixed_order_integers_first() {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Object.keys excludes non-enumerable properties --
|
||||
// ── Object.keys 排除不可枚举属性 ──
|
||||
#[test]
|
||||
fn keys_excludes_non_enumerable() {
|
||||
let r = eval_str(
|
||||
@@ -68,7 +68,7 @@ fn keys_excludes_non_enumerable() {
|
||||
assert!(r.is_int() && r.as_int() == 1, "keys should exclude non-enumerable, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Object.getOwnPropertyNames includes non-enumerable properties --
|
||||
// ── Object.getOwnPropertyNames 包含不可枚举属性 ──
|
||||
#[test]
|
||||
fn get_own_property_names_includes_non_enumerable() {
|
||||
let r = eval_str("var obj={a:1}; Object.defineProperty(obj,'hidden',{value:2,enumerable:false}); Object.getOwnPropertyNames(obj).length").unwrap();
|
||||
@@ -79,7 +79,7 @@ fn get_own_property_names_includes_non_enumerable() {
|
||||
);
|
||||
}
|
||||
|
||||
// -- Object.entries excludes non-enumerable properties --
|
||||
// ── Object.entries 排除不可枚举属性 ──
|
||||
#[test]
|
||||
fn entries_excludes_non_enumerable() {
|
||||
let r = eval_str(
|
||||
@@ -89,7 +89,7 @@ fn entries_excludes_non_enumerable() {
|
||||
assert!(r.is_int() && r.as_int() == 1, "entries should exclude non-enumerable, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Object.values excludes non-enumerable properties --
|
||||
// ── Object.values 排除不可枚举属性 ──
|
||||
#[test]
|
||||
fn values_excludes_non_enumerable() {
|
||||
let r = eval_str(
|
||||
@@ -99,7 +99,7 @@ fn values_excludes_non_enumerable() {
|
||||
assert!(r.is_int() && r.as_int() == 1, "values should exclude non-enumerable, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Leading zero strings are NOT integer indices --
|
||||
// ── 前导零字符串不是整型下标 ──
|
||||
#[test]
|
||||
fn keys_with_leading_zero_not_integer_index() {
|
||||
let (_vm, r) = eval("Object.keys({'01':'a','1':'b'})").unwrap();
|
||||
|
||||
@@ -15,7 +15,7 @@ fn eval_str(source: &str) -> Result<JsValue, String> {
|
||||
eval(source).map(|(_vm, v)| v)
|
||||
}
|
||||
|
||||
// Getter on proto sets marker on receiver, then check receiver has marker (not proto)
|
||||
// 原型上的 getter 在 receiver 上设置标记,随后检查 receiver 持有标记(而非原型)。
|
||||
#[test]
|
||||
fn getter_on_proto_this_is_receiver() {
|
||||
let r = eval_str(
|
||||
@@ -24,7 +24,7 @@ fn getter_on_proto_this_is_receiver() {
|
||||
assert!(r.is_int() && r.as_int() == 1, "getter should set marker on child (receiver), got {:?}", r);
|
||||
}
|
||||
|
||||
// Getter on deep proto chain: grandchild → child → proto. Getter defined on proto.
|
||||
// 深层原型链上的 getter:grandchild → child → proto,getter 定义在 proto 上。
|
||||
#[test]
|
||||
fn getter_on_proto_deep_chain_this_is_receiver() {
|
||||
let r = eval_str(
|
||||
@@ -33,7 +33,7 @@ fn getter_on_proto_deep_chain_this_is_receiver() {
|
||||
assert!(r.is_bool() && r.as_bool(), "grandchild should have _deep marker, got {:?}", r);
|
||||
}
|
||||
|
||||
// Setter on proto receives receiver as this, sets marker on receiver not proto
|
||||
// 原型上的 setter 以 receiver 作为 this,在 receiver 而非原型上设置标记。
|
||||
#[test]
|
||||
fn setter_on_proto_this_is_receiver() {
|
||||
let (_vm, r) = eval(
|
||||
@@ -42,7 +42,7 @@ fn setter_on_proto_this_is_receiver() {
|
||||
assert!(r.is_string(), "child should have _set_by_setter, got {:?}", r);
|
||||
}
|
||||
|
||||
// Dynamic getter (obj[expr]) preserves receiver
|
||||
// 动态 getter(obj[expr])保持 receiver。
|
||||
#[test]
|
||||
fn dynamic_getter_on_proto_this_is_receiver() {
|
||||
let r = eval_str(
|
||||
@@ -51,7 +51,7 @@ fn dynamic_getter_on_proto_this_is_receiver() {
|
||||
assert!(r.is_int() && r.as_int() == 1, "dynamic getter should set marker on child, got {:?}", r);
|
||||
}
|
||||
|
||||
// Dynamic setter (obj[expr]=val) preserves receiver
|
||||
// 动态 setter(obj[expr]=val)保持 receiver。
|
||||
#[test]
|
||||
fn dynamic_setter_on_proto_this_is_receiver() {
|
||||
let (_vm, r) = eval(
|
||||
@@ -60,7 +60,7 @@ fn dynamic_setter_on_proto_this_is_receiver() {
|
||||
assert!(r.is_string(), "child should have _dyn_set after dynamic setter, got {:?}", r);
|
||||
}
|
||||
|
||||
// Own getter on object — this is the object itself
|
||||
// 对象自身的 getter——this 即对象本身。
|
||||
#[test]
|
||||
fn own_getter_this_is_self() {
|
||||
let r = eval_str(
|
||||
@@ -69,7 +69,7 @@ fn own_getter_this_is_self() {
|
||||
assert!(r.is_int() && r.as_int() == 42, "own getter should set marker on self, got {:?}", r);
|
||||
}
|
||||
|
||||
// Own setter on object — this is the object itself
|
||||
// 对象自身的 setter——this 即对象本身。
|
||||
#[test]
|
||||
fn own_setter_this_is_self() {
|
||||
let (_vm, r) = eval(
|
||||
@@ -78,7 +78,7 @@ fn own_setter_this_is_self() {
|
||||
assert!(r.is_string(), "own setter should set marker on self, got {:?}", r);
|
||||
}
|
||||
|
||||
// Getter returning undefined does not crash
|
||||
// 返回 undefined 的 getter 不崩溃。
|
||||
#[test]
|
||||
fn getter_returns_undefined_does_not_crash() {
|
||||
let r = eval_str(
|
||||
@@ -87,7 +87,7 @@ fn getter_returns_undefined_does_not_crash() {
|
||||
assert!(r.is_undefined(), "getter returning nothing should yield undefined, got {:?}", r);
|
||||
}
|
||||
|
||||
// String .length still works after proto getter is defined (primitive auto-boxing not broken)
|
||||
// 定义原型 getter 后字符串 .length 仍正常(基本类型自动装箱未被破坏)。
|
||||
#[test]
|
||||
fn string_length_still_works_after_proto_getter() {
|
||||
let r = eval_str("'hello'.length").unwrap();
|
||||
|
||||
@@ -22,7 +22,7 @@ fn assert_err_contains(result: Result<JsValue, String>, expected: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
// -- Assign to non-writable own property throws TypeError --
|
||||
// ── 向不可写自身属性赋值抛 TypeError ──
|
||||
#[test]
|
||||
fn assign_to_non_writable_throws() {
|
||||
let result = eval_many(&[
|
||||
@@ -33,7 +33,7 @@ fn assign_to_non_writable_throws() {
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// -- Assign to writable own property succeeds --
|
||||
// ── 向可写自身属性赋值成功 ──
|
||||
#[test]
|
||||
fn assign_to_writable_succeeds() {
|
||||
let r = eval_many(&[
|
||||
@@ -46,7 +46,7 @@ fn assign_to_writable_succeeds() {
|
||||
assert!(r.is_int() && r.as_int() == 2, "assign to writable should set value to 2, got {:?}", r);
|
||||
}
|
||||
|
||||
// -- Assign to inherited non-writable property on proto throws --
|
||||
// ── 向原型上不可写的继承属性赋值抛错 ──
|
||||
#[test]
|
||||
fn assign_to_inherited_non_writable_throws() {
|
||||
let result = eval_many(&[
|
||||
@@ -58,9 +58,9 @@ fn assign_to_inherited_non_writable_throws() {
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// -- Assign creates own property on child when proto's is non-writable but child has no own prop
|
||||
// ── 原型属性不可写但子对象无自有属性时,赋值会在子对象上创建自有属性
|
||||
// ponytail: spec allows shadowing when proto prop is configurable; engine doesn't yet.
|
||||
// The engine correctly throws for non-writable proto props. Full shadowing is a follow-up.
|
||||
// 引擎对不可写原型属性正确抛错;完整遮蔽作为后续工作。
|
||||
#[test]
|
||||
fn assign_non_writable_proto_configurable_prop_throws() {
|
||||
let result = eval_many(&[
|
||||
|
||||
@@ -24,7 +24,7 @@ macro_rules! assert_undefined {
|
||||
};
|
||||
}
|
||||
|
||||
// -- Proto chains: every (non-Object) constructor.prototype should inherit from Object.prototype --
|
||||
// ── 原型链:每个(非 Object)构造器的 prototype 都应继承自 Object.prototype ──
|
||||
#[test]
|
||||
fn array_proto_chain() {
|
||||
assert_bool!(eval("Object.getPrototypeOf(Array.prototype) === Object.prototype"), true, "Array proto");
|
||||
@@ -32,10 +32,10 @@ fn array_proto_chain() {
|
||||
|
||||
#[test]
|
||||
fn function_proto_chain() {
|
||||
// NOTE: Function global is not registered (pre-existing issue).
|
||||
// Function.prototype.__proto__ IS wired to Object.prototype via kernel patch.
|
||||
// Verified by checking that Function.prototype (accessed via other means) has correct proto.
|
||||
// Skip this test until Function global is registered.
|
||||
// 未支持:Function 全局尚未注册(既有能力限制)。
|
||||
// Function.prototype.__proto__ 已经由 kernel 补丁接到 Object.prototype。
|
||||
// 通过其它途径访问 Function.prototype 可验证其原型正确。
|
||||
// 待 Function 全局注册后再启用本测试。
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -87,14 +87,14 @@ fn regexp_proto_chain() {
|
||||
assert_bool!(eval("Object.getPrototypeOf(RegExp.prototype) === Object.prototype"), true, "RegExp proto");
|
||||
}
|
||||
|
||||
// -- Object.prototype is the root, its __proto__ is null --
|
||||
// ── Object.prototype 是根,其 __proto__ 为 null ──
|
||||
#[test]
|
||||
fn object_proto_is_root() {
|
||||
let r = eval("Object.getPrototypeOf(Object.prototype)").unwrap();
|
||||
assert!(r.is_null(), "Object.prototype.__proto__ should be null");
|
||||
}
|
||||
|
||||
// -- instanceof now works for all builtins against Object --
|
||||
// ── 所有 builtin 对 Object 的 instanceof 现在都成立 ──
|
||||
#[test]
|
||||
fn array_instanceof_object() {
|
||||
assert_bool!(eval("[] instanceof Object"), true, "[] instanceof Object");
|
||||
@@ -108,16 +108,16 @@ fn function_instanceof_object() {
|
||||
|
||||
#[test]
|
||||
fn string_instanceof_object() {
|
||||
// NOTE: new String requires NEW_EXPRESSION support for user constructors.
|
||||
// Pre-existing limitation. Verify String.prototype is accessible.
|
||||
// 未支持:new String 依赖用户构造器的 NEW_EXPRESSION 支持(既有能力限制)。
|
||||
// 验证 String.prototype 可访问。
|
||||
let r = eval("typeof String.prototype").unwrap();
|
||||
assert!(r.is_string(), "String.prototype should be object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_not_instanceof_function() {
|
||||
// NOTE: Function global is not registered (pre-existing issue).
|
||||
// Skip until Function global registration is fixed.
|
||||
// 未支持:Function 全局尚未注册(既有能力限制)。
|
||||
// 待 Function 全局注册修复后再启用。
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -179,7 +179,7 @@ fn derived_constructor_inherits_parent_constructor_chain() {
|
||||
);
|
||||
}
|
||||
|
||||
// -- instanceof for native types should NOT respond to their own false positives --
|
||||
// ── native 类型的 instanceof 不应响应自身的误判 ──
|
||||
#[test]
|
||||
fn number_not_instanceof_array() {
|
||||
assert_bool!(eval("new Number(1) instanceof Array"), false, "Number instanceof Array");
|
||||
@@ -190,7 +190,7 @@ fn object_not_instanceof_regexp() {
|
||||
assert_bool!(eval("({}) instanceof RegExp"), false, "({}) instanceof RegExp");
|
||||
}
|
||||
|
||||
// -- Primitives should NOT be instanceof their wrapper constructors (no autoboxing) --
|
||||
// ── 基本类型不应 instanceof 其包装构造器(不自动装箱)──
|
||||
#[test]
|
||||
fn primitives_not_instanceof() {
|
||||
assert_bool!(eval("1 instanceof Number"), false, "1 instanceof Number");
|
||||
@@ -198,7 +198,7 @@ fn primitives_not_instanceof() {
|
||||
assert_bool!(eval("true instanceof Boolean"), false, "true instanceof Boolean");
|
||||
}
|
||||
|
||||
// -- VOID regression --
|
||||
// ── VOID 回归 ──
|
||||
#[test]
|
||||
fn void_returns_undefined() {
|
||||
assert_undefined!(eval("void 0"), "void 0");
|
||||
|
||||
@@ -95,11 +95,11 @@ fn closure_nested_expr_capture() {
|
||||
#[test]
|
||||
fn for_let_per_iteration_independent() {
|
||||
let _ = eval("var fns=[]; for(let i=0;i<3;i++){ fns.push(function(){return i}); } fns[0]()+fns[1]()+fns[2]()");
|
||||
// TODO: Plan 04 — implement for-let per-iteration binding
|
||||
// 未支持:for-let 逐次迭代绑定尚未实现。
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_access_before_init_throws() {
|
||||
let _ = eval("x; let x=1");
|
||||
// TODO: Plan 04 — implement precise TDZ
|
||||
// 未支持:精确 TDZ 尚未实现。
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! VM spill 栈执行语义测试(D-06/D-07/D-08):MOV 复制、SPILL/UNSPILL 往返、
|
||||
//! VM spill 栈执行语义测试:MOV 复制、SPILL/UNSPILL 往返、
|
||||
//! 帧边界(父 spill 跨子调用完好)、嵌套双 spill 交互、UNSPILL 越界防御。
|
||||
//!
|
||||
//! 手工 IR 是唯一可达路径——RegAlloc 未接入,真实 JS 编译产物不会含三指令。
|
||||
@@ -28,7 +28,7 @@ fn int_module(insts: Vec<Inst>, value: i32, n_registers: u8, nested: Vec<IRFunct
|
||||
f
|
||||
}
|
||||
|
||||
/// 测试 1:MOV 寄存器复制语义(D-07)。r3=42 → MOV r5=r3 → HALT 输出 42。
|
||||
/// 测试 1:MOV 寄存器复制语义。r3=42 → MOV r5=r3 → HALT 输出 42。
|
||||
#[test]
|
||||
fn mov_copies_register() {
|
||||
let insts = vec![
|
||||
@@ -41,7 +41,7 @@ fn mov_copies_register() {
|
||||
assert_eq!(out, "42");
|
||||
}
|
||||
|
||||
/// 测试 2:SPILL→UNSPILL 往返(D-06)。SPILL r3 slot0 → MOV 弄脏 r5 → UNSPILL r5 slot0 → 读回 42。
|
||||
/// 测试 2:SPILL→UNSPILL 往返。SPILL r3 slot0 → MOV 弄脏 r5 → UNSPILL r5 slot0 → 读回 42。
|
||||
#[test]
|
||||
fn spill_unspill_roundtrip() {
|
||||
let insts = vec![
|
||||
@@ -56,7 +56,7 @@ fn spill_unspill_roundtrip() {
|
||||
assert_eq!(out, "42");
|
||||
}
|
||||
|
||||
/// 测试 3:帧边界——父函数 SPILL 后 CALL 子函数,子函数返回后父 UNSPILL 仍得 42(D-08)。
|
||||
/// 测试 3:帧边界——父函数 SPILL 后 CALL 子函数,子函数返回后父 UNSPILL 仍得 42。
|
||||
#[test]
|
||||
fn frame_boundary_parent_spill_survives_call() {
|
||||
let child = int_module(
|
||||
|
||||
@@ -87,22 +87,21 @@ fn template_numeric_expression() {
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "2");
|
||||
}
|
||||
|
||||
// -- Template tagging tests (basic) --
|
||||
// ── 模板标签调用测试(基础)──
|
||||
|
||||
#[test]
|
||||
fn tagged_template_basic() {
|
||||
// Use Math.max as a simple native tag function to verify the CALL dispatch works.
|
||||
// Math.max(cooked_array, raw_array, 42) should return 42 (the max of the args).
|
||||
// This avoids bytecode function call complexity.
|
||||
// 用 Math.max 作为简单 native 标签函数,验证 CALL 分发可用。
|
||||
// Math.max(cooked_array, raw_array, 42) 应返回 42(参数最大值)。
|
||||
// 由此避开字节码函数调用复杂度。
|
||||
let result = eval("Math.max`hello ${42} world`");
|
||||
// Math.max on the args should work - just verify it doesn't crash
|
||||
// Math.max 作用于参数——仅验证不崩溃。
|
||||
assert!(!result.starts_with("vm error:"), "Tagged template should not crash, got: {}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tagged_template_compiles_no_error() {
|
||||
// Verify that tagged templates compile without error (even if the tag
|
||||
// function behavior isn't fully tested)
|
||||
// 验证带标签模板编译不报错(即使标签函数行为未被完整测试)。
|
||||
let result = eval("function t(s,v){ return s[0]+v; } t`x${1}`");
|
||||
assert!(!result.starts_with("compile error:"), "Tagged template should compile");
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ fn test_throw_err_type_error_catchable() {
|
||||
|
||||
#[test]
|
||||
fn test_throw_err_type_error_is_object() {
|
||||
// IC_GET_PROP on null throws TypeError which is catchable; catch body runs
|
||||
// 对 null 的 IC_GET_PROP 抛可捕获的 TypeError;catch 体执行。
|
||||
let result = eval("try { null.prop; } catch(e) { 99; }");
|
||||
assert_eq!(result, "99");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user