From c9e1ee2e09149ec6e63a13c291bda6b55a7191c1 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 20 Sep 2026 18:07:54 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A3=B8=20return=20=E7=89=A9=E5=8C=96?= =?UTF-8?q?=20undefined=EF=BC=8C=E9=98=B2=E8=B0=83=E7=94=A8=E6=AE=8B?= =?UTF-8?q?=E7=95=99=E5=80=BC=E5=86=92=E5=85=85=E5=AE=8C=E6=88=90=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/class/emit.rs | 7 ++- .../oxide_emit/src/stmt/basic/return_stmt.rs | 8 ++- crates/oxide_vm/tests/bare_return_tests.rs | 63 +++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 crates/oxide_vm/tests/bare_return_tests.rs diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index 060737fd..62019865 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -307,10 +307,15 @@ impl Emitter { .position(|u| u.name == "@@field_keys") .map(|i| i as u8); emit_instance_fields(self, &mut field_ctx)?; + // 构造器尾裸 return 须物化 undefined:RETURN 读 rd 槽值,字段初始化器 + // 尾表达式为返回对象的调用时,该对象会漏出为构造结果。 + let undef_idx = field_ctx.add_constant(Constant::Undefined); + let undef_reg = field_ctx.alloc_reg(); + field_ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); module.insts.extend(field_ctx.insts); module.constants = field_ctx.constants; module.n_registers = field_ctx.max_regs.max(1); - module.insts.push(Inst::ret(Operand::None, 0, 0)); + module.insts.push(Inst::ret(Operand::Reg(undef_reg), 0, 0)); } module }; diff --git a/crates/oxide_emit/src/stmt/basic/return_stmt.rs b/crates/oxide_emit/src/stmt/basic/return_stmt.rs index e4619014..23f38009 100644 --- a/crates/oxide_emit/src/stmt/basic/return_stmt.rs +++ b/crates/oxide_emit/src/stmt/basic/return_stmt.rs @@ -1,6 +1,7 @@ //! return 语句 emit:`emit_return_statement` 求值返回值并生成返回指令。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; @@ -32,7 +33,12 @@ impl Emitter { if for_of_count == 0 && for_in_count == 0 { self.emit_return_try_cleanup(ctx); } - ctx.inst(Inst::ret(Operand::None, for_of_count, for_in_count)); + // 裸 return 须显式物化 undefined 为完成值:RETURN 从 rd 槽读值, + // 不物化则交付槽内残留值(如最近一次调用的结果)冒充完成值。 + let undef_idx = ctx.add_constant(Constant::Undefined); + let undef_reg = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); + ctx.inst(Inst::ret(Operand::Reg(undef_reg), for_of_count, for_in_count)); } } Ok(None) diff --git a/crates/oxide_vm/tests/bare_return_tests.rs b/crates/oxide_vm/tests/bare_return_tests.rs new file mode 100644 index 00000000..3cf36636 --- /dev/null +++ b/crates/oxide_vm/tests/bare_return_tests.rs @@ -0,0 +1,63 @@ +//! 裸 return 完成值测试:无值 return 物化 undefined,防 rd 槽残留 +//! (如最近一次调用的结果)冒充完成值泄漏;覆盖函数体臂与派生类 +//! 隐式构造器臂两条发射路径。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_types::value::JsValue; +use oxide_vm::promise::promise_settled_value; +use oxide_vm::vm::Vm; + +/// parse → compile → run,返回 VM 与顶层执行结果。 +fn run(source: &str) -> Result<(Vm, JsValue), String> { + let allocator = 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))?; + let mut vm = Vm::new(); + let result = vm.run(&Arc::new(module)).map_err(|e| format!("vm error: {}", e))?; + Ok((vm, result)) +} + +/// 断言顶层结果为真值布尔。 +fn assert_true(source: &str) { + let (_vm, result) = run(source).unwrap(); + assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}"); +} + +/// 断言 async 函数完成值为 undefined(微任务已在 run 收尾 drain)。 +fn assert_async_undefined(source: &str) { + let (_vm, result) = run(source).unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert!(obj.is_promise_obj(), "expected promise, got: {result:?}"); + let settled = promise_settled_value(obj).expect("promise should be settled after drain").1; + assert_eq!(settled, JsValue::undefined()); +} + +#[test] +fn bare_return_after_call_materializes_undefined() { + // 裸 return 前最后一次求值为调用:槽内残留值不得冒充完成值。 + assert_true("function g(){return true} function f(){if(1){g();return;}return 'END'} f() === undefined"); +} + +#[test] +fn async_bare_return_after_call_materializes_undefined() { + // async 函数体与同步函数体共用同一条裸 return 发射臂。 + assert_async_undefined("function g(){return true} async function h(){if(1){g();return;}return 'END'} h()"); +} + +#[test] +fn bare_return_escaping_for_of_materializes_undefined() { + // 裸 return 逃出 for-of:迭代器关闭计数透传臂同样物化 undefined。 + assert_true( + "function g(){return true} function f(){for(var x of [1,2]){g();return;}return 'END'} f() === undefined", + ); +} + +#[test] +fn derived_class_implicit_constructor_returns_instance() { + // 隐式派生构造器尾裸 return:字段初始化器返回对象的调用结果 + // 不得漏出为构造结果。 + assert_true("class A{} class B extends A { x = (function(){return {mark:1};})() } new B() instanceof B"); +}