mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 补全 Error 栈访问器 isError toJSON 与 Promise.try
This commit is contained in:
@@ -150,14 +150,26 @@ pub fn create_uri_error<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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 {} (<unknown>:0:0)", n));
|
||||
for name in &names {
|
||||
result.push_str(&format!("\n at {} (<unknown>: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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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))
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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] 安装。
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -106,7 +106,8 @@ pub fn bind_error(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
eval_error: oxide_builtins::error::eval_error_constructor::<crate::vm::Vm> as *const (),
|
||||
suppressed_error: oxide_builtins::error::suppressed_error_constructor::<crate::vm::Vm> as *const (),
|
||||
to_string: oxide_builtins::error::error_to_string::<crate::vm::Vm> as *const (),
|
||||
stack: oxide_builtins::error::error_stack_getter::<crate::vm::Vm> as *const (),
|
||||
is_error: oxide_builtins::error::error_is_error::<crate::vm::Vm> as *const (),
|
||||
to_json: oxide_builtins::error::error_to_json::<crate::vm::Vm> as *const (),
|
||||
};
|
||||
session.builtin_world().bind_error_methods(
|
||||
&error_methods,
|
||||
@@ -114,6 +115,26 @@ pub fn bind_error(core: &Arc<KernelCore>, 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::<crate::vm::Vm> as *const (),
|
||||
oxide_builtins::error::error_stack_setter::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
}
|
||||
|
||||
// 全局 Error 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec),
|
||||
// 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。
|
||||
let si_err = core.perm_interner().intern("Error").0;
|
||||
|
||||
@@ -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>) -> 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>) -> 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<JsValue> = 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<JsValue> = 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())
|
||||
}
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user