From 11903fc52075a481130efed7a30265d134feccb5 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 11:41:50 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B9=9D=E5=8E=9F=E5=9E=8B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20toSource=20=E9=81=97=E7=95=99=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 ++-- Cargo.toml | 2 +- crates/oxide_builtins/src/lib.rs | 2 + crates/oxide_builtins/src/to_source.rs | 173 ++++++++++++++++++ crates/oxide_vm/src/bindings/bind_array.rs | 5 +- crates/oxide_vm/src/bindings/bind_boolean.rs | 1 + crates/oxide_vm/src/bindings/bind_date.rs | 1 + crates/oxide_vm/src/bindings/bind_error.rs | 14 ++ crates/oxide_vm/src/bindings/bind_function.rs | 10 + crates/oxide_vm/src/bindings/bind_number.rs | 1 + crates/oxide_vm/src/bindings/bind_object.rs | 2 + crates/oxide_vm/src/bindings/bind_regexp.rs | 1 + crates/oxide_vm/src/bindings/bind_string.rs | 5 +- .../oxide_vm/tests/builtins_tosource_tests.rs | 142 ++++++++++++++ 14 files changed, 375 insertions(+), 22 deletions(-) create mode 100644 crates/oxide_builtins/src/to_source.rs create mode 100644 crates/oxide_vm/tests/builtins_tosource_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 968eba29..600e99e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.100" +version = "0.8.101" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.100" +version = "0.8.101" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.100" +version = "0.8.101" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.100" +version = "0.8.101" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.100" +version = "0.8.101" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.100" +version = "0.8.101" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.100" +version = "0.8.101" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.100" +version = "0.8.101" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.100" +version = "0.8.101" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.100" +version = "0.8.101" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.100" +version = "0.8.101" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index a88c77b1..010ad7d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.100" +version = "0.8.101" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index d3c7587e..94d09652 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -75,6 +75,8 @@ pub mod symbol; pub mod ta_codec; /// Temporal 内置对象实现(目录模块树:Now / Instant / PlainDate / PlainTime / PlainDateTime / PlainMonthDay / PlainYearMonth / ZonedDateTime / Duration)。 pub mod temporal; +/// toSource 遗留扩展(九原型 own 方法与 null/undefined this 门禁)。 +pub mod to_source; /// TypedArray 内置对象实现(元素访问、fill/slice/subarray/set 等)。 pub mod typed_array; /// WeakMap 条目表实现(弱键 → 强值盒与 GC 六站点接线函数族)。 diff --git a/crates/oxide_builtins/src/to_source.rs b/crates/oxide_builtins/src/to_source.rs new file mode 100644 index 00000000..a5c55d7b --- /dev/null +++ b/crates/oxide_builtins/src/to_source.rs @@ -0,0 +1,173 @@ +//! toSource(SpiderMonkey 遗留扩展):Object / Function / Array / String / Boolean / +//! Number / Date / RegExp / Error 九原型各一个 own 方法。 +//! +//! 关键约定: +//! - 门禁统一走 CheckObjectCoercible 口径——this 为 null / undefined 抛 TypeError, +//! 其余原始值与原对象放行; +//! - 返回值取最小合理源码串(不追求 SpiderMonkey 完整序列化):Function 空串、 +//! Object / Array 恒形、String 带引号、Boolean / Number 字面量、Date / Error 构造形、 +//! RegExp 斜杠形; +//! - 本模块不提供 test262 度量面,验收靠单测钉 own 存在性与门禁抛错。 + +use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_types::value::JsValue; + +/// 九原型 toSource 共享门禁:this 为 null / undefined 抛 TypeError(CheckObjectCoercible +/// 口径),其余原样返回 this 值供各臂消费。 +fn to_source_check_this(vm: &mut H, args: &[u8]) -> Result { + let this_val = vm.reg(args[0]); + if this_val.is_null() || this_val.is_undefined() { + return Err(crate::error::create_type_error(vm, "toSource called on null or undefined")); + } + Ok(this_val) +} + +/// `Object.prototype.toSource`:门禁后恒返 `"{}"`。 +pub fn to_source_object(vm: &mut H, args: &[u8]) -> NativeResult { + if let Err(err) = to_source_check_this(vm, args) { + return NativeResult::Err(err); + } + NativeResult::Ok(vm.new_string_owned("{}".to_string())) +} + +/// `Function.prototype.toSource`:引擎无函数源文本,门禁后恒返空串(不抛错)。 +pub fn to_source_function(vm: &mut H, args: &[u8]) -> NativeResult { + if let Err(err) = to_source_check_this(vm, args) { + return NativeResult::Err(err); + } + NativeResult::Ok(vm.new_string_owned(String::new())) +} + +/// `Array.prototype.toSource`:门禁后恒返 `"[]"`。 +pub fn to_source_array(vm: &mut H, args: &[u8]) -> NativeResult { + if let Err(err) = to_source_check_this(vm, args) { + return NativeResult::Err(err); + } + NativeResult::Ok(vm.new_string_owned("[]".to_string())) +} + +/// `String.prototype.toSource`:门禁后读 this 串值,双引号包裹返回。 +/// +/// # 边界与前提 +/// - this 为字符串时直读单元文本;非字符串走完整 ToString(对象经 ToPrimitive, +/// 用户转换异常原样传播)。 +pub fn to_source_string(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = match to_source_check_this(vm, args) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let text = if this_val.is_string() { + vm.lookup_str(this_val).unwrap_or_default() + } else { + match oxide_runtime_api::to_string_full(this_val, vm) { + Ok(s) => s, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + } + }; + NativeResult::Ok(vm.new_string_owned(format!("\"{}\"", text))) +} + +/// `Boolean.prototype.toSource`:门禁后按 ToBoolean 判位,返 `"true"` / `"false"`。 +pub fn to_source_boolean(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = match to_source_check_this(vm, args) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let b = oxide_runtime_api::to_boolean(this_val); + NativeResult::Ok(vm.new_string_owned(if b { "true".to_string() } else { "false".to_string() })) +} + +/// `Number.prototype.toSource`:门禁后按 ToNumber 取 double,十进制字面量返回。 +/// +/// # 边界与前提 +/// - this 为 double 时直读;非 double 走完整 ToNumber(用户转换异常原样传播)。 +pub fn to_source_number(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = match to_source_check_this(vm, args) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let d = if this_val.is_double() { + this_val.as_double() + } else { + match oxide_runtime_api::to_number_full(this_val, vm) { + Ok(n) => n, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + } + }; + NativeResult::Ok(vm.number_to_string_cached(d)) +} + +/// `Date.prototype.toSource`:门禁后读时间戳(prop 0),返 `new Date()` 形; +/// 非 Date 对象或时间戳缺失时回退 `new Date(NaN)`。 +pub fn to_source_date(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = match to_source_check_this(vm, args) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let ms = if this_val.is_object() { + let ptr = this_val.as_js_object_ptr(); + if !ptr.is_null() && unsafe { &*ptr }.is_date_obj() { + let v = unsafe { &*ptr }.get_prop_at(0); + if v.is_double() { + v.as_double() + } else { + f64::NAN + } + } else { + f64::NAN + } + } else { + f64::NAN + }; + let num_text = oxide_runtime_api::js_number_to_string(ms); + NativeResult::Ok(vm.new_string_owned(format!("new Date({})", num_text))) +} + +/// `RegExp.prototype.toSource`:门禁后读 `[[OriginalSource]]` / `[[OriginalFlags]]` 槽, +/// 返 `/source/flags` 形;非 RegExp 对象回退 `/(?:)/`。 +pub fn to_source_regexp(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = match to_source_check_this(vm, args) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + if !this_val.is_object() { + return NativeResult::Ok(vm.new_string_owned("/(?:)/".to_string())); + } + let ptr = this_val.as_js_object_ptr(); + if ptr.is_null() || !unsafe { &*ptr }.is_regexp_obj() { + return NativeResult::Ok(vm.new_string_owned("/(?:)/".to_string())); + } + let re = unsafe { &*ptr }; + let source = vm.lookup_str(re.get_regexp_source()).unwrap_or_default(); + let flags = vm.lookup_str(re.get_regexp_flags()).unwrap_or_default(); + NativeResult::Ok(vm.new_string_owned(format!("/{}/{}", source, flags))) +} + +/// `Error.prototype.toSource`:门禁后读 `message` 属性,返 `new Error("message")` 形; +/// 无 message 时 message 段为空串。 +/// +/// # 边界与前提 +/// - message 经原型链 Get 读取(访问器触发,用户异常吞掉回落空串); +/// - message 非 undefined 时走完整 ToString(Symbol 等不可转换场景回落空串)。 +pub fn to_source_error(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = match to_source_check_this(vm, args) { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let mut msg = String::new(); + if this_val.is_object() { + let ptr = this_val.as_js_object_ptr(); + if !ptr.is_null() { + let obj = unsafe { &*ptr }; + let si_msg = vm.perm_intern("message"); + if let Ok(v) = vm.ordinary_get(obj, si_msg, this_val) { + if !v.is_undefined() { + if let Ok(s) = oxide_runtime_api::to_string_full(v, vm) { + msg = s; + } + } + } + } + } + NativeResult::Ok(vm.new_string_owned(format!("new Error({:?})", msg))) +} diff --git a/crates/oxide_vm/src/bindings/bind_array.rs b/crates/oxide_vm/src/bindings/bind_array.rs index 5e9a7746..75f855b3 100644 --- a/crates/oxide_vm/src/bindings/bind_array.rs +++ b/crates/oxide_vm/src/bindings/bind_array.rs @@ -67,7 +67,10 @@ pub fn bind_array(core: &Arc, session: &KernelSession, global: &mut session.builtin_world(), array_proto, core, - &[("toString", oxide_builtins::array::array_to_string:: as *const (), 0)], + &[ + ("toString", oxide_builtins::array::array_to_string:: as *const (), 0), + ("toSource", oxide_builtins::to_source::to_source_array:: as *const (), 0), + ], ); // 内置原型方法不可枚举(否则会泄漏进 for-in)。 let si = core.perm_interner().intern("toString").0; diff --git a/crates/oxide_vm/src/bindings/bind_boolean.rs b/crates/oxide_vm/src/bindings/bind_boolean.rs index bc7275ae..82562d70 100644 --- a/crates/oxide_vm/src/bindings/bind_boolean.rs +++ b/crates/oxide_vm/src/bindings/bind_boolean.rs @@ -29,6 +29,7 @@ pub fn bind_boolean(core: &Arc, session: &KernelSession, global: &mu oxide_builtins::boolean::boolean_prototype_to_string:: as *const (), 0, ), + ("toSource", oxide_builtins::to_source::to_source_boolean:: as *const (), 0), ], ); diff --git a/crates/oxide_vm/src/bindings/bind_date.rs b/crates/oxide_vm/src/bindings/bind_date.rs index 3864455b..ebc65801 100644 --- a/crates/oxide_vm/src/bindings/bind_date.rs +++ b/crates/oxide_vm/src/bindings/bind_date.rs @@ -139,6 +139,7 @@ pub fn bind_date(core: &Arc, session: &KernelSession, global: &mut J 0, ), ("valueOf", oxide_builtins::date::date_value_of:: as *const (), 0), + ("toSource", oxide_builtins::to_source::to_source_date:: as *const (), 0), ], ); diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index cd62df89..6210c2f0 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -135,6 +135,20 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut ); } + // toSource 遗留扩展(Error 原型方法不走 kernel 侧 bind_error_methods, + // 此处独立经 apply_binding_table 安装,描述符非枚举由绑定器内置)。 + { + let proto_ptr = session.builtin_world().error_proto.as_ptr() as *mut JsObject; + // SAFETY: error_proto 由 session 持有,存活整个 session;本块内只改其 shape/属性区,无 reset。 + let proto = unsafe { &mut *proto_ptr }; + super::apply_binding_table( + session.builtin_world(), + proto, + core, + &[("toSource", oxide_builtins::to_source::to_source_error:: as *const (), 0)], + ); + } + // 全局 Error 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec), // 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。 let si_err = core.perm_interner().intern("Error").0; diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index 62346788..27c3bd50 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -6,6 +6,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; +use super::apply_binding_table; use super::bind_global_value; use super::configure_native_constructor; @@ -66,6 +67,15 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m // 函数对象,任何访问抛 TypeError(AddRestrictedFunctionProperties 语义)。 bind_function_proto_restricted(core, session, proto); + // toSource 遗留扩展(Function 原型方法不走 kernel 侧 bind_function_methods, + // 此处独立经 apply_binding_table 安装,描述符非枚举由绑定器内置)。 + apply_binding_table( + session.builtin_world(), + proto, + core, + &[("toSource", oxide_builtins::to_source::to_source_function:: as *const (), 0)], + ); + bind_global_value(core, global, "Function", JsValue::from_js_object(function_ctor)); } diff --git a/crates/oxide_vm/src/bindings/bind_number.rs b/crates/oxide_vm/src/bindings/bind_number.rs index 6820a5d2..969fa76c 100644 --- a/crates/oxide_vm/src/bindings/bind_number.rs +++ b/crates/oxide_vm/src/bindings/bind_number.rs @@ -59,6 +59,7 @@ pub fn bind_number(core: &Arc, session: &KernelSession, global: &mut 0, ), ("valueOf", oxide_builtins::number::number_value_of:: as *const (), 0), + ("toSource", oxide_builtins::to_source::to_source_number:: as *const (), 0), ], ); diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index ebc65d22..d2af6e03 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -86,6 +86,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut oxide_builtins::object::object_proto_lookup_setter:: as *const (), 1, ), + ("toSource", oxide_builtins::to_source::to_source_object:: as *const (), 0), ], ); // 内置原型方法不可枚举(否则会泄漏进 for-in)。 @@ -98,6 +99,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut "__defineSetter__", "__lookupGetter__", "__lookupSetter__", + "toSource", ] { let si = core.perm_interner().intern(name).0; if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) { diff --git a/crates/oxide_vm/src/bindings/bind_regexp.rs b/crates/oxide_vm/src/bindings/bind_regexp.rs index 27f318a2..eee142ca 100644 --- a/crates/oxide_vm/src/bindings/bind_regexp.rs +++ b/crates/oxide_vm/src/bindings/bind_regexp.rs @@ -38,6 +38,7 @@ pub fn bind_regexp(core: &Arc, session: &KernelSession, global: &mut ("test", oxide_builtins::regexp::regexp_test:: as *const (), 1), ("toString", oxide_builtins::regexp::regexp_to_string:: as *const (), 0), ("compile", oxide_builtins::regexp::regexp_compile:: as *const (), 2), + ("toSource", oxide_builtins::to_source::to_source_regexp:: as *const (), 0), ], ); diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index da1f59e4..406078d9 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -86,7 +86,10 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut session.builtin_world(), proto, core, - &[("toString", oxide_builtins::string::string_to_string:: as *const (), 0)], + &[ + ("toString", oxide_builtins::string::string_to_string:: as *const (), 0), + ("toSource", oxide_builtins::to_source::to_source_string:: as *const (), 0), + ], ); // String.prototype[@@iterator]:逐 code point 迭代字符;name 属性按规范 // 落 "[Symbol.iterator]"。 diff --git a/crates/oxide_vm/tests/builtins_tosource_tests.rs b/crates/oxide_vm/tests/builtins_tosource_tests.rs new file mode 100644 index 00000000..ac5cd6b3 --- /dev/null +++ b/crates/oxide_vm/tests/builtins_tosource_tests.rs @@ -0,0 +1,142 @@ +//! toSource 遗留扩展(九原型 own 方法与 null/undefined this 门禁)单测。 +//! +//! 验收面:九原型 own toSource 存在性、描述符非枚举、null/undefined/de-Reference +//! 三形态门禁抛 TypeError、返回值形态抽查。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&Arc::new(module)) +} + +fn to_str(vm: &Vm, val: JsValue) -> String { + vm.lookup_str(val).unwrap_or_default() +} + +const CLASSES: [&str; 9] = ["Object", "Function", "Array", "String", "Boolean", "Number", "Date", "RegExp", "Error"]; + +// 九原型 own toSource 存在且为函数。 +#[test] +fn tosource_own_exists_on_nine_prototypes() { + let mut vm = Vm::new(); + for cls in CLASSES { + let source = format!("typeof {}.prototype.toSource", cls); + let result = eval(&mut vm, &source).unwrap(); + assert_eq!(to_str(&vm, result), "function", "toSource missing on {cls}"); + } +} + +// 九原型 toSource 描述符 { writable:true, enumerable:false, configurable:true }。 +#[test] +fn tosource_descriptor_non_enumerable() { + let mut vm = Vm::new(); + for cls in CLASSES { + let source = format!( + "(() => {{ var d = Object.getOwnPropertyDescriptor({}.prototype, 'toSource'); return d.writable + ',' + d.enumerable + ',' + d.configurable; }})()", + cls + ); + let result = eval(&mut vm, &source).unwrap(); + assert_eq!(to_str(&vm, result), "true,false,true", "descriptor wrong on {cls}"); + } +} + +// 九原型 toSource.call(null) 抛 TypeError(门禁核)。 +#[test] +fn tosource_rejects_null_this() { + let mut vm = Vm::new(); + for cls in CLASSES { + let source = format!( + "(() => {{ try {{ {}.prototype.toSource.call(null); return 'no-throw'; }} catch (e) {{ return e instanceof TypeError ? 'TypeError' : 'other'; }} }})()", + cls + ); + let result = eval(&mut vm, &source).unwrap(); + assert_eq!(to_str(&vm, result), "TypeError", "null this not rejected on {cls}"); + } +} + +// 九原型 toSource.call(undefined) 抛 TypeError(门禁核)。 +#[test] +fn tosource_rejects_undefined_this() { + let mut vm = Vm::new(); + for cls in CLASSES { + let source = format!( + "(() => {{ try {{ {}.prototype.toSource.call(undefined); return 'no-throw'; }} catch (e) {{ return e instanceof TypeError ? 'TypeError' : 'other'; }} }})()", + cls + ); + let result = eval(&mut vm, &source).unwrap(); + assert_eq!(to_str(&vm, result), "TypeError", "undefined this not rejected on {cls}"); + } +} + +// 九原型 (0, X.prototype.toSource)() de-Reference 形态抛 TypeError。 +#[test] +fn tosource_rejects_dereference_call() { + let mut vm = Vm::new(); + for cls in CLASSES { + let source = format!( + "(() => {{ try {{ (0, {}.prototype.toSource)(); return 'no-throw'; }} catch (e) {{ return e instanceof TypeError ? 'TypeError' : 'other'; }} }})()", + cls + ); + let result = eval(&mut vm, &source).unwrap(); + assert_eq!(to_str(&vm, result), "TypeError", "de-Reference not rejected on {cls}"); + } +} + +// 返回值形态抽查:Object/Array/Boolean/Number/String 五个最小合理形。 +#[test] +fn tosource_return_value_forms() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Object.prototype.toSource.call({})").unwrap(); + assert_eq!(to_str(&vm, result), "{}"); + + let result = eval(&mut vm, "Array.prototype.toSource.call([])").unwrap(); + assert_eq!(to_str(&vm, result), "[]"); + + let result = eval(&mut vm, "Boolean.prototype.toSource.call(true)").unwrap(); + assert_eq!(to_str(&vm, result), "true"); + + let result = eval(&mut vm, "Number.prototype.toSource.call(42)").unwrap(); + assert_eq!(to_str(&vm, result), "42"); + + let result = eval(&mut vm, "String.prototype.toSource.call('hi')").unwrap(); + assert_eq!(to_str(&vm, result), "\"hi\""); +} + +// Function 臂:引擎无函数源文本,恒返空串。 +#[test] +fn tosource_function_returns_empty() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Function.prototype.toSource.call(function(){})").unwrap(); + assert_eq!(to_str(&vm, result), ""); +} + +// RegExp 臂:/source/flags 斜杠形。 +#[test] +fn tosource_regexp_slash_form() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "RegExp.prototype.toSource.call(/ab+c/i)").unwrap(); + assert_eq!(to_str(&vm, result), "/ab+c/i"); +} + +// Error 臂:new Error("message") 构造形。 +#[test] +fn tosource_error_construction_form() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Error.prototype.toSource.call(new Error('boom'))").unwrap(); + assert_eq!(to_str(&vm, result), "new Error(\"boom\")"); +} + +// Date 臂:new Date() 构造形。 +#[test] +fn tosource_date_construction_form() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Date.prototype.toSource.call(new Date(1234))").unwrap(); + assert_eq!(to_str(&vm, result), "new Date(1234)"); +}