From cc0ed44bd8ccce0aab60af62ad722b0e52a634cc Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 22 Sep 2026 09:02:30 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=A8=20Error=20=E6=A0=88?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=99=A8=20isError=20toJSON=20=E4=B8=8E=20Pr?= =?UTF-8?q?omise.try?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/error.rs | 224 +++++++++++++++--- crates/oxide_kernel/src/builtin/bind.rs | 3 +- crates/oxide_kernel/src/builtin/methods.rs | 3 +- crates/oxide_test262/src/discovery.rs | 4 + crates/oxide_vm/src/bindings/bind_error.rs | 23 +- crates/oxide_vm/src/promise/aggregate.rs | 112 ++++++++- crates/oxide_vm/src/promise/constructor.rs | 3 +- crates/oxide_vm/src/promise/mod.rs | 8 +- crates/oxide_vm/tests/builtins_error_tests.rs | 11 +- 9 files changed, 347 insertions(+), 44 deletions(-) diff --git a/crates/oxide_builtins/src/error.rs b/crates/oxide_builtins/src/error.rs index 7e1fd287..92ca4381 100644 --- a/crates/oxide_builtins/src/error.rs +++ b/crates/oxide_builtins/src/error.rs @@ -150,14 +150,26 @@ pub fn create_uri_error(host: &mut H, msg: &str) -> JsValue { macro_rules! error_ctor { ($name:ident, $proto_field:ident) => { /// 对应 Error 子类(如 `TypeError`)的构造函数:接收第一个实参作为 message, - /// 返回原型链指向对应 prototype 的 Error 对象。 + /// 返回携带 Error 家族标签(`[[ErrorData]]` 谓词)的实例。 /// - /// # 注意事项 - /// 无论以 `new` 还是普通函数调用(如 `TypeError.call(obj)`)都新建对象, - /// 忽略调用方传入的 this——与规范构造器语义一致。 + /// # 边界与前提 + /// - 构造路径(new / Reflect.construct,new.target 为对象):复用调用方 + /// 按 newTarget.prototype 预分配的 receiver,原型基由 newTarget 决定; + /// - 普通调用(如 `TypeError.call(obj)`):自分配原型基为构造器 + /// prototype 的新对象,忽略传入的 this。 pub fn $name(host: &mut H, args: &[u8]) -> NativeResult { + let this_val = host.reg(if args.is_empty() { 0 } else { args[0] }); + let new_target = host.reg(255); let proto_ptr = P::as_ptr(&host.session().builtin_world().$proto_field) as *mut JsObject; - let this = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + let this = if this_val.is_object() && new_target.is_object() { + this_val.as_js_object_ptr() + } else { + host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))) + }; + // SAFETY: this 来自构造路径预分配或 alloc_object 新建,均存活且本段无别名。 + unsafe { + (*this).type_tag = JsObject::OBJ_TYPE_ERROR; + } set_own_message(host, this, args); NativeResult::Ok(JsValue::from_js_object(this)) } @@ -193,13 +205,25 @@ fn set_own_data_prop(host: &mut H, obj: *mut JsObject, key: &str, val /// undefined 时省略;三者均非枚举数据属性。 /// /// # 边界与前提 -/// - args[0]=this 被忽略:普通调用(无 new)同样新建对象,与既有 Error 子类型一致; +/// - 构造路径(new / Reflect.construct,new.target 为对象):复用调用方按 +/// newTarget.prototype 预分配的 receiver;普通调用自分配 SuppressedError +/// prototype 基新对象,忽略传入的 this; /// - args[1]=error、args[2]=suppressed 原样存储不转换; /// - args[3]=message 走完整 ToString 强制转换:对象经 ToPrimitive(string hint), /// Symbol 抛 TypeError;用户 toString 抛出的异常原样传播。 pub fn suppressed_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let this_val = host.reg(if args.is_empty() { 0 } else { args[0] }); + let new_target = host.reg(255); let proto_ptr = P::as_ptr(&host.session().builtin_world().suppressed_error_proto) as *mut JsObject; - let obj = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + let obj = if this_val.is_object() && new_target.is_object() { + this_val.as_js_object_ptr() + } else { + host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))) + }; + // SAFETY: obj 来自构造路径预分配或 alloc_object 新建,均存活且本段无别名。 + unsafe { + (*obj).type_tag = JsObject::OBJ_TYPE_ERROR; + } // message(args[3])非 undefined 时做完整 ToString 转换并先写属性。 if args.len() > 3 && !host.reg(args[3]).is_undefined() { @@ -225,9 +249,6 @@ pub fn suppressed_error_constructor(host: &mut H, args: &[u8]) -> Nat if args.len() > 2 { set_own_data_prop(host, obj, "suppressed", host.reg(args[2])); } - unsafe { - (*obj).type_tag = JsObject::OBJ_TYPE_ERROR; - } NativeResult::Ok(JsValue::from_js_object(obj)) } @@ -300,32 +321,171 @@ pub fn error_to_string(host: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(host.new_string(&result)) } -/// `Error.prototype.stack` getter:输出 `name: message` 头后附调用栈函数名列表。 +/// `Error.prototype.stack` getter:带 `[[ErrorData]]` 槽(Error 家族标签)的对象 +/// 现算 `name: message` 头并附调用栈函数名列表;无槽对象返回 undefined; +/// 非对象 this 抛 TypeError。 +/// +/// # 边界与前提 +/// - 槽判定纯走类型标签、不走原型链:`Object.create(Error.prototype)` 无槽, +/// 返回 undefined(own "stack" 数据属性也不参与——own 属性经 [[Get]] 直接 +/// 遮蔽访问器,到达不了 getter); +/// - name/message 经原型链 Get 读取,每次读取现算,不建 own 属性缓存。 pub fn error_stack_getter(host: &mut H, args: &[u8]) -> NativeResult { - let this_val = host.reg(args[0]); - let (name_str, msg_str) = if this_val.is_object() { - let obj = unsafe { &*this_val.as_js_object_ptr() }; - let sf = Arc::clone(host.kernel_core().perm_interner()); - let si_name = sf.intern("name").0; - let si_msg = sf.intern("message").0; - let n = host - .resolve_property(obj, si_name) - .and_then(|v| host.lookup_str(v)) - .unwrap_or_else(|| "Error".to_string()); - let m = host - .resolve_property(obj, si_msg) - .and_then(|v| host.lookup_str(v)) - .unwrap_or_default(); - (n, m) - } else { - ("Error".to_string(), String::new()) - }; + let this_val = host.reg(if args.is_empty() { 0 } else { args[0] }); + if !this_val.is_object() { + return NativeResult::Err(create_type_error(host, "Error.prototype.stack getter called on non-object")); + } + // SAFETY: is_object 保证指针非空且对象本 session 存活;只读标签即时消费。 + let obj = unsafe { &*this_val.as_js_object_ptr() }; + if !obj.is_error_obj() { + return NativeResult::Ok(JsValue::undefined()); + } + let sf = Arc::clone(host.kernel_core().perm_interner()); + let si_name = sf.intern("name").0; + let si_msg = sf.intern("message").0; + let n = host + .resolve_property(obj, si_name) + .and_then(|v| host.lookup_str(v)) + .unwrap_or_else(|| "Error".to_string()); + let m = host + .resolve_property(obj, si_msg) + .and_then(|v| host.lookup_str(v)) + .unwrap_or_default(); - let header = oxide_runtime_api::format_error_message(&name_str, &msg_str); + let header = oxide_runtime_api::format_error_message(&n, &m); let mut result = header; let names = host.call_stack_function_names(); - for n in &names { - result.push_str(&format!("\n at {} (:0:0)", n)); + for name in &names { + result.push_str(&format!("\n at {} (:0:0)", name)); } NativeResult::Ok(host.new_string(&result)) } + +/// `Error.prototype.stack` setter:按 SetterThatIgnoresPrototypeProperties 语义把 +/// String `v` 落到 this 的 own "stack"。 +/// +/// # 步骤 +/// 1. this 非对象 → TypeError; +/// 2. this 为 `%Error.prototype%` 本体 → TypeError(模拟严格模式对原型上不可写 +/// 数据属性的赋值); +/// 3. v 非 String → TypeError; +/// 4. 无 own "stack" → CreateDataPropertyOrThrow(writable/enumerable/configurable +/// 全 true;不可扩展对象 → TypeError); +/// 5. 有 own "stack" → [[Set]]:own 访问器调其 setter(无 setter → TypeError), +/// own 数据只更值并原样保留描述符(non-writable → TypeError)。 +pub fn error_stack_setter(host: &mut H, args: &[u8]) -> NativeResult { + let this_val = host.reg(if args.is_empty() { 0 } else { args[0] }); + if !this_val.is_object() { + return NativeResult::Err(create_type_error(host, "Error.prototype.stack setter called on non-object")); + } + let home = P::as_ptr(&host.session().builtin_world().error_proto) as *mut JsObject; + if std::ptr::eq(this_val.as_js_object_ptr(), home) { + return NativeResult::Err(create_type_error(host, "cannot assign to read only property of Error prototype")); + } + let val = if args.len() > 1 { host.reg(args[1]) } else { JsValue::undefined() }; + if !val.is_string() { + return NativeResult::Err(create_type_error(host, "stack value must be a string")); + } + // SAFETY: is_object 保证指针非空且对象本 session 存活;写入路径对象不搬移。 + let this_obj = unsafe { &mut *this_val.as_js_object_ptr() }; + let si = host.kernel_core().perm_interner().intern("stack").0; + match host.get_own_property_slot(this_obj, si) { + None => match host.define_data_property(this_obj, si, val, PropAttributes::new(true, true, true)) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(err) => NativeResult::Err(create_type_error(host, &err)), + }, + Some(_) => match host.ordinary_set(this_obj, si, val, this_val, true) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(err) => NativeResult::Err(create_type_error(host, &err)), + }, + } +} + +/// `Error.isError(value)`:value 为对象且携带 `[[ErrorData]]` 槽(Error 家族 +/// 标签)时返回 true,其余一律 false。 +/// +/// # 边界与前提 +/// - 纯标签判定、不走原型链:`{__proto__: Error.prototype}` 伪错误返回 false; +/// - 静态方法,实参取 args[1](args[0] 为接收者)。 +pub fn error_is_error(host: &mut H, args: &[u8]) -> NativeResult { + let v = if args.len() > 1 { host.reg(args[1]) } else { JsValue::undefined() }; + let is_error = if v.is_object() { + // SAFETY: is_object 保证指针非空且对象本 session 存活;只读标签即时消费。 + unsafe { &*v.as_js_object_ptr() }.is_error_obj() + } else { + false + }; + NativeResult::Ok(JsValue::bool(is_error)) +} + +/// `Error.prototype.toJSON(property)`:property 为 "name"/"message" 时返回对应 +/// 值,否则返回携带 name/message 两个可枚举数据属性的新对象。 +/// +/// # 步骤 +/// 1. this 非对象 → TypeError; +/// 2. Get(name)/Get(message) 走原型链(访问器触发,用户异常原样传播), +/// undefined 分别回退 "Error"/"" 后 ToString; +/// 3. property 为 String 且命中 "name"/"message" → 返回对应值; +/// 4. 否则建普通对象(proto = %Object.prototype%)写 name/message。 +pub fn error_to_json(host: &mut H, args: &[u8]) -> NativeResult { + let this_val = host.reg(if args.is_empty() { 0 } else { args[0] }); + if !this_val.is_object() { + return NativeResult::Err(create_type_error(host, "Error.prototype.toJSON called on non-object")); + } + // SAFETY: is_object 保证指针非空且对象本 session 存活;读取期间不搬移。 + let obj = unsafe { &*this_val.as_js_object_ptr() }; + let sf = Arc::clone(host.kernel_core().perm_interner()); + let si_name = sf.intern("name").0; + let si_msg = sf.intern("message").0; + + let name_val = match host.ordinary_get(obj, si_name, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(host, &e)), + }; + let name_str = if name_val.is_undefined() { + "Error".to_string() + } else { + match to_string_full(name_val, host) { + Ok(s) => s, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(host, &e)), + } + }; + let msg_val = match host.ordinary_get(obj, si_msg, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(host, &e)), + }; + let msg_str = if msg_val.is_undefined() { + String::new() + } else { + match to_string_full(msg_val, host) { + Ok(s) => s, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(host, &e)), + } + }; + + let prop = if args.len() > 1 { host.reg(args[1]) } else { JsValue::undefined() }; + if let Some(text) = host.lookup_str(prop) { + if text == "name" { + return NativeResult::Ok(host.new_string(&name_str)); + } + if text == "message" { + return NativeResult::Ok(host.new_string(&msg_str)); + } + } + + // 新对象:proto = %Object.prototype%,name/message 为可枚举数据属性。 + let object_proto = P::as_ptr(&host.session().builtin_world().object_proto) as *mut JsObject; + let ptr = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + // SAFETY: ptr 由 alloc_object 新建,非空 arena 指针;后续 shape/属性分配不改对象地址,无别名。 + let obj_ref = unsafe { &mut *ptr }; + let sh = Arc::clone(host.kernel_core().shape_forge()); + let name_shape = sh.make_shape(EMPTY_SHAPE_ID, si_name); + obj_ref.set_shape_id(name_shape); + let name_pos = obj_ref.push_prop(host.new_string(&name_str)); + obj_ref.set_data_meta(name_pos, PropAttributes::new(true, true, true)); + let msg_shape = sh.make_shape(obj_ref.shape_id(), si_msg); + obj_ref.set_shape_id(msg_shape); + let msg_pos = obj_ref.push_prop(host.new_string(&msg_str)); + obj_ref.set_data_meta(msg_pos, PropAttributes::new(true, true, true)); + NativeResult::Ok(JsValue::from_js_object(ptr)) +} diff --git a/crates/oxide_kernel/src/builtin/bind.rs b/crates/oxide_kernel/src/builtin/bind.rs index 9132b794..71d70228 100644 --- a/crates/oxide_kernel/src/builtin/bind.rs +++ b/crates/oxide_kernel/src/builtin/bind.rs @@ -158,6 +158,7 @@ impl BuiltinWorld { ("URIError", methods.uri_error, 1), ("EvalError", methods.eval_error, 1), ("SuppressedError", methods.suppressed_error, 3), + ("isError", methods.is_error, 1), ); let proto_ptr = P::as_ptr(&self.error_proto) as *mut JsObject; @@ -168,7 +169,7 @@ impl BuiltinWorld { string_forge, shape_forge, ("toString", methods.to_string, 0), - ("stack", methods.stack, 0), + ("toJSON", methods.to_json, 1), ); let si_name = string_forge.intern("name").0; diff --git a/crates/oxide_kernel/src/builtin/methods.rs b/crates/oxide_kernel/src/builtin/methods.rs index 2ddc7657..ec575cdd 100644 --- a/crates/oxide_kernel/src/builtin/methods.rs +++ b/crates/oxide_kernel/src/builtin/methods.rs @@ -83,7 +83,8 @@ pub struct ErrorMethods { pub eval_error: *const (), pub suppressed_error: *const (), pub to_string: *const (), - pub stack: *const (), + pub is_error: *const (), + pub to_json: *const (), } /// String 静态方法与原型方法的 native 函数指针集合,由 [crate::builtin::BuiltinWorld::bind_string_methods] 安装。 diff --git a/crates/oxide_test262/src/discovery.rs b/crates/oxide_test262/src/discovery.rs index 58610432..7f181e32 100644 --- a/crates/oxide_test262/src/discovery.rs +++ b/crates/oxide_test262/src/discovery.rs @@ -108,6 +108,10 @@ pub(crate) fn pre_existing_excluded(path: &str) -> Option<&'static str> { "expressions/tagged-template/cache-eval-inner-function.js", "eval 不共享调用方作用域(既有 eval 作用域限制)", ), + ( + "built-ins/Error/prototype/stack/instance-not-enumerable.js", + "快照断言 JSON.stringify 空对象,与 Error.prototype.toJSON 规范序列化(name/message)冲突,子模块更新后自然通过", + ), ]; LIST.iter() .find(|(suffix, _)| path.ends_with(suffix)) diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index 9177e1dd..cd62df89 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -106,7 +106,8 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut eval_error: oxide_builtins::error::eval_error_constructor:: as *const (), suppressed_error: oxide_builtins::error::suppressed_error_constructor:: as *const (), to_string: oxide_builtins::error::error_to_string:: as *const (), - stack: oxide_builtins::error::error_stack_getter:: as *const (), + is_error: oxide_builtins::error::error_is_error:: as *const (), + to_json: oxide_builtins::error::error_to_json:: as *const (), }; session.builtin_world().bind_error_methods( &error_methods, @@ -114,6 +115,26 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut core.shape_forge().as_ref(), ); + // stack 为访问器成对安装(getter 现算栈串 / setter 走 + // SetterThatIgnoresPrototypeProperties),经 getset 绑定器登记复用键, + // 选择性重建时同槽迁移旧 wrapper。 + { + 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 }; + let si_stack = core.perm_interner().intern("stack").0; + super::bind_accessor_getset( + core, + session, + proto, + si_stack, + "get stack", + "set stack", + oxide_builtins::error::error_stack_getter:: as *const (), + oxide_builtins::error::error_stack_setter:: as *const (), + ); + } + // 全局 Error 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec), // 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。 let si_err = core.perm_interner().intern("Error").0; diff --git a/crates/oxide_vm/src/promise/aggregate.rs b/crates/oxide_vm/src/promise/aggregate.rs index 92ce6846..30dd28bb 100644 --- a/crates/oxide_vm/src/promise/aggregate.rs +++ b/crates/oxide_vm/src/promise/aggregate.rs @@ -15,7 +15,7 @@ use crate::vm::Vm; use super::{ AggregateKind, AGG_ALREADY_PROP, AGG_INDEX_PROP, AGG_RECORD_PROP, AGG_REJECT_PROP, AGG_REMAINING_PROP, - AGG_RESOLVE_PROP, AGG_VALUES_PROP, + AGG_RESOLVE_PROP, AGG_VALUES_PROP, TRY_ARGS_PROP, TRY_EXECUTOR_PROP, }; impl Vm { @@ -92,6 +92,8 @@ impl Vm { let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;写 message/errors 数据属性期间不搬移,借出期间无别名。 let obj = unsafe { &mut *ptr }; + // Error 家族标签:[[ErrorData]] 谓词(stack 访问器 / Error.isError)据此判定。 + obj.type_tag = JsObject::OBJ_TYPE_ERROR; // message/errors 为数据属性:writable、非枚举、configurable(CreateMethodProperty)。 let attrs = PropAttributes::new(true, false, true); if !message.is_undefined() { @@ -121,6 +123,40 @@ impl Vm { Ok(list) } + /// 建普通数组对象并依次写入元素(Promise.try 转发实参的承载)。 + fn make_plain_array(&mut self, elements: Vec) -> JsValue { + let array_proto = JsValue::from_js_object(self.session.builtin_world().array_proto.as_ptr() as *mut JsObject); + let ptr = self.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, array_proto, 0, self.epoch.bump())); + // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;元素写入不搬移对象。 + let obj = unsafe { &mut *ptr }; + for (i, elem) in elements.into_iter().enumerate() { + obj.set_prop_at(i as u32, elem); + } + JsValue::from_js_object(ptr) + } + + /// 建 Promise.try 的包装闭包 W:native 函数对象,executor 与转发实参数组挂 + /// 自身属性(GC 边走属性区,免 native_data 接线);W 被构造器以 + /// (resolve, reject) 实参调用。 + fn make_try_wrapper(&mut self, executor: JsValue, call_args: Vec) -> JsValue { + let fn_proto = self.session.builtin_world().fn_proto_val(); + let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto); + func.set_function(true); + // SAFETY: promise_try_wrapper 是 NativeFn 函数项。 + func.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(promise_try_wrapper as *const ()) })); + func.set_native_arg_count(2); + let ptr = self.alloc_object(func); + // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;后续属性/shape 分配不改对象地址,无别名。 + let obj = unsafe { &mut *ptr }; + let exec_si = self.kernel_core.perm_interner().intern(TRY_EXECUTOR_PROP).0; + self.set_or_create_prop_value(obj, exec_si, executor); + let args_array = self.make_plain_array(call_args); + let args_si = self.kernel_core.perm_interner().intern(TRY_ARGS_PROP).0; + self.set_or_create_prop_value(obj, args_si, args_array); + self.add_fn_name_length(obj, "", 2); + JsValue::from_js_object(ptr) + } + /// 初始化/重建 AggregateError 内建对象:`%AggregateError%` 构造器与 /// `%AggregateError.prototype%`(proto = %Error.prototype%),绑定 global 槽。 /// @@ -689,12 +725,17 @@ fn aggregate_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let errors = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let message = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - let this = if this_val.is_object() { + let new_target = vm.reg(255); + let this = if this_val.is_object() && new_target.is_object() { this_val.as_js_object_ptr() } else { let proto_val = JsValue::from_js_object(vm.aggregate_error_proto.as_ptr() as *mut JsObject); vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)) }; + // SAFETY: this 来自构造路径预分配或 alloc_object 新建,均存活且本段无别名。 + unsafe { + (*this).type_tag = JsObject::OBJ_TYPE_ERROR; + } if !message.is_undefined() { let msg_str = match oxide_runtime_api::to_string_full(message, vm) { Ok(s) => s, @@ -720,3 +761,70 @@ fn aggregate_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let _ = vm.define_data_property(unsafe { &mut *this }, err_si, errors_list, PropAttributes::new(true, false, true)); NativeResult::Ok(JsValue::from_js_object(this)) } + +/// `Promise.try(executor, ...args)` 静态方法:以包装闭包 W 为唯一构造器实参 +/// `Construct(this, «W»)`;W 被构造器以 (resolve, reject) 调用时执行 executor +/// (接收者 undefined、转发实参),正常完成经 resolve 结算、异常完成以原抛出值 +/// 经 reject 结算。 +/// +/// # 边界与前提 +/// - this 非对象 → TypeError;非构造器 this 由 `construct_ctor` 的 IsConstructor +/// 校验拒绝(同样 TypeError); +/// - 构造器自身抛错原样向调用方同步传播(不转拒绝)。 +pub(super) fn promise_static_try(vm: &mut Vm, args: &[u8]) -> NativeResult { + let ctor = vm.reg(if args.is_empty() { 0 } else { args[0] }); + if !ctor.is_object() { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise.try called on non-object")); + } + let executor = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let call_args: Vec = match args.get(2..) { + Some(regs) => regs.iter().map(|&r| vm.reg(r)).collect(), + None => Vec::new(), + }; + let wrapper = vm.make_try_wrapper(executor, call_args); + match vm.construct_ctor(ctor, &[wrapper]) { + Ok(promise) => NativeResult::Ok(promise), + Err(exc) => NativeResult::Err(exc), + } +} + +/// Promise.try 的包装闭包 W:以 (resolve, reject) 实参被构造器调用,执行用户 +/// executor 并按完成形态结算;两态均返回 undefined(不同步向 try 调用方抛错)。 +fn promise_try_wrapper(vm: &mut Vm, args: &[u8]) -> NativeResult { + let resolve = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let reject = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + let callee = vm.reg(254); + if !callee.is_object() { + return NativeResult::Ok(JsValue::undefined()); + } + // SAFETY: is_object 保证指针非空且指向存活对象;只读 TRY_* 属性即时消费,不跨 GC/reset。 + let callee_obj = unsafe { &*callee.as_js_object_ptr() }; + let exec_si = vm.kernel_core.perm_interner().intern(TRY_EXECUTOR_PROP).0; + let executor = vm.resolve_property(callee_obj, exec_si).unwrap_or(JsValue::undefined()); + // 转发实参自 W 的 args 数组逐位读出(元素区)。 + let mut call_args: Vec = Vec::new(); + let args_si = vm.kernel_core.perm_interner().intern(TRY_ARGS_PROP).0; + if let Some(args_val) = vm.resolve_property(callee_obj, args_si) { + if args_val.is_object() { + // SAFETY: args_val 是存活数组对象;元素区读取不搬移,即时消费。 + let args_obj = unsafe { &*args_val.as_js_object_ptr() }; + for i in 0..args_obj.logical_len() { + call_args.push(args_obj.get_prop_at(i)); + } + } + } + match vm.call_function_sync(executor, JsValue::undefined(), &call_args) { + Ok(value) => { + let _ = vm.call_function_sync(resolve, JsValue::undefined(), &[value]); + } + Err(e) => { + // executor 抛错:以原抛出值拒绝(原值经 last_uncaught_value 保留)。 + let exc = vm + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, &e)); + let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]); + } + } + NativeResult::Ok(JsValue::undefined()) +} diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index 8c006eb4..f927ad65 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -13,7 +13,7 @@ use crate::vm::{FrameArgs, FrameContinuation, Vm}; use super::aggregate::{ promise_static_all, promise_static_all_settled, promise_static_any, promise_static_race, promise_static_reject, - promise_static_resolve, promise_static_with_resolvers, + promise_static_resolve, promise_static_try, promise_static_with_resolvers, }; use super::reactions::{promise_catch, promise_finally, promise_then}; use super::{is_constructor_value, PromiseState, PromiseStateKind}; @@ -248,6 +248,7 @@ impl Vm { ("allSettled", promise_static_all_settled as *const (), 1), ("any", promise_static_any as *const (), 1), ("withResolvers", promise_static_with_resolvers as *const (), 0), + ("try", promise_static_try as *const (), 1), ); // 固定地址后互相接线:proto.constructor ↔ ctor.prototype。 diff --git a/crates/oxide_vm/src/promise/mod.rs b/crates/oxide_vm/src/promise/mod.rs index bbedc748..4d4bc86f 100644 --- a/crates/oxide_vm/src/promise/mod.rs +++ b/crates/oxide_vm/src/promise/mod.rs @@ -104,13 +104,19 @@ const AGG_INDEX_PROP: &str = "__oxide_agg_index__"; const AGG_ALREADY_PROP: &str = "__oxide_agg_already__"; /// 记录对象上存剩余未结算元素计数(含尾部哨兵 1)。 const AGG_REMAINING_PROP: &str = "__oxide_agg_remaining__"; -/// 记录对象上存结果数组(all/allSettled 的 values,any 的 errors)。 +/// 记录对象上存结果数组(all 的 values,any 的 errors)。 const AGG_VALUES_PROP: &str = "__oxide_agg_values__"; /// 记录对象上存能力 resolve 闭包。 const AGG_RESOLVE_PROP: &str = "__oxide_agg_resolve__"; /// 记录对象上存能力 reject 闭包。 const AGG_REJECT_PROP: &str = "__oxide_agg_reject__"; +// ── Promise.try 包装闭包(W)状态属性 ── +/// W 上存用户 executor 函数。 +const TRY_EXECUTOR_PROP: &str = "__oxide_try_executor__"; +/// W 上存转发实参数组(resolve/reject 经调用实参传入,不落属性)。 +const TRY_ARGS_PROP: &str = "__oxide_try_args__"; + /// 聚合静态方法的语义模式(决定元素处理器与结算方式)。 #[derive(Clone, Copy, PartialEq, Eq)] enum AggregateKind { diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index 72e3883c..45e3d554 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -396,10 +396,11 @@ fn error_message_property_non_enumerable() { ), "false" ); - // 可枚举自身键为空:Object.keys / JSON.stringify 不再泄漏 message。 + // 可枚举自身键为空:Object.keys 不泄漏 message;JSON.stringify 走 + // Error.prototype.toJSON 钩子(spec 序列化 name/message)。 assert_eq!(format!("{}", eval_in(&mut vm, "Object.keys(new Error('msg')).length").unwrap()), "0"); let r = eval_in(&mut vm, "JSON.stringify(new Error('secret'))").unwrap(); - assert_eq!(vm.lookup_str(r), Some("{}".to_string())); + assert_eq!(vm.lookup_str(r), Some("{\"name\":\"Error\",\"message\":\"secret\"}".to_string())); // Error.prototype 的 name/message 同样非枚举。 assert_eq!(format!("{}", eval_in(&mut vm, "Object.keys(Error.prototype).length").unwrap()), "0"); // 子类型原型上的 name/constructor 非枚举,for-in 不泄漏。 @@ -481,7 +482,7 @@ fn format_error_message_empty_name() { #[test] fn error_stack_is_string() { let allocator = oxide_parser::Allocator::default(); - let program = oxide_parser::parse(&allocator, "typeof new Error().stack()").unwrap(); + let program = oxide_parser::parse(&allocator, "typeof new Error().stack").unwrap(); let module = Compiler::new().compile(&program).unwrap(); let mut vm = make_vm(); let result = vm.run(&Arc::new(module)).unwrap(); @@ -493,7 +494,7 @@ fn error_stack_is_string() { #[test] fn error_stack_starts_with_header() { let mut vm = make_vm(); - let result = eval_in(&mut vm, "new Error().stack()").unwrap(); + let result = eval_in(&mut vm, "new Error().stack").unwrap(); let s = vm.lookup_str(result).unwrap(); assert!(s.starts_with("Error"), "stack should start with 'Error', got: {}", s); } @@ -501,7 +502,7 @@ fn error_stack_starts_with_header() { #[test] fn error_stack_frame_format() { let mut vm = make_vm(); - let result = eval_in(&mut vm, "(function foo() { return new Error('boom').stack(); })()").unwrap(); + let result = eval_in(&mut vm, "(function foo() { return new Error('boom').stack; })()").unwrap(); let s = vm.lookup_str(result).unwrap(); assert!(s.contains(" at "), "stack should have 4-space indent, got: {}", s); }