diff --git a/Cargo.lock b/Cargo.lock index 37563a62..c44c82ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.23" +version = "0.8.24" dependencies = [ "chrono", "fast-float", @@ -1120,7 +1120,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.23" +version = "0.8.24" dependencies = [ "num-bigint", "oxide_log", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.23" +version = "0.8.24" dependencies = [ "ansi_term", "bumpalo", @@ -1159,7 +1159,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.23" +version = "0.8.24" dependencies = [ "lru", "oxide_bytecode", @@ -1168,7 +1168,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1187,7 +1187,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1201,7 +1201,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.23" +version = "0.8.24" dependencies = [ "num-bigint", "oxc_allocator", @@ -1216,7 +1216,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.23" +version = "0.8.24" dependencies = [ "bumpalo", "dashmap", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.23" +version = "0.8.24" dependencies = [ "tracing", "tracing-appender", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1276,7 +1276,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1292,7 +1292,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.23" +version = "0.8.24" dependencies = [ "num-bigint", "num-traits", @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.23" +version = "0.8.24" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.23" +version = "0.8.24" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.23" +version = "0.8.24" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 215196de..53069c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.23" +version = "0.8.24" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 423880a3..88fb782d 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -304,8 +304,9 @@ impl Vm { /// 定义全局 var 绑定数据属性:rd=全局对象,a=值,b=键。 /// /// 描述符语义(CreateGlobalVarBinding 写点,对照 node 锚定): - /// - 已有 accessor 属性(可配置或不可配置):零修改返回——var 声明写点 - /// 不覆写访问器(两模式均不抛,值与描述符保持原样)。 + /// - 已有 accessor 属性(可配置或不可配置):走 Set 语义——有 setter 以 + /// 全局对象为 receiver 调用之;无 setter strict 抛 TypeError、sloppy 静默 + /// no-op(值与描述符均保持原样)。 /// - 已有数据属性且不可写(可配置或不可配置):strict 抛 TypeError、 /// sloppy 静默 no-op(规范不更新既有数据描述符,writable/enumerable/ /// configurable 与值均保持原样)。 @@ -324,14 +325,22 @@ impl Vm { let prop_name_si = self.property_key_si(self.regs[b])?; let value = self.regs[a]; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; - // 已有属性:CreateGlobalVarBinding 不改既有描述符——accessor 形零修改 - // (不覆写、不抛);数据属性不可写时 strict 抛 TypeError、sloppy 静默 - // no-op;可写数据属性保 enumerable/configurable 仅更新值。 + // 已有属性:accessor 形走 Set 语义(有 setter 调用之、无 setter strict + // 抛、sloppy 静默 no-op);数据属性不可写时 strict 抛 TypeError、sloppy + // 静默 no-op;可写数据属性保 enumerable/configurable 仅更新值。 if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { if let Some(current) = obj.prop_meta_at(pos) { - // accessor 形:var 声明写点不覆写访问器,两模式均零修改。 + // accessor 形:有 setter 以全局对象为 receiver 调用之;无 setter + // strict 抛 TypeError、sloppy 静默 no-op(值与描述符均保持原样)。 if current.is_accessor { - return Ok(()); + if current.set.is_undefined() { + return if self.current_strict() { + self.raise_error_kind("TypeError", "property has no setter") + } else { + Ok(()) + }; + } + return self.call_or_push_setter(current.set, obj_val, value, false); } // 既有数据属性不可写(可配置与不可配置同口径):strict 抛 // TypeError、sloppy 静默 no-op。 @@ -360,9 +369,10 @@ impl Vm { /// eval 脚本 var/函数声明的属性描述符)。 /// /// # 步骤 - /// 1. 属性已存在:CreateGlobalVarBinding 不改既有描述符——accessor 形零修改 - /// (不覆写、不抛);数据属性不可写时 strict 抛 TypeError、sloppy 静默 - /// no-op;可写数据属性保原描述符仅更新值。 + /// 1. 属性已存在:accessor 形走 Set 语义(有 setter 以全局对象为 receiver + /// 调用之;无 setter strict 抛 TypeError、sloppy 静默 no-op);数据属性 + /// 不可写时 strict 抛 TypeError、sloppy 静默 no-op;可写数据属性保原 + /// 描述符仅更新值。 /// 2. 属性缺失:新建可写/可枚举/可配置属性;全局对象不可扩展 → TypeError /// (两模式均抛,CreateGlobalVarBinding 语义)。 /// @@ -380,14 +390,23 @@ impl Vm { let global_ptr = self.session.global_object().as_ptr() as *mut JsObject; // SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。 let obj = unsafe { &mut *global_ptr }; - // 属性已存在:CreateGlobalVarBinding 不改既有描述符——accessor 形零修改 - // (不覆写、不抛);数据属性不可写时 strict 抛 TypeError、sloppy 静默 - // no-op;可写数据属性保原描述符仅更新值。 + // 属性已存在:accessor 形走 Set 语义(有 setter 调用之、无 setter + // strict 抛、sloppy 静默 no-op);数据属性不可写时 strict 抛 TypeError、 + // sloppy 静默 no-op;可写数据属性保原描述符仅更新值。 if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), si) { if let Some(current) = obj.prop_meta_at(pos) { - // accessor 形:var 声明写点不覆写访问器,两模式均零修改。 + // accessor 形:有 setter 以全局对象为 receiver 调用之;无 setter + // strict 抛 TypeError、sloppy 静默 no-op(值与描述符均保持原样)。 if current.is_accessor { - return Ok(()); + if current.set.is_undefined() { + return if self.current_strict() { + self.raise_error_kind("TypeError", "property has no setter") + } else { + Ok(()) + }; + } + let receiver = JsValue::from_js_object(global_ptr); + return self.call_or_push_setter(current.set, receiver, value, false); } // 既有数据属性不可写:strict 抛 TypeError、sloppy 静默 no-op。 if !current.attributes.writable() { diff --git a/crates/oxide_vm/tests/global_var_binding_tests.rs b/crates/oxide_vm/tests/global_var_binding_tests.rs index 471724f3..d82ff8ce 100644 --- a/crates/oxide_vm/tests/global_var_binding_tests.rs +++ b/crates/oxide_vm/tests/global_var_binding_tests.rs @@ -637,10 +637,11 @@ fn strict_var_init_on_builtin_math_updates_value() { } #[test] -fn var_init_on_accessor_property_does_not_overwrite() { - // accessor 形:var 声明写点不覆写访问器,两模式均零修改(描述符保持原样)。 - // 既有 accessor 须先于 GDI 存在,分两阶段。写面锚定:globalThis 反射见 - // 访问器原值(getter 返回 1),描述符保持 accessor 形(get 在、无 value 面)。 +fn var_init_on_accessor_property_no_setter() { + // accessor 形(无 setter):var 声明写点走 Set 语义——sloppy 静默 no-op + // (值与描述符均保持原样,getter 返回值不变、描述符保持 accessor 形); + // strict 抛 TypeError(无 setter 可路由)。既有 accessor 须先于 GDI 存在, + // 分两阶段。 assert_two_phases_truthy( "Object.defineProperty(globalThis, 'acc', {get() { return 1; }, configurable: true})", "var acc = 2; globalThis.acc === 1", @@ -650,9 +651,42 @@ fn var_init_on_accessor_property_does_not_overwrite() { "var acc2 = 2; var d = Object.getOwnPropertyDescriptor(globalThis, 'acc2'); \ typeof d.get === 'function' && d.value === undefined", ); - // strict 同形:accessor 零修改不抛。 - assert_two_phases_truthy( + // strict:无 setter 抛 TypeError。 + let err = phase2_err_text( "Object.defineProperty(globalThis, 'acc3', {get() { return 1; }, configurable: true})", - "'use strict'; var acc3 = 2; globalThis.acc3 === 1", + "'use strict'; var acc3 = 2;", + ); + assert!(err.contains("TypeError"), "应抛 TypeError,实际: {err}"); +} + +#[test] +fn var_init_on_accessor_property_with_setter_calls_setter() { + // accessor 形(有 setter):var 声明写点以全局对象为 receiver 调用 setter, + // 两模式均不抛,描述符保持 accessor 形(getter 返回值不变)。 + assert_two_phases_truthy( + "Object.defineProperty(globalThis, 'accs', {get() { return 1; }, \ + set(v) { globalThis.setterSaw = v; }, configurable: true}); true", + "var accs = 2; globalThis.setterSaw === 2 && globalThis.accs === 1", + ); + // strict 同形:setter 被调用、不抛。 + assert_two_phases_truthy( + "Object.defineProperty(globalThis, 'accs2', {get() { return 1; }, \ + set(v) { globalThis.setterSaw2 = v; }, configurable: true}); true", + "'use strict'; var accs2 = 2; globalThis.setterSaw2 === 2 && globalThis.accs2 === 1", ); } + +#[test] +fn var_init_on_nonconfigurable_accessor_no_setter() { + // 不可配置 accessor 形(无 setter):与可配置形同口径——sloppy 静默 no-op、 + // strict 抛 TypeError(值与描述符均保持原样)。 + assert_two_phases_truthy( + "Object.defineProperty(globalThis, 'accnc', {get() { return 1; }, configurable: false})", + "var accnc = 2; globalThis.accnc === 1", + ); + let err = phase2_err_text( + "Object.defineProperty(globalThis, 'accnc2', {get() { return 1; }, configurable: false})", + "'use strict'; var accnc2 = 2;", + ); + assert!(err.contains("TypeError"), "应抛 TypeError,实际: {err}"); +}