From cde3417e151d6a0108db251ce3ae4ef47497964f Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 16:58:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20sloppy=20=E6=99=AE=E9=80=9A=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=B0=83=E7=94=A8=20this=20=E6=9C=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89/null=20=E7=BB=91=E5=AE=9A=E5=85=A8=E5=B1=80=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm.rs | 11 +- crates/oxide_vm/src/vm_runtime.rs | 10 +- .../oxide_vm/tests/builtins_iterator_tests.rs | 8 +- crates/oxide_vm/tests/function_tests.rs | 14 +- crates/oxide_vm/tests/this_binding_tests.rs | 160 ++++++++++++++++++ 5 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 crates/oxide_vm/tests/this_binding_tests.rs diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 334e95f..78f9e15 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1326,6 +1326,7 @@ impl Vm { let sub_n_registers = self.sub_modules[sub_idx].n_registers; let sub_param_base = self.sub_modules[sub_idx].param_base as usize; let sub_is_arrow = self.sub_modules[sub_idx].is_arrow; + let sub_is_strict = self.sub_modules[sub_idx].is_strict; // 窗口 = min(调用方活动寄存器, 存活上界);call_window=0 表示调用方全量 // (运行时发起路径 / 未编码的旧模块)。恢复按窗口回拷,active_reg_limit // 仍还原为调用方真实值(caller_active_reg_limit)。 @@ -1360,7 +1361,15 @@ impl Vm { }; self.regs[sub_param_base + i] = v; } - self.regs[254] = if sub_is_arrow { obj.captured_this() } else { this_value }; + // this 绑定:箭头函数恒用词法捕获;sloppy 普通函数 this 为 null/undefined 时 + // 替换为全局对象(ECMA-262 10.4.3);严格模式与显式方法/构造 this 原样保留。 + self.regs[254] = if sub_is_arrow { + obj.captured_this() + } else if !sub_is_strict && this_value.is_nullish() { + JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject) + } else { + this_value + }; self.regs[255] = new_target; self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode)); diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index d21f1d8..70348f8 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -381,7 +381,15 @@ impl Vm { for i in 0..sub.n_args as usize { self.regs[sub.param_base as usize + i] = args.get(i).copied().unwrap_or(JsValue::undefined()); } - self.regs[254] = if sub.is_arrow { callee_obj.captured_this() } else { receiver }; + // this 绑定:箭头函数恒用词法捕获;sloppy 普通函数 this 为 null/undefined 时 + // 替换为全局对象(ECMA-262 10.4.3);严格模式与显式 receiver 原样保留。 + self.regs[254] = if sub.is_arrow { + callee_obj.captured_this() + } else if !sub.is_strict && receiver.is_nullish() { + JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject) + } else { + receiver + }; self.regs[255] = JsValue::undefined(); for (name, reg) in &sub.builtin_reg_map { let si = self.kernel_core.perm_interner().intern(name.as_str()).0; diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index 21a4741..eae4dcc 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -656,10 +656,10 @@ fn iterator_terminal_for_each_basic() { ("var out = 0; [1, 2, 3].values().forEach(function (v) { out += v; }); out === 6", true), // 空迭代立即返回 undefined。 ("Iterator.from([]).forEach(function () {}) === undefined", true), - // 回调 this 为 undefined(规范 Call(procedure, undefined, ...))。 + // 回调 this:规范 Call(procedure, undefined) 对 sloppy 回调仍替换为全局对象。 ( "var captured; Iterator.from([1]).forEach(function () { captured = this; }); \ - captured === undefined", + captured === globalThis", true, ), ]; @@ -1055,10 +1055,10 @@ fn iterator_helper_map_filter_basic() { assert_eq!(to_str(&vm, result), expected, "for {}", src); } let bool_cases = [ - // 回调 this 为 undefined。 + // 回调 this:规范 Call(procedure, undefined) 对 sloppy 回调仍替换为全局对象。 ( "var captured; Iterator.from([1]).map(function () { captured = this; return 1; }).next(); \ - captured === undefined", + captured === globalThis", true, ), // 空迭代立即 done。 diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index 922c63c..ad9a330 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -163,21 +163,25 @@ fn fd_returns_new_array() { fn this_in_function_reads_value() { let mut vm = Vm::new(); let result = eval(&mut vm, "function f() { return this; } f()").unwrap(); - assert!(result.is_undefined(), "expected undefined in strict-mode call, got: {:?}", result); + // sloppy 普通调用 this 绑定全局对象(ECMA-262 10.4.3)。 + let global = vm.session().global_object().as_ptr() as *mut oxide_types::object::JsObject as usize; + assert_eq!(result.as_js_object_ptr() as usize, global, "expected global object, got: {:?}", result); } #[test] fn this_in_function_assign_member() { let mut vm = Vm::new(); - let err = eval(&mut vm, "function f(m) { this.message = m; } f('hello'); 1").unwrap_err(); - assert!(err.contains("TypeError"), "expected TypeError, got: {err}"); + // sloppy this 为全局对象:成员写入落到全局,不抛错。 + let result = eval(&mut vm, "function f(m) { this.message = m; } f('hello'); 1").unwrap(); + assert_eq!(result.as_int(), 1); } #[test] fn this_member_access() { let mut vm = Vm::new(); - let err = eval(&mut vm, "function f() { this.x = 42; return this.x; } f()").unwrap_err(); - assert!(err.contains("TypeError"), "expected TypeError, got: {err}"); + // sloppy this 为全局对象:读写的成员落在全局,返回写入值。 + let result = eval(&mut vm, "function f() { this.x = 42; return this.x; } f()").unwrap(); + assert_eq!(result.as_int(), 42); } #[test] diff --git a/crates/oxide_vm/tests/this_binding_tests.rs b/crates/oxide_vm/tests/this_binding_tests.rs new file mode 100644 index 0000000..d5cc707 --- /dev/null +++ b/crates/oxide_vm/tests/this_binding_tests.rs @@ -0,0 +1,160 @@ +//! sloppy 普通函数 `this` 绑定语义测试:非箭头 && 非 strict && this 为 null/undefined +//! 时替换为全局对象;箭头/严格/method/构造/显式 receiver 均不替换。 +//! 覆盖普通调用、call/apply/bind、builtin 回调、构造、生成器、async、derived 构造。 + +use oxide_compiler::compiler::Compiler; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::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))?; + vm.run(&module) +} + +/// 断言表达式结果为 `true`。 +fn assert_true(vm: &mut Vm, source: &str) { + let result = eval(vm, source).unwrap_or_else(|e| panic!("eval 失败: {} \n source: {}", e, source)); + assert_eq!(result, JsValue::bool(true), "期望 true,source: {}", source); +} + +fn global_ptr(vm: &Vm) -> usize { + vm.session().global_object().as_ptr() as *mut JsObject as usize +} + +// ── 普通调用 ── + +#[test] +fn sloppy_direct_call_binds_global_this() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "(function(){ return this; })()").unwrap(); + assert_eq!(result.as_js_object_ptr() as usize, global_ptr(&vm), "sloppy 普通调用 this 应为全局对象"); +} + +#[test] +fn strict_direct_call_keeps_undefined_this() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "(function(){ 'use strict'; return this; })()").unwrap(); + assert!(result.is_undefined(), "strict 函数普通调用 this 保持 undefined,got: {:?}", result); +} + +#[test] +fn strict_inside_sloppy_inherits_directive() { + let mut vm = Vm::new(); + // sloppy 外层调用 strict 内层:内层自身 directive 使其 this 保持 undefined。 + let result = eval( + &mut vm, + "function outer(){ return (function(){ 'use strict'; return this; })(); } outer()", + ) + .unwrap(); + assert!(result.is_undefined(), "strict 内层 this 应为 undefined,got: {:?}", result); +} + +#[test] +fn sloppy_top_level_script_this_is_global() { + let mut vm = Vm::new(); + assert_true(&mut vm, "this === globalThis"); +} + +// ── 箭头 / method / 构造 ── + +#[test] +fn arrow_keeps_lexical_this() { + let mut vm = Vm::new(); + assert_true(&mut vm, "var f = () => this; f() === globalThis"); +} + +#[test] +fn method_call_keeps_receiver() { + let mut vm = Vm::new(); + assert_true(&mut vm, "var o = { m(){ return this === o; } }; o.m()"); +} + +#[test] +fn detached_method_call_binds_global() { + let mut vm = Vm::new(); + // 方法解绑后无 receiver 调用:sloppy 函数 this 应为全局对象。 + assert_true(&mut vm, "var o = { m: function(){ return this === globalThis; } }; var m = o.m; m()"); +} + +#[test] +fn constructor_this_is_new_instance() { + let mut vm = Vm::new(); + assert_true(&mut vm, "var a = new (function(){ this.self = this; })(); a.self === a"); +} + +#[test] +fn derived_constructor_this_is_instance() { + let mut vm = Vm::new(); + assert_true( + &mut vm, + "class B {} class A extends B { constructor(){ super(); this.self = this; } } var a = new A(); a.self === a", + ); +} + +// ── call / apply / bind ── + +#[test] +fn call_apply_bind_with_nullish_this_binds_global() { + let mut vm = Vm::new(); + assert_true(&mut vm, "function f(){ return this === globalThis; } f.call(undefined)"); + assert_true(&mut vm, "function f(){ return this === globalThis; } f.call(null)"); + assert_true(&mut vm, "function f(){ return this === globalThis; } f.apply(null)"); + assert_true(&mut vm, "function f(){ return this === globalThis; } f.bind(undefined)()"); +} + +#[test] +fn explicit_object_receiver_not_replaced() { + let mut vm = Vm::new(); + assert_true(&mut vm, "var o = {}; function f(){ return this === o; } f.call(o)"); +} + +#[test] +fn strict_call_with_nullish_keeps_undefined() { + let mut vm = Vm::new(); + assert_true(&mut vm, "function f(){ 'use strict'; return this === undefined; } f.call(undefined)"); +} + +// ── builtin 回调 / 生成器 / async ── + +#[test] +fn builtin_callback_sloppy_this_binds_global() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "[1,2].map(function(){ return this === globalThis; }).join(',')").unwrap(); + let rendered = vm.lookup_str(result).unwrap_or_default(); + assert_eq!(rendered, "true,true"); +} + +#[test] +fn generator_this_binds_global() { + let mut vm = Vm::new(); + assert_true(&mut vm, "(function*(){ return this === globalThis; })().next().value"); +} + +#[test] +fn async_function_this_binds_global() { + let mut vm = Vm::new(); + // async 函数 body 同步执行到首个 await:无 await 时整个 body 立即完成。 + assert_true(&mut vm, "var ok = false; (async function(){ ok = (this === globalThis); })(); ok"); +} + +// ── this 写路径回归 ── + +#[test] +fn sloppy_this_write_lands_on_global() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(){ this.this_marker = 42; } f(); globalThis.this_marker").unwrap(); + assert_eq!(result.as_int(), 42, "sloppy `this.x =` 应写入全局对象"); +} + +#[test] +fn strict_this_write_throws() { + let mut vm = Vm::new(); + let err = eval(&mut vm, "function f(){ 'use strict'; this.strict_marker = 1; } f()").unwrap_err(); + assert!( + err.contains("TypeError"), + "strict 函数 this 为 undefined,写属性应抛 TypeError,got: {err}" + ); +}