From 89c1f3e1627de2ab796856e8ed1b336ffa108115 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 10 Oct 2026 05:06:52 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=AD=E4=BB=A3=E5=99=A8=E5=8C=85?= =?UTF-8?q?=E8=A3=85=E5=99=A8=E9=9B=B6=E5=8F=82=E8=BD=AC=E5=8F=91=E5=8A=A0?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=9C=9F=E6=83=B0=E6=80=A7return=E8=AF=BB?= =?UTF-8?q?=E5=8F=96?= 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/iterator.rs | 62 ++++--- .../tests/iterator_wrapper_tests.rs | 151 ++++++++++++++++++ 4 files changed, 215 insertions(+), 38 deletions(-) create mode 100644 crates/oxide_builtins/tests/iterator_wrapper_tests.rs diff --git a/Cargo.lock b/Cargo.lock index d9f19de0..23fc3836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.13.27" +version = "0.13.28" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.13.27" +version = "0.13.28" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.13.27" +version = "0.13.28" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.13.27" +version = "0.13.28" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.13.27" +version = "0.13.28" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.13.27" +version = "0.13.28" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.13.27" +version = "0.13.28" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.13.27" +version = "0.13.28" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.13.27" +version = "0.13.28" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.13.27" +version = "0.13.28" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.13.27" +version = "0.13.28" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 6c11185b..9e28db10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.13.27" +version = "0.13.28" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 872fab2f..49bf20a2 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1230,11 +1230,13 @@ pub(crate) fn try_make_iterator_inner_proto( /// `@@iterator` 返回自身)。 /// 2. 写入 `__inner__`/`__index__` 槽;鸭子回退路径把创建时读到的 `next` 闭包 /// 缓存进 `__next__` 槽,消费期直读槽不重触发 getter。 -/// 3. 挂 `next` 方法;`bind_return` 且内层有可调用 `return` 时条件暴露 `return`。 +/// 3. 挂 `next` 方法;`bind_return` 且内层是对象时恒挂 `return` 方法 +/// (内层 return 在关闭期惰性读取,构造期不触碰其 getter)。 /// /// # 边界 /// - `bind_return` 控制是否暴露 `return` 方法(for-of/解构的 IteratorClose 需要, -/// `yield*` 委托不需要且须避免创建时访问内层 return getter)。 +/// `yield*` 委托不需要);内层 return 一律关闭期惰性 GetMethod,构造期不触碰 +/// 其 getter,getter 抛错与不可调用判定在关闭期发生。 /// - 本函数不解析 `@@iterator`:`get_iterator` 派生内层,或默认迭代器函数直接以 /// 已 ToString 的串为内层构造(其语义只由 `this` 决定,与属性表状态解耦)。 pub(crate) fn build_iterator_wrapper( @@ -1264,22 +1266,28 @@ pub(crate) fn build_iterator_wrapper( let wrapper_next = make_native_function(vm, "next", iterator_wrapper_next:: as *const (), 0); vm.set_or_create_prop_value(wrapper_obj, next_si, wrapper_next); - // for-of/解构的 IteratorClose 需要 return 方法:条件暴露(内层有可调用 return 时)。 + // for-of/解构的 IteratorClose 需要 return 方法:bind_return 且内层是对象时恒挂, + // 内层 return 由包装器 return 方法在关闭期惰性 GetMethod(getter 抛错传播、 + // 不可调用抛 TypeError、undefined/null 无操作)。 // `yield*` 委托(bind_return=false)不绑定,转发时对内层延迟 GetMethod。 let return_si = vm.perm_intern("return"); if bind_return && inner.is_object() { - let inner_obj = unsafe { &*inner.as_js_object_ptr() }; - if let Ok(return_fn) = vm.ordinary_get(inner_obj, return_si, inner) { - if is_callable(return_fn) { - let wrapper_return = make_native_function(vm, "return", iterator_wrapper_return:: as *const (), 0); - vm.set_or_create_prop_value(wrapper_obj, return_si, wrapper_return); - } - } + let wrapper_return = make_native_function(vm, "return", iterator_wrapper_return:: as *const (), 0); + vm.set_or_create_prop_value(wrapper_obj, return_si, wrapper_return); } JsValue::from_js_object(wrapper) } +/// 迭代器包装器的 `return` 方法:关闭期惰性读取内层 `return`(完整 GetMethod +/// 步序)并转发调用,默认零参。 +/// +/// # 步骤 +/// 1. 读 `__inner__` 槽;this 非对象或槽缺失/非对象时返回 undefined(无操作)。 +/// 2. GetMethod(inner, "return"):getter 抛错透传原异常;undefined/null 返回空 +/// 对象(无操作,使调用方步 7 的对象校验通过);已定义不可调用值抛 TypeError。 +/// 3. 调用方显式传参时转发该实参(Iterator.prototype.return 的 return(v) 语义), +/// 否则零参调用(IteratorClose 步 4c);结果原样返回,步 7 由调用方执行。 fn iterator_wrapper_return(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !this_val.is_object() { @@ -1291,16 +1299,29 @@ fn iterator_wrapper_return(vm: &mut H, args: &[u8]) -> NativeResult { Ok(inner) if inner.is_object() => inner, _ => return NativeResult::Ok(JsValue::undefined()), }; - // 延迟 GetMethod:内层无 return 方法时返回 undefined(IteratorClose 跳过)。 + // 延迟 GetMethod:内层 return 在关闭期读取,getter 抛错透传原异常。 let inner_obj = unsafe { &*inner.as_js_object_ptr() }; let return_si = vm.perm_intern("return"); let return_fn = match vm.ordinary_get(inner_obj, return_si, inner) { Ok(f) if is_callable(f) => f, - _ => return NativeResult::Ok(JsValue::undefined()), + // undefined/null:GetMethod 返回 unused,无操作,返空对象使调用方步 7 通过。 + Ok(f) if f.is_undefined() || f.is_null() => { + let object_proto = vm.builtin_proto(ProtoKind::ObjectProto); + let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + return NativeResult::Ok(JsValue::from_js_object(obj)); + } + // 已定义不可调用值:GetMethod 抛 TypeError。 + Ok(_) => return NativeResult::Err(crate::error::create_type_error(vm, "iterator return is not a function")), + Err(err) => return NativeResult::Err(engine_error(vm, &err)), }; - // 转发调用实参(`yield*` 委托的 return(v) 语义),缺省为 undefined。 - let arg = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - match vm.call_function_sync(return_fn, inner, &[arg]) { + // 默认零参(IteratorClose 步 4c);调用方显式传参时转发(Iterator.prototype.return + // 的 return(v) 语义)。结果原样返回,步 7 由调用方 IteratorClose 执行。 + let result = if args.len() > 1 { + vm.call_function_sync(return_fn, inner, &[vm.reg(args[1])]) + } else { + vm.call_function_sync(return_fn, inner, &[]) + }; + match result { Ok(result) => NativeResult::Ok(result), Err(err) => match vm.take_uncaught_value() { Some(original) => NativeResult::Err(original), @@ -1363,9 +1384,14 @@ pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult } } }; - // 转发调用实参(`yield*` 委托的 next(v) 语义),缺省为 undefined。 - let arg = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - return match vm.call_function_sync(next, inner, &[arg]) { + // 默认零参(IteratorNext 步 4 零参调用);调用方显式传参时转发(Iterator. + // prototype.next 的 next(v) 语义)。 + let result = if args.len() > 1 { + vm.call_function_sync(next, inner, &[vm.reg(args[1])]) + } else { + vm.call_function_sync(next, inner, &[]) + }; + return match result { Ok(result) => NativeResult::Ok(result), // 透传原始抛出的值(任意类型)而非重新包装成 TypeError, // 使外围 try/catch 能看到真正的错误。 diff --git a/crates/oxide_builtins/tests/iterator_wrapper_tests.rs b/crates/oxide_builtins/tests/iterator_wrapper_tests.rs new file mode 100644 index 00000000..36e2f97c --- /dev/null +++ b/crates/oxide_builtins/tests/iterator_wrapper_tests.rs @@ -0,0 +1,151 @@ +//! 迭代器包装器(make_iterator_for_value 建出的统一迭代器对象)的 +//! return/next 转发语义测试:零参转发、关闭期惰性 GetMethod(getter 抛错 +//! 传播、不可调用 TypeError、null 无操作)、构造期不触碰内层 return getter。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_compiler::DefaultCompilerService; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + vm.set_compiler_service(Arc::new(DefaultCompilerService)); + match vm.run(&Arc::new(module)) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn wrapper_return_forwards_zero_args() { + // 包装器 return 默认零参转发内层 return(IteratorClose 步 4c)。 + assert_eq!( + eval( + "var it={next:function(){return {value:1,done:false};},\ + return:function(){globalThis.n=arguments.length;return {};}};\ + for(var v of it){break;}globalThis.n===0" + ), + "true", + "wrapper return must call inner return with zero arguments" + ); +} + +#[test] +fn wrapper_next_forwards_zero_args() { + // 包装器 next 默认零参转发内层 next(IteratorNext 步 4)。 + assert_eq!( + eval( + "var it={next:function(){globalThis.n=arguments.length;\ + return {value:1,done:true};}};\ + for(var v of it){}globalThis.n===0" + ), + "true", + "wrapper next must call inner next with zero arguments" + ); +} + +#[test] +fn wrapper_return_getter_throw_propagates_on_normal_close() { + // 正常完成关闭时,内层 return getter 抛错必须传播(关闭期惰性 GetMethod)。 + assert_eq!( + eval( + "var it={next:function(){return {value:1,done:false};},\ + get return(){throw new TypeError('getter');}};\ + globalThis.r=false;\ + try{for(var v of it){break;}}\ + catch(e){globalThis.r=(e.name==='TypeError'&&e.message==='getter');}\ + globalThis.r" + ), + "true", + "getter throw on inner return must propagate on normal-completion close" + ); +} + +#[test] +fn wrapper_return_non_callable_throws_type_error() { + // 正常完成关闭时,内层 return 为已定义不可调用值必须抛 TypeError。 + assert_eq!( + eval( + "var it={next:function(){return {value:1,done:false};},return:1};\ + globalThis.r=false;\ + try{for(var v of it){break;}}\ + catch(e){globalThis.r=(e instanceof TypeError);}\ + globalThis.r" + ), + "true", + "defined non-callable inner return must throw TypeError on close" + ); +} + +#[test] +fn wrapper_return_null_is_noop() { + // 内层 return 为 null 时关闭无操作(GetMethod 步 2 返回 unused)。 + assert_eq!( + eval( + "var it={next:function(){return {value:1,done:false};},return:null};\ + var threw=false;\ + try{for(var v of it){break;}}catch(e){threw=true;}\ + threw===false" + ), + "true", + "null inner return must be a no-op on close" + ); +} + +#[test] +fn wrapper_return_getter_throw_suppressed_on_throw_close() { + // throw 完成关闭时,内层 return getter 抛错被在途异常替代(原异常胜出)。 + assert_eq!( + eval( + "var it={next:function(){return {value:1,done:false};},\ + get return(){throw new TypeError('getter');}};\ + globalThis.r=false;\ + try{for(var v of it){throw new Error('body');}}\ + catch(e){globalThis.r=(e.name==='Error'&&e.message==='body');}\ + globalThis.r" + ), + "true", + "getter throw on inner return must be suppressed when close follows a throw" + ); +} + +#[test] +fn wrapper_return_not_eagerly_bound_at_construction() { + // 构造包装器时不得触碰内层 return getter(计数时点在关闭期)。 + assert_eq!( + eval( + "var count=0;\ + var it={next:function(){return {value:1,done:false};},\ + get return(){count++;throw new TypeError('getter');}};\ + var w=Iterator.from(it);count===0" + ), + "true", + "wrapper construction must not touch the inner return getter" + ); +} + +#[test] +fn wrapper_return_getter_called_once_at_close() { + // 内层 return getter 在关闭期恰好被调用一次。 + assert_eq!( + eval( + "var count=0;\ + var it={next:function(){return {value:1,done:false};},\ + get return(){count++;return function(){return {};};}};\ + for(var v of it){break;}count===1" + ), + "true", + "inner return getter must be invoked exactly once, at close" + ); +}