fix: 全局构造器与内置对象属性改非枚举与规范对齐
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user