From c59ad64e7d3902f526cf8856acec200dcf8bd1d6 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 02:29:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=A8=E5=B1=80=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=99=A8=E4=B8=8E=E5=86=85=E7=BD=AE=E5=AF=B9=E8=B1=A1=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E6=94=B9=E9=9D=9E=E6=9E=9A=E4=B8=BE=E4=B8=8E=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/kernel.rs | 14 ++ crates/oxide_vm/src/bindings/bind_bigint.rs | 9 +- crates/oxide_vm/src/bindings/bind_error.rs | 6 + crates/oxide_vm/src/bindings/bind_math.rs | 9 +- crates/oxide_vm/src/bindings/bind_string.rs | 5 +- crates/oxide_vm/src/bindings/mod.rs | 8 ++ .../oxide_vm/tests/global_prop_desc_tests.rs | 123 ++++++++++++++++++ 7 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 crates/oxide_vm/tests/global_prop_desc_tests.rs diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 1a69851b..88e77abc 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -513,17 +513,31 @@ impl KernelSession { let si_undef = core.perm_interner.intern("undefined").0; let si_infinity = core.perm_interner.intern("Infinity").0; + // 全局三常量描述符:{ writable:false, enumerable:false, configurable:false }, + // 规范要求不可写不可枚举不可配置,否则会泄漏进 Object.keys(globalThis)。 let nan_shape = core.shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan); global_obj.set_shape_id(nan_shape); global_obj.ensure_hash_props().push(JsValue::float(f64::NAN)); + global_obj.set_data_meta( + global_obj.prop_vec_len().saturating_sub(1) as u32, + oxide_types::object::PropAttributes::new(false, false, false), + ); let undef_shape = core.shape_forge.make_shape(nan_shape, si_undef); global_obj.set_shape_id(undef_shape); global_obj.ensure_hash_props().push(JsValue::undefined()); + global_obj.set_data_meta( + global_obj.prop_vec_len().saturating_sub(1) as u32, + oxide_types::object::PropAttributes::new(false, false, false), + ); let inf_shape = core.shape_forge.make_shape(undef_shape, si_infinity); global_obj.set_shape_id(inf_shape); global_obj.ensure_hash_props().push(JsValue::float(f64::INFINITY)); + global_obj.set_data_meta( + global_obj.prop_vec_len().saturating_sub(1) as u32, + oxide_types::object::PropAttributes::new(false, false, false), + ); P::new(global_obj) } diff --git a/crates/oxide_vm/src/bindings/bind_bigint.rs b/crates/oxide_vm/src/bindings/bind_bigint.rs index dd9f79e5..fe173ccc 100644 --- a/crates/oxide_vm/src/bindings/bind_bigint.rs +++ b/crates/oxide_vm/src/bindings/bind_bigint.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use crate::bindings::{apply_binding_table, bind_global_value, bind_well_known_data_property, configure_native_constructor}; +use crate::bindings::{ + apply_binding_table, bind_global_value, bind_well_known_data_property, configure_native_constructor, +}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; @@ -58,9 +60,4 @@ pub fn bind_bigint(core: &Arc, session: &KernelSession, global: &mut bind_well_known_data_property(core, proto, 9, tag_value, PropAttributes::new(false, false, true)); bind_global_value(core, global, "BigInt", JsValue::from_js_object(ctor_ptr)); - // 全局 BigInt 属性描述符:{ writable: true, enumerable: false, configurable: true }。 - let bigint_si = core.perm_interner().intern("BigInt").0; - if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), bigint_si) { - global.set_data_meta(pos, PropAttributes::new(true, false, true)); - } } diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index 0fae2712..647be3c3 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -50,6 +50,9 @@ fn bind_error_subtype_constructor( let global_shape = sh.make_shape(global.shape_id(), name_si); global.set_shape_id(global_shape); global.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr)); + // 全局子类型构造器槽位非枚举(规范全局构造器描述符约定)。 + let global_pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(global_pos, PropAttributes::new(true, false, true)); global.bump_generation(); } @@ -77,6 +80,9 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut let err_val = JsValue::from_js_object(session.builtin_world().error_constructor.as_ptr() as *mut JsObject); global.set_shape_id(err_shape); global.ensure_hash_props().push(err_val); + // 全局 Error 构造器槽位非枚举(规范 { writable:true, enumerable:false, configurable:true })。 + let err_pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(err_pos, PropAttributes::new(true, false, true)); global.bump_generation(); bind_error_subtype_constructor( diff --git a/crates/oxide_vm/src/bindings/bind_math.rs b/crates/oxide_vm/src/bindings/bind_math.rs index 1ffc8c99..eb3f7192 100644 --- a/crates/oxide_vm/src/bindings/bind_math.rs +++ b/crates/oxide_vm/src/bindings/bind_math.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use oxide_kernel::bind_methods; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; /// 把 Math 单例对象及其方法(abs/floor/max/random 等)绑定到 global。 @@ -70,6 +70,10 @@ pub fn bind_math(core: &Arc, session: &KernelSession, global: &mut J let sh_c = core.shape_forge().as_ref().make_shape(math.shape_id(), si); math.set_shape_id(sh_c); math.ensure_hash_props().push(JsValue::float(val)); + // Math 常量描述符:{ writable:false, enumerable:false, configurable:false }, + // 保证 Object.keys(Math) 为空(与 Number 常量同款收口)。 + let pos = math.prop_vec_len().saturating_sub(1) as u32; + math.set_data_meta(pos, PropAttributes::new(false, false, false)); } let si_m = core.perm_interner().intern("Math").0; @@ -77,5 +81,8 @@ pub fn bind_math(core: &Arc, session: &KernelSession, global: &mut J let m_val = JsValue::from_js_object(session.builtin_world().math_object.as_ptr() as *mut JsObject); global.set_shape_id(m_shape); global.ensure_hash_props().push(m_val); + // 全局 Math 槽位非枚举(规范全局命名空间对象属性均 enumerable:false)。 + let pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(pos, PropAttributes::new(true, false, true)); global.bump_generation(); } diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index 54c5f12c..1d1d51db 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use crate::bindings::{apply_binding_table, configure_native_constructor}; use oxide_kernel::builtin::StringMethods; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; /// 把 String 构造器与原型方法绑定到 global。 @@ -77,5 +77,8 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut let str_val = JsValue::from_js_object(session.builtin_world().string_constructor.as_ptr() as *mut JsObject); global.set_shape_id(str_shape); global.ensure_hash_props().push(str_val); + // 全局构造器槽位非枚举(规范 { writable:true, enumerable:false, configurable:true })。 + let pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(pos, PropAttributes::new(true, false, true)); global.bump_generation(); } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 3b3b025c..e86c8695 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -74,6 +74,10 @@ macro_rules! bind_constructor { } else { $global.push_prop(val); } + // 全局构造器槽位:规范描述符 { writable:true, enumerable:false, configurable:true }, + // 不设 meta 时默认全枚举会泄漏进 Object.keys(globalThis) / for-in。 + let global_pos = $global.prop_vec_len().saturating_sub(1) as u32; + $global.set_data_meta(global_pos, oxide_types::object::PropAttributes::new(true, false, true)); let ctor = unsafe { &mut *$ctor_ptr }; let ptr: *const () = ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (); ctor.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(ptr) })); @@ -346,6 +350,10 @@ pub(crate) fn bind_global_value(core: &Arc, global: &mut JsObject, n let shape = core.shape_forge().make_shape(global.shape_id(), si); global.set_shape_id(shape); global.ensure_hash_props().push(value); + // 内置全局值统一非枚举:描述符 { writable:true, enumerable:false, configurable:true }, + // 与构造器/命名空间对象(Math/JSON/Reflect/Temporal 等)规范一致。 + let pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(pos, PropAttributes::new(true, false, true)); global.bump_generation(); } diff --git a/crates/oxide_vm/tests/global_prop_desc_tests.rs b/crates/oxide_vm/tests/global_prop_desc_tests.rs new file mode 100644 index 00000000..efd05779 --- /dev/null +++ b/crates/oxide_vm/tests/global_prop_desc_tests.rs @@ -0,0 +1,123 @@ +//! 内置全局属性描述符测试:规范要求全局构造器/命名空间对象属性 +//! enumerable=false(不泄漏进 Object.keys(globalThis) / for-in), +//! 全局 NaN/undefined/Infinity 三常量不可写不可枚举不可配置。 + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval_truthy(source: &str) { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse"); + let module = Compiler::new().compile(&program).expect("compile"); + let mut vm = Vm::new(); + let result = vm.run(&module).expect("run"); + assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}"); +} + +#[test] +fn global_constructors_are_non_enumerable() { + for name in [ + "Array", "Object", "Math", "JSON", "Reflect", "Number", "String", "Date", "Map", "Set", "Error", "Promise", + "BigInt", + ] { + let src = format!("Object.getOwnPropertyDescriptor(globalThis, '{name}').enumerable === false"); + eval_truthy(&src); + } +} + +#[test] +fn global_constructors_descriptor_full_attributes() { + eval_truthy( + "var d = Object.getOwnPropertyDescriptor(globalThis, 'Array'); \ + d.writable === true && d.enumerable === false && d.configurable === true", + ); + eval_truthy( + "var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \ + d.writable === true && d.enumerable === false && d.configurable === true", + ); +} + +#[test] +fn global_this_is_non_enumerable_data_property() { + eval_truthy( + "var d = Object.getOwnPropertyDescriptor(globalThis, 'globalThis'); \ + d.writable === true && d.enumerable === false && d.configurable === true", + ); +} + +#[test] +fn keys_of_global_this_exclude_builtins() { + eval_truthy("Object.keys(globalThis).indexOf('Math') === -1"); + eval_truthy("Object.keys(globalThis).indexOf('Array') === -1"); + eval_truthy("Object.keys(globalThis).indexOf('NaN') === -1"); + // 用户属性仍可枚举:内置属性收口不影响用户写入。 + eval_truthy("var x = 1; Object.keys(globalThis).indexOf('x') >= 0"); +} + +#[test] +fn for_in_global_this_excludes_builtins() { + eval_truthy("var found = false; for (var k in globalThis) { if (k === 'Math') found = true; } found === false"); +} + +#[test] +fn nan_undefined_infinity_are_non_writable_non_configurable() { + for name in ["NaN", "undefined", "Infinity"] { + let src = format!( + "var d = Object.getOwnPropertyDescriptor(globalThis, '{name}'); \ + d.writable === false && d.enumerable === false && d.configurable === false" + ); + eval_truthy(&src); + } + eval_truthy("isNaN(globalThis.NaN) === true"); + eval_truthy("globalThis.undefined === undefined"); + eval_truthy("globalThis.Infinity === Infinity"); +} + +#[test] +fn math_constants_non_enumerable() { + eval_truthy("Object.getOwnPropertyDescriptor(Math, 'PI').enumerable === false"); + eval_truthy("Object.getOwnPropertyDescriptor(Math, 'E').writable === false"); + eval_truthy("Object.getOwnPropertyDescriptor(Math, 'PI').configurable === false"); + eval_truthy("Object.keys(Math).length === 0"); + eval_truthy("Math.PI === 3.141592653589793"); +} + +#[test] +fn math_namespace_object_is_non_enumerable_on_global() { + eval_truthy("Object.keys(globalThis).indexOf('Math') === -1"); +} + +#[test] +fn global_functions_non_enumerable() { + for name in [ + "parseInt", + "parseFloat", + "isNaN", + "isFinite", + "encodeURI", + "decodeURI", + "escape", + "unescape", + ] { + let src = format!("Object.getOwnPropertyDescriptor(globalThis, '{name}').enumerable === false"); + eval_truthy(&src); + } + eval_truthy("Object.keys(globalThis).indexOf('parseInt') === -1"); +} + +#[test] +fn user_global_properties_remain_enumerable() { + eval_truthy("var x = 1; Object.getOwnPropertyDescriptor(globalThis, 'x').enumerable === true"); + eval_truthy("globalThis.y = 2; Object.getOwnPropertyDescriptor(globalThis, 'y').enumerable === true"); +} + +#[test] +fn builtin_globals_remain_readable() { + eval_truthy("Array.isArray([]) === true"); + eval_truthy("JSON.stringify({a:1}) === '{\"a\":1}'"); + eval_truthy("Math.abs(-3) === 3"); + eval_truthy("typeof parseInt === 'function'"); + eval_truthy("Reflect.has({a:1}, 'a') === true"); + eval_truthy("Number('42') === 42"); +}