diff --git a/crates/oxide_builtins/src/global.rs b/crates/oxide_builtins/src/global.rs index e210f55b..118292d2 100644 --- a/crates/oxide_builtins/src/global.rs +++ b/crates/oxide_builtins/src/global.rs @@ -59,6 +59,14 @@ fn string_arg(vm: &mut H, args: &[u8]) -> String { } } +/// 解析两位十六进制单元为字节:逐位严格匹配十六进制数字,不带符号位 +/// (`from_str_radix` 会接受 `+1`/`-1`,`%+1` 这类序列按规范须判非法)。 fn parse_hex_u8(slice: &[u8]) -> Option { - std::str::from_utf8(slice).ok().and_then(|s| u8::from_str_radix(s, 16).ok()) + let digit = |c: u8| match c { + b'0'..=b'9' => Some(c - b'0'), + b'a'..=b'f' => Some(c - b'a' + 10), + b'A'..=b'F' => Some(c - b'A' + 10), + _ => None, + }; + Some(digit(slice[0])? * 16 + digit(slice[1])?) } diff --git a/crates/oxide_builtins/src/global/uri.rs b/crates/oxide_builtins/src/global/uri.rs index 3a90296a..501a2335 100644 --- a/crates/oxide_builtins/src/global/uri.rs +++ b/crates/oxide_builtins/src/global/uri.rs @@ -6,20 +6,56 @@ const URI_RESERVED: &str = ";/?:@&=+$,#"; const URI_SAFE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'();/?:@&=+$,#"; const URI_ERROR_MESSAGE: &str = "malformed URI sequence"; -fn encode_uri_string(input: &str, safe: &str) -> String { - let mut out = String::with_capacity(input.len()); - for ch in input.chars() { - if ch.is_ascii() && safe.contains(ch) { - out.push(ch); +/// 单元口径的 `encodeURI`/`encodeURIComponent` 共享循环:未转义字符原样 +/// 透传,其余按 UTF-8 字节转成 `%XX`;良配 surrogate 对先合成码点再编码, +/// 孤立 surrogate 单元(0xD800-0xDFFF)无良形 UTF-8 编码,按规范抛 URIError +/// (以 `Err(())` 返回)。 +fn encode_uri_units(input: &[u16], safe: &str) -> Result { + let mut out = String::with_capacity(input.len() * 3); + let mut i = 0; + while i < input.len() { + let unit = input[i]; + + // 高 surrogate 须紧跟低 surrogate:合成超平面码点后按 UTF-8 编码。 + if (0xD800..=0xDBFF).contains(&unit) { + let next = *input.get(i + 1).ok_or(())?; + if !(0xDC00..=0xDFFF).contains(&next) { + return Err(()); + } + let cp = 0x10000 + ((unit as u32 - 0xD800) << 10) + (next as u32 - 0xDC00); + let mut buf = [0u8; 4]; + for byte in char::from_u32(cp).expect("合成码点必在超平面区间内").encode_utf8(&mut buf).as_bytes() { + out.push_str(&format!("%{byte:02X}")); + } + i += 2; continue; } + // 孤立低 surrogate(及任何未配对的 surrogate 单元):无良形编码。 + if (0xD800..=0xDFFF).contains(&unit) { + return Err(()); + } + + if unit < 0x80 { + let ch = unit as u8 as char; + if safe.contains(ch) { + out.push(ch); + } else { + out.push_str(&format!("%{unit:02X}")); + } + i += 1; + continue; + } + + // 非 surrogate 单元即良形 BMP 码点(surrogate 臂已先行处理)。 let mut buf = [0u8; 4]; + let ch = char::from_u32(unit as u32).expect("非 surrogate 单元必为良形码点"); for byte in ch.encode_utf8(&mut buf).as_bytes() { out.push_str(&format!("%{byte:02X}")); } + i += 1; } - out + Ok(out) } /// 从单元序列的 `%` 位置读一个 `%XX` 字节(hex 两位须为 ASCII 十六进制单元)。 @@ -103,27 +139,45 @@ fn uri_error(vm: &mut H) -> NativeResult { } /// 取 URI 函数实参的单元序列(单元口径,不 lossy):无实参等价于处理 -/// `"undefined"`。ToPrimitive 抛错(Symbol 等)原样返回异常值。 +/// `"undefined"`。对象经 ToPrimitive 转换,用户 valueOf/toString 抛出的原值 +/// 优先于格式化文本传播(S15.1.3.x A6 族按原值比对)。 fn string_arg_units(vm: &mut H, args: &[u8]) -> Result, NativeResult> { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; match oxide_runtime_api::to_units_full(val, vm) { Ok(u) => Ok(u), - Err(e) => Err(NativeResult::Err(crate::error::create_from_text(vm, &e))), + Err(e) => { + if let Some(exc) = vm.take_uncaught_value() { + return Err(NativeResult::Err(exc)); + } + Err(NativeResult::Err(crate::error::create_from_text(vm, &e))) + } } } /// `encodeURI`:编码输入为 URI,保留未转义字符与保留字符 `;/?:@&=+$,#`, -/// 其余按 UTF-8 字节转成 `%XX`。 +/// 其余按 UTF-8 字节转成 `%XX`;孤立 surrogate 单元抛 URIError。 pub fn encode_uri(vm: &mut H, args: &[u8]) -> NativeResult { - let input = super::string_arg(vm, args); - NativeResult::Ok(vm.new_string(&encode_uri_string(&input, URI_SAFE))) + let input = match string_arg_units(vm, args) { + Ok(u) => u, + Err(e) => return e, + }; + match encode_uri_units(&input, URI_SAFE) { + Ok(s) => NativeResult::Ok(vm.new_string_owned(s)), + Err(()) => uri_error(vm), + } } /// `encodeURIComponent`:编码输入为 URI component,仅保留未转义字符, -/// 保留字符(如 `=&`)也会被转成 `%XX`。 +/// 保留字符(如 `=&`)也会被转成 `%XX`;孤立 surrogate 单元抛 URIError。 pub fn encode_uri_component(vm: &mut H, args: &[u8]) -> NativeResult { - let input = super::string_arg(vm, args); - NativeResult::Ok(vm.new_string(&encode_uri_string(&input, URI_UNESCAPED))) + let input = match string_arg_units(vm, args) { + Ok(u) => u, + Err(e) => return e, + }; + match encode_uri_units(&input, URI_UNESCAPED) { + Ok(s) => NativeResult::Ok(vm.new_string_owned(s)), + Err(()) => uri_error(vm), + } } /// `decodeURI`:解码 `%XX` 序列,但保留字符的转义形式原样保留(不还原), @@ -155,7 +209,7 @@ pub fn decode_uri_component(vm: &mut H, args: &[u8]) -> NativeResult #[cfg(test)] mod tests { - use super::{decode_uri_units, encode_uri_string, URI_UNESCAPED}; + use super::{decode_uri_units, encode_uri_units, URI_UNESCAPED}; fn units(s: &str) -> Vec { s.encode_utf16().collect() @@ -164,14 +218,25 @@ mod tests { #[test] fn encode_uri_keeps_reserved_and_encodes_space() { assert_eq!( - encode_uri_string("https://example.com/path?q=hello world", super::URI_SAFE), + encode_uri_units(&units("https://example.com/path?q=hello world"), super::URI_SAFE).unwrap(), "https://example.com/path?q=hello%20world" ); } #[test] fn encode_uri_component_encodes_reserved() { - assert_eq!(encode_uri_string("a=1&b=2", URI_UNESCAPED), "a%3D1%26b%3D2"); + assert_eq!(encode_uri_units(&units("a=1&b=2"), URI_UNESCAPED).unwrap(), "a%3D1%26b%3D2"); + } + + /// 孤立 surrogate 单元(0xD800-0xDFFF 全量遍历)必须抛 URIError, + /// 良配 surrogate 对按码点 UTF-8 编码。 + #[test] + fn encode_uri_units_rejects_lone_surrogates() { + for u in 0xD800u16..=0xDFFF { + assert!(encode_uri_units(&[u], super::URI_SAFE).is_err()); + assert!(encode_uri_units(&[0x61, u], URI_UNESCAPED).is_err()); + } + assert_eq!(encode_uri_units(&[0xD83D, 0xDE00], URI_UNESCAPED).unwrap(), "%F0%9F%98%80"); } #[test] diff --git a/crates/oxide_kernel/src/builtin/construct.rs b/crates/oxide_kernel/src/builtin/construct.rs index 35ea56d1..19497b66 100644 --- a/crates/oxide_kernel/src/builtin/construct.rs +++ b/crates/oxide_kernel/src/builtin/construct.rs @@ -371,7 +371,7 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) { } let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject); - let non_object_protos: [&P; 27] = [ + let non_object_protos: [&P; 28] = [ &world.array_proto, &world.function_proto, &world.string_proto, @@ -399,6 +399,7 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) { &world.bigint_proto, &world.disposable_stack_proto, &world.async_disposable_stack_proto, + &world.async_iterator_proto, ]; for proto in &non_object_protos { set_proto_if_changed(proto, obj_proto_val); @@ -553,6 +554,7 @@ impl BuiltinWorld { let set_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let string_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let regexp_string_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let async_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let iterator_helper_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let async_disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); @@ -664,6 +666,7 @@ impl BuiltinWorld { set_iterator_proto, string_iterator_proto, regexp_string_iterator_proto, + async_iterator_proto, iterator_helper_proto, disposable_stack_proto, async_disposable_stack_proto, diff --git a/crates/oxide_kernel/src/builtin/rebuild.rs b/crates/oxide_kernel/src/builtin/rebuild.rs index c5ab4cac..37a2230a 100644 --- a/crates/oxide_kernel/src/builtin/rebuild.rs +++ b/crates/oxide_kernel/src/builtin/rebuild.rs @@ -456,6 +456,7 @@ impl BuiltinWorld { set_iterator_proto, string_iterator_proto, regexp_string_iterator_proto, + async_iterator_proto, iterator_helper_proto, disposable_stack_proto, async_disposable_stack_proto, @@ -470,6 +471,7 @@ impl BuiltinWorld { P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), ) } else { ( @@ -479,6 +481,7 @@ impl BuiltinWorld { current.set_iterator_proto.clone(), current.string_iterator_proto.clone(), current.regexp_string_iterator_proto.clone(), + current.async_iterator_proto.clone(), current.iterator_helper_proto.clone(), current.disposable_stack_proto.clone(), current.async_disposable_stack_proto.clone(), @@ -594,6 +597,7 @@ impl BuiltinWorld { set_iterator_proto, string_iterator_proto, regexp_string_iterator_proto, + async_iterator_proto, iterator_helper_proto, disposable_stack_proto, async_disposable_stack_proto, diff --git a/crates/oxide_kernel/src/builtin/world.rs b/crates/oxide_kernel/src/builtin/world.rs index c05a3699..11a71ec6 100644 --- a/crates/oxide_kernel/src/builtin/world.rs +++ b/crates/oxide_kernel/src/builtin/world.rs @@ -134,6 +134,9 @@ pub struct BuiltinWorld { pub string_default_iterator: std::cell::Cell<*const JsObject>, /// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。 pub regexp_string_iterator_proto: P, + /// `%AsyncIteratorPrototype%`:异步迭代器原型(链到 Object.prototype), + /// 持有 `@@asyncIterator`/`@@asyncDispose`;`%AsyncGeneratorPrototype%` 链到它。 + pub async_iterator_proto: P, /// `%IteratorHelperPrototype%`:Iterator helpers 结果对象的共享原型,链到 %IteratorPrototype%。 pub iterator_helper_proto: P, /// `DisposableStack.prototype`:同步资源栈原型(链到 Object.prototype), @@ -350,7 +353,7 @@ impl BuiltinWorld { /// session 收尾(`teardown_heap_data`)与选择性重建收尾(`retire_replaced`) /// 的 P 字段枚举唯一入口:`BuiltinWorld` 新增 P 字段须在此同步补一行,否则 /// 收尾时该字段属性区无法释放、重建原型槽改写/释放漏掉该字段。 - pub(crate) fn all_p_fields(&self) -> [&P; 107] { + pub(crate) fn all_p_fields(&self) -> [&P; 108] { [ &self.object_proto, &self.array_proto, @@ -455,6 +458,7 @@ impl BuiltinWorld { &self.set_iterator_proto, &self.string_iterator_proto, &self.regexp_string_iterator_proto, + &self.async_iterator_proto, &self.iterator_helper_proto, &self.disposable_stack_proto, &self.async_disposable_stack_proto, @@ -604,6 +608,7 @@ impl BuiltinWorld { BuiltinId::BigIntConstructor => &self.bigint_constructor, BuiltinId::BigIntProto => &self.bigint_proto, BuiltinId::Console => &self.console_object, + BuiltinId::AsyncIteratorProto => &self.async_iterator_proto, } } } diff --git a/crates/oxide_kernel/src/kernel/builtin_id.rs b/crates/oxide_kernel/src/kernel/builtin_id.rs index 784ba286..04f25d09 100644 --- a/crates/oxide_kernel/src/kernel/builtin_id.rs +++ b/crates/oxide_kernel/src/kernel/builtin_id.rs @@ -1,4 +1,4 @@ -//! 内置对象 id 枚举(`BuiltinId` 96 变体 + `ALL` 顺序钉表)、世代快照 +//! 内置对象 id 枚举(`BuiltinId` 98 变体 + `ALL` 顺序钉表)、世代快照 //! (`BuiltinSnapshot`)与按家族划分的脏标记位集(`BuiltinDirtySet`); //! `NUM_BUILTINS` 文档承载"新增 BuiltinWorld 字段须同步"四处约束注记。 @@ -11,7 +11,7 @@ use crate::builtin::BuiltinWorld; /// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及 /// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的 /// builtin 家族。 -pub const NUM_BUILTINS: usize = 97; +pub const NUM_BUILTINS: usize = 98; /// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。 /// @@ -118,6 +118,7 @@ pub enum BuiltinId { SharedArrayBufferProto = 94, SharedArrayBufferConstructor = 95, AtomicsObject = 96, + AsyncIteratorProto = 97, } impl BuiltinId { @@ -222,6 +223,7 @@ impl BuiltinId { BuiltinId::SharedArrayBufferProto, BuiltinId::SharedArrayBufferConstructor, BuiltinId::AtomicsObject, + BuiltinId::AsyncIteratorProto, ]; } @@ -369,19 +371,20 @@ impl BuiltinDirtySet { mod tests { use super::*; - /// 槽对齐面:表尾追加 Atomics 单例后总槽数 97,既有 96 个判别值零位移, - /// 快照数组随 NUM_BUILTINS 自动扩维、逐槽对齐。 + /// 槽对齐面:表尾追加 AsyncIteratorPrototype 后总槽数 98,既有 97 个 + /// 判别值零位移,快照数组随 NUM_BUILTINS 自动扩维、逐槽对齐。 #[test] fn builtin_snapshot_all_slots_aligned() { - assert_eq!(NUM_BUILTINS, 97); + assert_eq!(NUM_BUILTINS, 98); assert_eq!(BuiltinId::ALL.len(), NUM_BUILTINS); - // 前 96 项判别值 0-95 逐项不变(尾追加零位移)。 - for i in 0..96usize { + // 前 97 项判别值 0-96 逐项不变(尾追加零位移)。 + for i in 0..97usize { assert_eq!(BuiltinId::ALL[i] as usize, i); } assert_eq!(BuiltinId::ALL[94], BuiltinId::SharedArrayBufferProto); assert_eq!(BuiltinId::ALL[95], BuiltinId::SharedArrayBufferConstructor); assert_eq!(BuiltinId::ALL[96], BuiltinId::AtomicsObject); + assert_eq!(BuiltinId::ALL[97], BuiltinId::AsyncIteratorProto); // 快照经 session 全量构造路径采集,generations 数组维度 = 槽数。 use crate::kernel::{KernelConfig, KernelCore, KernelSession}; diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index bd9377bb..c94ecc42 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -16,7 +16,7 @@ use std::collections::VecDeque; use oxide_builtins::iterator::make_iter_result; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_runtime_api::NativeResult; +use oxide_runtime_api::{NativeResult, VmHost}; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; @@ -731,6 +731,150 @@ pub(crate) fn async_generator_symbol_async_iterator(vm: &mut Vm, args: &[u8]) -> NativeResult::Ok(this_val) } +/// `@@asyncDispose` 结算闭包上存目标能力 promise 的属性名。 +const AD_SETTLE_PROMISE_PROP: &str = "__oxide_async_dispose_promise__"; +/// `@@asyncDispose` 结算闭包上区分 fulfill/reject 角色的属性名。 +const AD_SETTLE_ROLE_PROP: &str = "__oxide_async_dispose_role__"; + +/// `%AsyncIteratorPrototype%[@@asyncDispose]`:新建能力,取 `this` 的 `return` +/// 方法调用后,把结果经 PromiseResolve 展开、以 undefined 结算能力。 +/// +/// # 步骤 +/// 1. GetMethod(this, "return"):非对象接收者或方法缺失/非对象时按无方法处理。 +/// 2. 无方法:能力以 undefined resolve 后立即返回。 +/// 3. Call(return, this, « undefined »):调用失败以原异常 reject 能力。 +/// 4. 结果为 promise 时注册 unwrap 反应(最终值恒 undefined);非 promise 直接 +/// 以 undefined resolve 能力。 +/// +/// # 边界与前提 +/// - 本方法返回的 promise 永不同步抛错:所有异常路径都转为能力 reject。 +/// +/// # 副作用 +/// - 新建能力 promise 与两个结算闭包(登记进 session GC 根)。 +/// - 调用用户 `return` 方法(可能执行任意用户代码)。 +pub(crate) fn async_iterator_async_dispose(vm: &mut Vm, args: &[u8]) -> NativeResult { + let o = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let (promise, _, _) = vm.new_promise_capability(); + + // GetMethod(O, "return"):完整 [[Get]](触发 getter,getter 抛错经 + // IfAbruptRejectPromise 转能力 reject)。 + let ret = if o.is_object() { + let si = vm.kernel_core.perm_interner().intern("return").0; + let obj = unsafe { &*o.as_js_object_ptr() }; + match vm.ordinary_get(obj, si, o) { + Ok(r) => Some(r), + Err(_) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, "AsyncIterator return getter failed")); + let _ = vm.reject_promise(promise, exc); + return NativeResult::Ok(promise); + } + } + } else { + None + }; + let ret = match ret { + Some(r) if r.is_object() => r, + _ => { + let _ = vm.resolve_promise(promise, JsValue::undefined()); + return NativeResult::Ok(promise); + } + }; + + // Call(return, O, « undefined »):调用失败以原异常 reject 能力。 + let result = match vm.call_function_sync(ret, o, &[JsValue::undefined()]) { + Ok(r) => r, + Err(_) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, "AsyncIterator return method call failed")); + let _ = vm.reject_promise(promise, exc); + return NativeResult::Ok(promise); + } + }; + + // PromiseResolve + PerformPromiseThen(resultWrapper, unwrap, undefined, capability): + // unwrap 闭包恒返回 undefined,故能力最终值恒为 undefined。 + if vm.is_promise_value(result) { + let unwrap = vm.make_async_dispose_unwrap_fn(); + let derived = match vm.perform_promise_then(result, unwrap, JsValue::undefined()) { + Ok(d) => d, + Err(exc) => { + let _ = vm.reject_promise(promise, exc); + return NativeResult::Ok(promise); + } + }; + let on_fulfilled = vm.make_async_dispose_settle_fn(promise, false); + let on_rejected = vm.make_async_dispose_settle_fn(promise, true); + let _ = vm.perform_promise_then(derived, on_fulfilled, on_rejected); + } else { + let _ = vm.resolve_promise(promise, JsValue::undefined()); + } + NativeResult::Ok(promise) +} + +/// unwrap 闭包:忽略反应值,恒返回 undefined(规范 CreateBuiltinFunction(unwrap, 1, "", « »))。 +fn async_dispose_unwrap_closure(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(JsValue::undefined()) +} + +/// 结算闭包:以反应值结算自身 prop 携带的能力 promise(角色 prop 区分 fulfill/reject)。 +fn async_dispose_settle_closure(vm: &mut Vm, args: &[u8]) -> NativeResult { + let callee = vm.reg(254); + if !callee.is_object() { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "settle handler is invalid")); + } + let callee_obj = unsafe { &*callee.as_js_object_ptr() }; + let prom_si = vm.kernel_core.perm_interner().intern(AD_SETTLE_PROMISE_PROP).0; + let promise = vm.resolve_property(callee_obj, prom_si).unwrap_or(JsValue::undefined()); + let role_si = vm.kernel_core.perm_interner().intern(AD_SETTLE_ROLE_PROP).0; + let is_reject = vm + .resolve_property(callee_obj, role_si) + .is_some_and(oxide_runtime_api::to_boolean); + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if is_reject { + let _ = vm.reject_promise(promise, value); + } else { + let _ = vm.resolve_promise(promise, value); + } + NativeResult::Ok(JsValue::undefined()) +} + +impl Vm { + /// 构造 `@@asyncDispose` 的 unwrap 闭包(无捕获状态,恒返回 undefined)。 + fn make_async_dispose_unwrap_fn(&mut self) -> 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: async_dispose_unwrap_closure 是 NativeFn 函数项。 + func.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(async_dispose_unwrap_closure as *const ()) })); + func.set_native_arg_count(1); + let ptr = self.alloc_object(func); + self.add_fn_name_length(unsafe { &mut *ptr }, "", 1); + JsValue::from_js_object(ptr) + } + + /// 构造 `@@asyncDispose` 结算闭包:捕获能力 promise 与结算角色, + /// 反应触发时以反应值结算能力 promise。 + fn make_async_dispose_settle_fn(&mut self, promise: JsValue, reject_role: bool) -> 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: async_dispose_settle_closure 是 NativeFn 函数项。 + func.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(async_dispose_settle_closure as *const ()) })); + func.set_native_arg_count(1); + let ptr = self.alloc_object(func); + let obj = unsafe { &mut *ptr }; + let prom_si = self.kernel_core.perm_interner().intern(AD_SETTLE_PROMISE_PROP).0; + self.set_or_create_prop_value(obj, prom_si, promise); + let role_si = self.kernel_core.perm_interner().intern(AD_SETTLE_ROLE_PROP).0; + self.set_or_create_prop_value(obj, role_si, JsValue::bool(reject_role)); + self.add_fn_name_length(obj, "", 1); + JsValue::from_js_object(ptr) + } +} + /// 初始化/重建异步生成器内建对象:`%AsyncGeneratorPrototype%`、 /// `%AsyncGeneratorFunction.prototype%` 与占位 `%AsyncGeneratorFunction%`。 /// @@ -740,10 +884,13 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { let sh = vm.kernel_core.shape_forge().as_ref(); let fn_proto_val = vm.session.builtin_world().fn_proto_val(); let world = vm.session.builtin_world(); - let object_proto_val = JsValue::from_js_object(vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject); + // %AsyncGeneratorPrototype% 链到 %AsyncIteratorPrototype%(规范原型链), + // 方法 next/return/throw 挂其自身。 + let async_iterator_proto_val = + JsValue::from_js_object(world.async_iterator_proto.as_ptr() as *mut JsObject); - // %AsyncGeneratorPrototype%:proto = Object.prototype,方法 next/return/throw。 - let mut ag_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto_val)); + // %AsyncGeneratorPrototype%:proto = %AsyncIteratorPrototype%。 + let mut ag_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, async_iterator_proto_val)); // 站点标签:与 Generator 原型的同名方法槽(next/return/throw)区分复用键。 let ag_label = sf.intern("AsyncGeneratorPrototype").0; oxide_kernel::bind_methods_static!( diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 7e9a0f63..85194e17 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -567,6 +567,37 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession) { ); } + // %AsyncIteratorPrototype%:@@asyncIterator(返回 this)与 @@asyncDispose + // (经 return 方法关闭迭代器)。幂等:键槽已存在时跳过。 + let aiter_proto_ptr = world.async_iterator_proto.as_ptr() as *mut JsObject; + let aiter_proto = unsafe { &mut *aiter_proto_ptr }; + let sym_aiter = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_ITERATOR); + if core.shape_forge().lookup_position(aiter_proto.shape_id(), sym_aiter).is_none() { + bind_well_known_method( + world, + core, + aiter_proto, + oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_ITERATOR, + "[Symbol.asyncIterator]", + crate::async_generator::async_generator_symbol_async_iterator as *const (), + 0, + ); + } + let sym_adispose = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_DISPOSE); + if core.shape_forge().lookup_position(aiter_proto.shape_id(), sym_adispose).is_none() { + bind_well_known_method( + world, + core, + aiter_proto, + oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_DISPOSE, + "[Symbol.asyncDispose]", + crate::async_generator::async_iterator_async_dispose as *const (), + 0, + ); + } + // %IteratorHelperPrototype%:wrapper 结果对象的共享原型(链到 // %IteratorPrototype%),绑 next/return/throw 三方法(throw length=1, // next/return 为 0)+ @@toStringTag 数据属性 "Iterator Helper"。