fix: 全局构造器与内置对象属性改非枚举与规范对齐

This commit is contained in:
2026-08-16 02:29:42 +08:00
parent 355b0233f0
commit 06441e09ff
7 changed files with 166 additions and 8 deletions
+14
View File
@@ -513,17 +513,31 @@ impl KernelSession {
let si_undef = core.perm_interner.intern("undefined").0; let si_undef = core.perm_interner.intern("undefined").0;
let si_infinity = core.perm_interner.intern("Infinity").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); let nan_shape = core.shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan);
global_obj.set_shape_id(nan_shape); global_obj.set_shape_id(nan_shape);
global_obj.ensure_hash_props().push(JsValue::float(f64::NAN)); 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); let undef_shape = core.shape_forge.make_shape(nan_shape, si_undef);
global_obj.set_shape_id(undef_shape); global_obj.set_shape_id(undef_shape);
global_obj.ensure_hash_props().push(JsValue::undefined()); 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); let inf_shape = core.shape_forge.make_shape(undef_shape, si_infinity);
global_obj.set_shape_id(inf_shape); global_obj.set_shape_id(inf_shape);
global_obj.ensure_hash_props().push(JsValue::float(f64::INFINITY)); 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) P::new(global_obj)
} }
+3 -6
View File
@@ -1,6 +1,8 @@
use std::sync::Arc; 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_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
@@ -58,9 +60,4 @@ pub fn bind_bigint(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
bind_well_known_data_property(core, proto, 9, tag_value, PropAttributes::new(false, false, true)); 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)); 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));
}
} }
@@ -50,6 +50,9 @@ fn bind_error_subtype_constructor(
let global_shape = sh.make_shape(global.shape_id(), name_si); let global_shape = sh.make_shape(global.shape_id(), name_si);
global.set_shape_id(global_shape); global.set_shape_id(global_shape);
global.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr)); 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(); global.bump_generation();
} }
@@ -77,6 +80,9 @@ pub fn bind_error(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
let err_val = JsValue::from_js_object(session.builtin_world().error_constructor.as_ptr() as *mut JsObject); let err_val = JsValue::from_js_object(session.builtin_world().error_constructor.as_ptr() as *mut JsObject);
global.set_shape_id(err_shape); global.set_shape_id(err_shape);
global.ensure_hash_props().push(err_val); 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(); global.bump_generation();
bind_error_subtype_constructor( bind_error_subtype_constructor(
+8 -1
View File
@@ -2,7 +2,7 @@ use std::sync::Arc;
use oxide_kernel::bind_methods; use oxide_kernel::bind_methods;
use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::JsObject; use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
/// 把 Math 单例对象及其方法(abs/floor/max/random 等)绑定到 global。 /// 把 Math 单例对象及其方法(abs/floor/max/random 等)绑定到 global。
@@ -70,6 +70,10 @@ pub fn bind_math(core: &Arc<KernelCore>, session: &KernelSession, global: &mut J
let sh_c = core.shape_forge().as_ref().make_shape(math.shape_id(), si); let sh_c = core.shape_forge().as_ref().make_shape(math.shape_id(), si);
math.set_shape_id(sh_c); math.set_shape_id(sh_c);
math.ensure_hash_props().push(JsValue::float(val)); 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; let si_m = core.perm_interner().intern("Math").0;
@@ -77,5 +81,8 @@ pub fn bind_math(core: &Arc<KernelCore>, session: &KernelSession, global: &mut J
let m_val = JsValue::from_js_object(session.builtin_world().math_object.as_ptr() as *mut JsObject); let m_val = JsValue::from_js_object(session.builtin_world().math_object.as_ptr() as *mut JsObject);
global.set_shape_id(m_shape); global.set_shape_id(m_shape);
global.ensure_hash_props().push(m_val); 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(); global.bump_generation();
} }
+4 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::bindings::{apply_binding_table, configure_native_constructor}; use crate::bindings::{apply_binding_table, configure_native_constructor};
use oxide_kernel::builtin::StringMethods; use oxide_kernel::builtin::StringMethods;
use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::JsObject; use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
/// 把 String 构造器与原型方法绑定到 global。 /// 把 String 构造器与原型方法绑定到 global。
@@ -77,5 +77,8 @@ pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
let str_val = JsValue::from_js_object(session.builtin_world().string_constructor.as_ptr() as *mut JsObject); let str_val = JsValue::from_js_object(session.builtin_world().string_constructor.as_ptr() as *mut JsObject);
global.set_shape_id(str_shape); global.set_shape_id(str_shape);
global.ensure_hash_props().push(str_val); 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(); global.bump_generation();
} }
+8
View File
@@ -74,6 +74,10 @@ macro_rules! bind_constructor {
} else { } else {
$global.push_prop(val); $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 ctor = unsafe { &mut *$ctor_ptr };
let ptr: *const () = ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (); 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) })); 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<KernelCore>, global: &mut JsObject, n
let shape = core.shape_forge().make_shape(global.shape_id(), si); let shape = core.shape_forge().make_shape(global.shape_id(), si);
global.set_shape_id(shape); global.set_shape_id(shape);
global.ensure_hash_props().push(value); 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(); global.bump_generation();
} }
@@ -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");
}