From 659bb272c417785e92e7070fe6fafe425434e0a9 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 18 Oct 2026 08:23:10 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=98=E5=8E=9F=E4=BB=A3=E7=90=86=20g?= =?UTF-8?q?OPD=20=E4=B8=8D=E5=8F=98=E9=87=8F=E6=A3=80=E6=9F=A5=E7=9A=84=20?= =?UTF-8?q?writable=20=E4=BD=8D=E6=96=B9=E5=90=91=E5=B9=B6=E8=A1=A5?= =?UTF-8?q?=E5=80=BC=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 ++-- Cargo.toml | 2 +- crates/oxide_builtins/src/object.rs | 186 ++++++++++-------- .../tests/proxy_gopd_invariant_tests.rs | 123 +++++++++++- 4 files changed, 241 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7356917d..db264ae4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.12" +version = "0.20.13" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.12" +version = "0.20.13" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.12" +version = "0.20.13" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.12" +version = "0.20.13" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.12" +version = "0.20.13" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.12" +version = "0.20.13" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.12" +version = "0.20.13" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.12" +version = "0.20.13" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.12" +version = "0.20.13" [[package]] name = "oxide_parser" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.12" +version = "0.20.13" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.12" +version = "0.20.13" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.12" +version = "0.20.13" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.12" +version = "0.20.13" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 6e6a7979..a9b85d0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.12" +version = "0.20.13" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index cbf3f7a9..c7db05a6 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1439,7 +1439,8 @@ fn own_descriptor_of(vm: &mut H, obj: &JsObject, key_si: u32) -> Resu // 调用;trap 缺失落穿 target 的自身描述符(target 自身为代理时经递归再 // 路由);trap 返 undefined 表示无属性(target 自身属性不可配置或 target // 不可扩展时抛 TypeError);trap 返非对象按规范抛 TypeError;trap 返对象 - // 时与 target 自身描述符做不变量交叉检查(target 不可扩展时)。 + // 时与 target 自身描述符做不变量交叉检查(targetDesc 缺失仅在 target + // 不可扩展时抛;targetDesc 不可配置时全量交叉检查与可扩展性无关)。 if obj.is_proxy_obj() { let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target; // SAFETY: target 由构造器校验为非空对象指针。 @@ -1600,12 +1601,13 @@ fn proxy_is_extensible(vm: &mut H, obj: &JsObject) -> Result( vm: &mut H, target: &JsObject, key_si: u32, result_desc: JsValue, ) -> Result<(), JsValue> { @@ -1617,90 +1619,102 @@ fn proxy_gopd_invariant_check( let value_si = vm.perm_intern("value"); let extensible = proxy_is_extensible(vm, target).map_err(|msg| crate::error::create_from_text(vm, &msg))?; let target_desc = own_descriptor_of(vm, target, key_si)?; - // IsCompatiblePropertyDescriptor:仅 target 不可扩展时执行。 - if !extensible { - let Some(td) = target_desc else { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a property that does not exist on a non-extensible target", - )); - }; - if desc_field_bool(vm, td, cfg_si)? { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a descriptor for a configurable target property of a non-extensible target", - )); - } - if desc_field_bool(vm, result_desc, cfg_si)? { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a configurable descriptor for a non-extensible target property", - )); - } - if desc_field_bool(vm, result_desc, enum_si)? != desc_field_bool(vm, td, enum_si)? { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a different enumerable flag for a non-configurable target property", - )); - } - let result_get = own_field(vm, result_desc, get_si)?; - let target_get = own_field(vm, td, get_si)?; - if target_get.is_some() { - if result_get.is_none() || result_get != target_get { + // IsCompatiblePropertyDescriptor(三参形态):targetDesc 缺失仅在 target + // 不可扩展时抛;targetDesc 不可配置时执行全量交叉检查(与可扩展性无关); + // targetDesc 可配置时无约束。 + match target_desc { + None => { + if !extensible { return Err(crate::error::create_type_error( vm, - "getOwnPropertyDescriptor trap reported a different getter for a non-configurable target accessor", + "getOwnPropertyDescriptor trap reported a property that does not exist on a non-extensible target", )); } - let target_set = own_field(vm, td, set_si)?; - let result_set = own_field(vm, result_desc, set_si)?; - if target_set.is_some() { - if result_set.is_none() || result_set != target_set { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a different setter for a non-configurable target accessor", - )); + } + Some(td) => { + if !desc_field_bool(vm, td, cfg_si)? { + // targetDesc 不可配置:result 可配置抛。 + if let Some(cfg) = own_field(vm, result_desc, cfg_si)? { + if oxide_runtime_api::to_boolean(cfg) { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a configurable descriptor for a non-configurable target property", + )); + } } - } else if result_set.is_some() { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a setter for a target accessor without setter", - )); - } - } else { - if result_get.is_some() { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a getter for a non-configurable target data property", - )); - } - if own_field(vm, result_desc, set_si)?.is_some() { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a setter for a non-configurable target data property", - )); - } - let target_writable = desc_field_bool(vm, td, writable_si)?; - let result_writable = desc_field_bool(vm, result_desc, writable_si)?; - if !target_writable && result_writable { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a writable descriptor for a non-writable target property", - )); - } - if !result_writable && target_writable { - let result_value = own_field(vm, result_desc, value_si)?.unwrap_or(JsValue::undefined()); - let target_value = own_field(vm, td, value_si)?.unwrap_or(JsValue::undefined()); - if !oxide_runtime_api::same_value(result_value, target_value) { - return Err(crate::error::create_type_error( - vm, - "getOwnPropertyDescriptor trap reported a non-writable descriptor with a different value for a writable target property", - )); + // targetDesc 不可配置:enumerable 须一致(字段缺失不检查)。 + if let Some(en) = own_field(vm, result_desc, enum_si)? { + if oxide_runtime_api::to_boolean(en) != desc_field_bool(vm, td, enum_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different enumerable flag for a non-configurable target property", + )); + } + } + let result_get = own_field(vm, result_desc, get_si)?; + let target_get = own_field(vm, td, get_si)?; + if target_get.is_some() { + if result_get.is_none() || result_get != target_get { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different getter for a non-configurable target accessor", + )); + } + let target_set = own_field(vm, td, set_si)?; + let result_set = own_field(vm, result_desc, set_si)?; + if target_set.is_some() { + if result_set.is_none() || result_set != target_set { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different setter for a non-configurable target accessor", + )); + } + } else if result_set.is_some() { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a setter for a target accessor without setter", + )); + } + } else { + if result_get.is_some() { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a getter for a non-configurable target data property", + )); + } + if own_field(vm, result_desc, set_si)?.is_some() { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a setter for a non-configurable target data property", + )); + } + // target 不可写:result 不得放宽为可写,result 有值字段时 + // 值须 SameValue。 + if !desc_field_bool(vm, td, writable_si)? { + if let Some(w) = own_field(vm, result_desc, writable_si)? { + if oxide_runtime_api::to_boolean(w) { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a writable descriptor for a non-writable target property", + )); + } + } + if let Some(v) = own_field(vm, result_desc, value_si)? { + let target_value = own_field(vm, td, value_si)?.unwrap_or(JsValue::undefined()); + if !oxide_runtime_api::same_value(v, target_value) { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different value for a non-writable target property", + )); + } + } + } } } } } - // 结果描述符不可配置:target 自身属性必须存在且不可配置(与可扩展性无关)。 + // 结果描述符不可配置:target 自身属性必须存在且不可配置;result 不可写且 + // target 可写无条件抛(无值相等豁免)。 if !desc_field_bool(vm, result_desc, cfg_si)? { match target_desc { None => { @@ -1716,6 +1730,14 @@ fn proxy_gopd_invariant_check( "getOwnPropertyDescriptor trap reported a non-configurable descriptor for a configurable target property", )); } + if let Some(w) = own_field(vm, result_desc, writable_si)? { + if !oxide_runtime_api::to_boolean(w) && desc_field_bool(vm, td, writable_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a non-writable descriptor for a writable target property", + )); + } + } } } } diff --git a/crates/oxide_vm/tests/proxy_gopd_invariant_tests.rs b/crates/oxide_vm/tests/proxy_gopd_invariant_tests.rs index db5c0d3a..61d86aa6 100644 --- a/crates/oxide_vm/tests/proxy_gopd_invariant_tests.rs +++ b/crates/oxide_vm/tests/proxy_gopd_invariant_tests.rs @@ -11,8 +11,10 @@ fn eval(vm: &mut Vm, source: &str) -> Result { vm.run(&Arc::new(module)) } -// gOPD 不变量检查(writable 位方向与 value 一致性):target 不可写加 result -// 可写时抛 TypeError(规范 ValidateAndApplyPropertyDescriptor 步 5.5.1 口径)。 +// gOPD 不变量检查(writable 位方向与值一致性):target 不可写且 result 可写时 +// 抛 TypeError(规范 ValidateAndApplyPropertyDescriptor 步 5e.i,与可扩展性 +// 无关);result 不可配置且不可写、target 可写时抛 TypeError(规范 +// [[GetOwnProperty]] 步 16b 形态,无值相等豁免)。 #[test] fn gopd_trap_writable_relaxation_throws() { let mut vm = Vm::new(); @@ -33,7 +35,28 @@ fn gopd_trap_writable_relaxation_throws() { assert_eq!(vm.lookup_str(result).unwrap_or_default(), "TypeError"); } -// target 可配置时放宽:writable 交叉检查不执行,result 可写不抛。 +// 5e.i 可扩展 target 情形:target 可扩展、targetDesc 不可写且不可配置、 +// result 可写时同样抛 TypeError。 +#[test] +fn gopd_trap_writable_relaxation_extensible_target_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(() => { \ + var t = { a: 1 }; \ + Object.defineProperty(t, 'a', { value: 1, writable: false, configurable: false, enumerable: false }); \ + var p = new Proxy(t, { getOwnPropertyDescriptor: function (tt, k) { \ + return { value: 1, writable: true, enumerable: false, configurable: false }; \ + } }); \ + try { Object.getOwnPropertyDescriptor(p, 'a'); return 'no-throw'; } \ + catch (e) { return e.name; } \ + })()", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "TypeError"); +} + +// targetDesc 可配置时放宽:交叉检查不执行,result 可写且可配置不抛。 #[test] fn gopd_trap_configurable_target_relaxed() { let mut vm = Vm::new(); @@ -53,7 +76,71 @@ fn gopd_trap_configurable_target_relaxed() { assert_eq!(vm.lookup_str(result).unwrap_or_default(), "ok"); } -// value 一致性:target 可写加 result 不可写且值不同抛 TypeError。 +// target 不可扩展且 targetDesc 可配置、result 可配置:规范对 current 可配置 +// 无约束,不抛。 +#[test] +fn gopd_trap_nonextensible_configurable_target_relaxed() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(() => { \ + var t = { a: 1 }; \ + Object.defineProperty(t, 'a', { value: 1, writable: false, configurable: true, enumerable: false }); \ + Object.preventExtensions(t); \ + var p = new Proxy(t, { getOwnPropertyDescriptor: function (tt, k) { \ + return { value: 1, writable: true, enumerable: false, configurable: true }; \ + } }); \ + try { Object.getOwnPropertyDescriptor(p, 'a'); return 'ok'; } \ + catch (e) { return e.name; } \ + })()", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "ok"); +} + +// 步 16b 方向:result 不可配置且不可写、target 可写时抛 TypeError(与值无关)。 +#[test] +fn gopd_trap_nonwritable_result_writable_target_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(() => { \ + var t = { a: 1 }; \ + Object.defineProperty(t, 'a', { value: 1, writable: true, configurable: false, enumerable: false }); \ + var p = new Proxy(t, { getOwnPropertyDescriptor: function (tt, k) { \ + return { configurable: false, writable: false }; \ + } }); \ + try { Object.getOwnPropertyDescriptor(p, 'a'); return 'no-throw'; } \ + catch (e) { return e.name; } \ + })()", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "TypeError"); +} + +// 步 16b 无值相等豁免:target 可写、result 不可配置且不可写且值 SameValue +// 时同样抛 TypeError。 +#[test] +fn gopd_trap_nonwritable_result_same_value_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(() => { \ + var t = { a: 1 }; \ + Object.defineProperty(t, 'a', { value: 1, writable: true, configurable: false, enumerable: false }); \ + Object.preventExtensions(t); \ + var p = new Proxy(t, { getOwnPropertyDescriptor: function (tt, k) { \ + return { value: 1, writable: false, enumerable: false, configurable: false }; \ + } }); \ + try { Object.getOwnPropertyDescriptor(p, 'a'); return 'no-throw'; } \ + catch (e) { return e.name; } \ + })()", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "TypeError"); +} + +// 步 16b 方向:target 可写、result 不可写且值不一致时抛 TypeError。 #[test] fn gopd_trap_value_mismatch_throws() { let mut vm = Vm::new(); @@ -74,16 +161,36 @@ fn gopd_trap_value_mismatch_throws() { assert_eq!(vm.lookup_str(result).unwrap_or_default(), "TypeError"); } -// value 一致性:target 可写加 result 不可写且值 SameValue 不抛。 +// 5e.ii 值检查:target 不可写且 result 有值字段、值不一致时抛 TypeError +// (target 不可写时步 16b 不触发,此情形独立钉 5e.ii)。 #[test] -fn gopd_trap_value_same_ok() { +fn gopd_trap_nonwritable_target_value_mismatch_throws() { let mut vm = Vm::new(); let result = eval( &mut vm, "(() => { \ var t = { a: 1 }; \ - Object.defineProperty(t, 'a', { writable: true, configurable: false, enumerable: false }); \ - Object.preventExtensions(t); \ + Object.defineProperty(t, 'a', { value: 1, writable: false, configurable: false, enumerable: false }); \ + var p = new Proxy(t, { getOwnPropertyDescriptor: function (tt, k) { \ + return { value: 2, writable: false, enumerable: false, configurable: false }; \ + } }); \ + try { Object.getOwnPropertyDescriptor(p, 'a'); return 'no-throw'; } \ + catch (e) { return e.name; } \ + })()", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "TypeError"); +} + +// 5e.ii 值检查通过侧:target 不可写且 result 有值字段、值 SameValue 时不抛。 +#[test] +fn gopd_trap_nonwritable_target_value_same_ok() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(() => { \ + var t = { a: 1 }; \ + Object.defineProperty(t, 'a', { value: 1, writable: false, configurable: false, enumerable: false }); \ var p = new Proxy(t, { getOwnPropertyDescriptor: function (tt, k) { \ return { value: 1, writable: false, enumerable: false, configurable: false }; \ } }); \