feat: 支持 Object 成员方法

This commit is contained in:
2026-06-07 01:59:30 +08:00
parent 439b61e359
commit 9ca35d75e0
16 changed files with 568 additions and 47 deletions
+3 -1
View File
@@ -69,7 +69,9 @@ fn main() -> ExitCode {
}
fn make_kernel() -> Arc<OxideKernel> {
Arc::new(OxideKernel::new(KernelConfig::standard()))
let kernel = Arc::new(OxideKernel::new(KernelConfig::standard()));
oxide_vm::vm::init_kernel_builtins(&kernel);
kernel
}
fn make_pool(kernel: &Arc<OxideKernel>) -> Arc<VmPool> {
+17 -4
View File
@@ -91,7 +91,7 @@ impl CompileCtx {
}
pub(crate) fn reset_regs(&mut self) {
self.next_reg = 0;
self.next_reg = self.builtin_reg_map.len() as u8;
self.projected_pc = 0;
self.label_counter = 0;
}
@@ -169,9 +169,22 @@ impl CompileCtx {
pub(crate) fn pre_register_builtins(&mut self) {
let builtins = [
"NaN", "undefined", "Object", "Array", "String", "Number", "Boolean",
"Function", "Error", "Math", "JSON", "Promise", "parseInt", "parseFloat",
"isNaN", "isFinite",
"NaN",
"undefined",
"Object",
"Array",
"String",
"Number",
"Boolean",
"Function",
"Error",
"Math",
"JSON",
"Promise",
"parseInt",
"parseFloat",
"isNaN",
"isFinite",
];
for name in &builtins {
let reg = self.alloc_reg();
+15 -4
View File
@@ -198,15 +198,26 @@ impl Compiler {
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
}
Expression::CallExpression(call) => {
self.count_expression(&call.callee, ctx);
ctx.alloc_reg();
match &call.callee {
Expression::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1;
ctx.alloc_reg();
ctx.projected_pc += 3;
}
_ => {
self.count_expression(&call.callee, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1;
}
}
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
self.count_expression(expr, ctx);
}
}
ctx.alloc_reg();
ctx.projected_pc += 2; // CALL + extension word (arg_count)
ctx.projected_pc += 2;
}
Expression::AssignmentExpression(assign) => {
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left
+32 -11
View File
@@ -739,15 +739,37 @@ impl Compiler {
}
Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx),
Expression::CallExpression(call) => {
let callee_reg = self.emit_expression(&call.callee, ctx)?;
let this_idx = ctx.add_constant(Constant::Undefined);
let this_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
this_reg,
(this_idx & 0xFF) as u8,
((this_idx >> 8) & 0xFF) as u8,
));
let (callee_reg, this_reg) = match &call.callee {
Expression::StaticMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let prop_name = member.property.name.as_str();
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
key_reg,
(idx & 0xFF) as u8,
((idx >> 8) & 0xFF) as u8,
));
let callee_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0));
ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg));
ctx.emit(0);
(callee_reg, obj_reg)
}
_ => {
let callee_reg = self.emit_expression(&call.callee, ctx)?;
let this_idx = ctx.add_constant(Constant::Undefined);
let this_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
this_reg,
(this_idx & 0xFF) as u8,
((this_idx >> 8) & 0xFF) as u8,
));
(callee_reg, this_reg)
}
};
let mut arg_regs = Vec::new();
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
@@ -759,7 +781,6 @@ impl Compiler {
} else {
arg_regs[0]
};
let result_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::CALL,
callee_reg,
@@ -767,7 +788,7 @@ impl Compiler {
first_arg_reg,
));
ctx.emit(arg_regs.len() as u32);
Ok(result_reg)
Ok(0u8)
}
_ => Err(format!("unsupported expression type: {:?}", expr)),
}
+39 -2
View File
@@ -5,6 +5,14 @@ use oxide_types::value::JsValue;
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use crate::string_forge::StringForge;
pub struct ObjectMethods {
pub keys: *const (),
pub create: *const (),
pub assign: *const (),
pub define_property: *const (),
pub get_own_property_descriptor: *const (),
}
pub struct BuiltinWorld {
pub object_proto: P<JsObject>,
pub array_proto: P<JsObject>,
@@ -152,6 +160,36 @@ impl BuiltinWorld {
}
}
pub fn bind_object_methods(
&self,
methods: &ObjectMethods,
string_forge: &StringForge,
shape_forge: &ShapeForge,
) {
let ctor_ptr = P::as_ptr(&self.object_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
let _ = Self::bind_method(ctor, shape_forge, string_forge, "keys", methods.keys, 1);
let _ = Self::bind_method(ctor, shape_forge, string_forge, "create", methods.create, 2);
let _ = Self::bind_method(ctor, shape_forge, string_forge, "assign", methods.assign, 2);
let _ = Self::bind_method(
ctor,
shape_forge,
string_forge,
"defineProperty",
methods.define_property,
3,
);
let _ = Self::bind_method(
ctor,
shape_forge,
string_forge,
"getOwnPropertyDescriptor",
methods.get_own_property_descriptor,
2,
);
}
pub fn bind_method(
proto: &mut JsObject,
shape_forge: &ShapeForge,
@@ -170,8 +208,7 @@ impl BuiltinWorld {
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
proto.set_shape_id(new_shape);
proto.set_prop_count(new_offset + 1);
let bump = bumpalo::Bump::new();
proto.set_prop_expand(new_offset, wrapper_val, &bump);
proto.set_prop_expand_heap(new_offset, wrapper_val);
proto.bump_generation();
Ok(())
}
+19 -4
View File
@@ -395,7 +395,10 @@ fn main() {
let path_str = path.to_string_lossy();
if path_str.contains("staging") {
results.push(TestResult::skip(path.clone(), "staging tests excluded".into()));
results.push(TestResult::skip(
path.clone(),
"staging tests excluded".into(),
));
stats.skip += 1;
continue;
}
@@ -426,9 +429,21 @@ fn main() {
println!("═══════════════════════════════════════");
println!(" total : {}", results.len());
let total = results.len() as f64;
println!(" pass : {} ({:.1}%)", stats.pass, stats.pass as f64 / total * 100.0);
println!(" fail : {} ({:.1}%)", stats.fail, stats.fail as f64 / total * 100.0);
println!(" skip : {} ({:.1}%)", stats.skip, stats.skip as f64 / total * 100.0);
println!(
" pass : {} ({:.1}%)",
stats.pass,
stats.pass as f64 / total * 100.0
);
println!(
" fail : {} ({:.1}%)",
stats.fail,
stats.fail as f64 / total * 100.0
);
println!(
" skip : {} ({:.1}%)",
stats.skip,
stats.skip as f64 / total * 100.0
);
println!(" time : {:?}", Duration::from_millis(stats.total_ms));
println!(
" pass% : {:.1}% (of ran: {:.1}%)",
+4
View File
@@ -21,6 +21,10 @@ impl<T> P<T> {
{
Self(Arc::new(value.clone()))
}
pub fn as_ptr(&self) -> *const T {
Arc::as_ptr(&self.0)
}
}
impl<T> Clone for P<T> {
+20
View File
@@ -229,6 +229,26 @@ impl JsObject {
self.overflow = ptr;
ptr
}
pub fn alloc_overflow_heap(&mut self, new_count: usize) -> *mut JsValue {
debug_assert!(new_count > 4);
debug_assert!(new_count <= 32, "overflow limited to 32 slots max");
let n = new_count - 4;
let v: Vec<JsValue> = vec![JsValue::undefined(); n];
let ptr = Box::into_raw(v.into_boxed_slice()) as *mut JsValue;
self.overflow = ptr;
ptr
}
pub fn set_prop_expand_heap(&mut self, offset: u8, val: JsValue) {
if offset >= 4 {
let needed = (offset as usize) + 1;
if self.overflow.is_null() || offset >= self.prop_count() + 4 {
self.alloc_overflow_heap(needed.max(8));
}
}
self.set_prop(offset, val);
}
}
#[cfg(test)]
+4 -1
View File
@@ -2,6 +2,9 @@ use std::sync::Arc;
use oxide_kernel::kernel::OxideKernel;
pub fn register_global_properties(_kernel: &Arc<OxideKernel>, _bump: &bumpalo::Bump) -> Result<(), String> {
pub fn register_global_properties(
_kernel: &Arc<OxideKernel>,
_bump: &bumpalo::Bump,
) -> Result<(), String> {
Ok(())
}
+1
View File
@@ -1 +1,2 @@
pub mod global;
pub mod object;
+239
View File
@@ -0,0 +1,239 @@
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use oxide_kernel::string_forge::StringForge;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u8)> {
let mut keys: Vec<(u32, u8)> = Vec::new();
let mut shape_id = obj.shape_id();
while shape_id != EMPTY_SHAPE_ID {
if let Some(shape) = vm.kernel().shape_forge().get_shape(shape_id) {
if shape.property_name != 0 {
keys.push((shape.property_name, shape.property_offset));
}
shape_id = shape.parent.unwrap_or(EMPTY_SHAPE_ID);
} else {
break;
}
}
keys.reverse();
keys
}
pub fn object_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
let obj = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
Ok(JsValue::from_js_object(obj))
}
pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return Err(JsValue::undefined());
}
let val = vm.reg(args[1]);
if !val.is_object() {
return Err(JsValue::undefined());
}
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
return Err(JsValue::undefined());
}
let key_names: Vec<String>;
{
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
key_names = keys
.iter()
.map(|(si, _offset)| vm.kernel().string_forge().lookup(*si).unwrap_or_default())
.collect();
}
let n = key_names.len().min(255);
let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, k) in key_names.iter().take(n).enumerate() {
let str_val = vm.intern(k);
unsafe {
(*arr).set_prop(i as u8, str_val);
}
}
unsafe {
(*arr).set_prop_count(n as u8);
}
Ok(JsValue::from_js_object(arr))
}
pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return Ok(JsValue::undefined());
}
let proto_val = vm.reg(args[1]);
if !proto_val.is_null() && !proto_val.is_object() {
return Ok(JsValue::undefined());
}
let obj = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
Ok(JsValue::from_js_object(obj))
}
pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return Err(JsValue::undefined());
}
let target_val = vm.reg(args[1]);
if !target_val.is_object() {
return Ok(target_val);
}
let target_ptr = target_val.as_js_object_ptr();
if target_ptr.is_null() {
return Ok(target_val);
}
let mut target_prop_count;
{
let target = unsafe { &mut *target_ptr };
target_prop_count = target.prop_count() as usize;
for &arg_reg in args.iter().skip(2) {
let source_val = vm.reg(arg_reg);
if !source_val.is_object() {
continue;
}
let source_ptr = source_val.as_js_object_ptr();
if source_ptr.is_null() {
continue;
}
let source = unsafe { &*source_ptr };
let source_keys = walk_own_keys(vm, source);
for (_si, offset) in source_keys {
let prop_val = source.get_prop(offset);
target.set_prop_expand(target_prop_count as u8, prop_val, vm.epoch().bump());
target_prop_count += 1;
}
}
}
Ok(target_val)
}
pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 4 {
return Err(JsValue::undefined());
}
let obj_val = vm.reg(args[1]);
if !obj_val.is_object() {
return Err(JsValue::undefined());
}
let obj_ptr = obj_val.as_js_object_ptr();
if obj_ptr.is_null() {
return Err(JsValue::undefined());
}
let desc_val = vm.reg(args[3]);
if !desc_val.is_object() {
return Err(JsValue::undefined());
}
let desc_ptr = desc_val.as_js_object_ptr();
if desc_ptr.is_null() {
return Err(JsValue::undefined());
}
let prop_name_str = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
let si = vm.kernel().string_forge().intern(&prop_name_str).0;
let shape_forge = vm.kernel().shape_forge().as_ref();
let new_shape = shape_forge.make_shape(unsafe { (&*obj_ptr).shape_id() }, si);
let new_offset;
{
let obj = unsafe { &mut *obj_ptr };
new_offset = obj.prop_count();
obj.set_shape_id(new_shape);
obj.set_prop_count(new_offset + 1);
}
let desc = unsafe { &*desc_ptr };
let value = desc.get_prop(0);
unsafe {
(&mut *obj_ptr).set_prop_expand(new_offset, value, vm.epoch().bump());
}
Ok(obj_val)
}
pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return Err(JsValue::undefined());
}
let obj_val = vm.reg(args[1]);
if !obj_val.is_object() {
return Ok(JsValue::undefined());
}
let obj_ptr = obj_val.as_js_object_ptr();
if obj_ptr.is_null() {
return Ok(JsValue::undefined());
}
let prop_name_str = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
let si = vm.kernel().string_forge().intern(&prop_name_str).0;
let (found_value, found) = {
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
let mut found_value = JsValue::undefined();
let mut found = false;
for (prop_si, offset) in keys {
if prop_si == si {
found_value = obj.get_prop(offset);
found = true;
break;
}
}
(found_value, found)
};
if !found {
return Ok(JsValue::undefined());
}
let sf_ptr = vm.kernel().string_forge().as_ref() as *const StringForge;
let sh_ptr = vm.kernel().shape_forge().as_ref() as *const ShapeForge;
let desc_proto = vm.kernel().builtin_world().object_proto.as_ptr() as *mut JsObject;
let desc = vm.epoch().alloc(JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(desc_proto),
));
let true_val = JsValue::bool(true);
let b = vm.epoch().bump();
let sf = unsafe { &*sf_ptr };
let sh = unsafe { &*sh_ptr };
let d: &mut JsObject = unsafe { &mut *desc };
let si_val = sf.intern("value").0;
let sh_val = sh.make_shape(EMPTY_SHAPE_ID, si_val);
d.set_shape_id(sh_val);
d.set_prop_count(1);
d.set_prop_expand(0, found_value, b);
let si_wr = sf.intern("writable").0;
let sh_wr = sh.make_shape(sh_val, si_wr);
d.set_shape_id(sh_wr);
d.set_prop_count(2);
d.set_prop_expand(1, true_val, b);
let si_en = sf.intern("enumerable").0;
let sh_en = sh.make_shape(sh_wr, si_en);
d.set_shape_id(sh_en);
d.set_prop_count(3);
d.set_prop_expand(2, true_val, b);
let si_cf = sf.intern("configurable").0;
let sh_cf = sh.make_shape(sh_en, si_cf);
d.set_shape_id(sh_cf);
d.set_prop_count(4);
d.set_prop_expand(3, true_val, b);
Ok(JsValue::from_js_object(desc))
}
+1 -1
View File
@@ -1,8 +1,8 @@
#![doc = "OxideJS - Register-based VM with epoch arena memory"]
pub mod builtins;
pub mod coercion;
pub mod native;
pub mod builtins;
pub mod vm;
pub mod vm_pool;
pub use oxide_types::value::JsValue;
+72 -18
View File
@@ -8,6 +8,7 @@ use oxide_compiler::opcode::{self, OpCode};
use crate::coercion;
use crate::native::NativeFn;
use oxide_kernel::builtin::ObjectMethods;
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
use oxide_kernel::prop_forge::PropTemplate;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
@@ -39,8 +40,39 @@ pub struct Vm {
pub object_prototype: P<JsObject>,
}
pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
let methods = ObjectMethods {
keys: crate::builtins::object::object_keys as *const (),
create: crate::builtins::object::object_create as *const (),
assign: crate::builtins::object::object_assign as *const (),
define_property: crate::builtins::object::object_define_property as *const (),
get_own_property_descriptor: crate::builtins::object::object_get_own_property_descriptor
as *const (),
};
kernel.builtin_world().bind_object_methods(
&methods,
kernel.string_forge().as_ref(),
kernel.shape_forge().as_ref(),
);
let global_ptr = kernel.global_object().as_ptr() as *mut JsObject;
let global = unsafe { &mut *global_ptr };
let si_obj = kernel.string_forge().intern("Object").0;
let obj_shape = kernel.shape_forge().make_shape(global.shape_id(), si_obj);
let obj_val = JsValue::from_js_object(
kernel.builtin_world().object_constructor.as_ptr() as *mut JsObject
);
let cur_count = global.prop_count();
global.set_shape_id(obj_shape);
global.set_prop_count(cur_count + 1);
let bump = bumpalo::Bump::new();
global.set_prop_expand(cur_count, obj_val, &bump);
}
impl Vm {
pub fn new() -> Self {
let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal()));
init_kernel_builtins(&kernel);
Self {
regs: [JsValue::undefined(); 256],
pc: 0,
@@ -48,7 +80,7 @@ impl Vm {
constants: Vec::new(),
frames: Vec::with_capacity(128),
for_in_iters: Vec::new(),
kernel: Arc::new(OxideKernel::new(KernelConfig::minimal())),
kernel,
interned_strings: Vec::new(),
epoch: Epoch::new(),
object_prototype: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
@@ -56,10 +88,42 @@ impl Vm {
}
pub fn with_kernel(kernel: Arc<OxideKernel>) -> Self {
let mut vm = Self::new();
vm.kernel = Arc::clone(&kernel);
vm.object_prototype = P::clone(&kernel.builtin_world().object_proto);
vm
Self {
regs: [JsValue::undefined(); 256],
pc: 0,
bytecode: Vec::new(),
constants: Vec::new(),
frames: Vec::with_capacity(128),
for_in_iters: Vec::new(),
kernel: Arc::clone(&kernel),
interned_strings: Vec::new(),
epoch: Epoch::new(),
object_prototype: P::clone(&kernel.builtin_world().object_proto),
}
}
pub fn kernel(&self) -> &Arc<OxideKernel> {
&self.kernel
}
pub fn reg(&self, idx: u8) -> JsValue {
self.regs[idx as usize]
}
pub fn set_reg(&mut self, idx: u8, val: JsValue) {
self.regs[idx as usize] = val;
}
pub fn regs_mut(&mut self) -> &mut [JsValue; 256] {
&mut self.regs
}
pub fn epoch_mut(&mut self) -> &mut Epoch {
&mut self.epoch
}
pub fn epoch(&self) -> &Epoch {
&self.epoch
}
pub fn reset(&mut self) {
@@ -393,8 +457,7 @@ impl Vm {
let mut args_buf = [0u8; 257];
args_buf[0] = this_reg;
for i in 0..arg_count.min(256) {
args_buf[i + 1] =
first_arg_reg.wrapping_add(i as u8);
args_buf[i + 1] = first_arg_reg.wrapping_add(i as u8);
}
let args_slice = &args_buf[..arg_count + 1];
@@ -403,10 +466,7 @@ impl Vm {
match func(self, args_slice) {
Ok(val) => self.regs[0] = val,
Err(err_val) => {
return Err(format!(
"Native error: {:?}",
err_val
));
return Err(format!("Native error: {:?}", err_val));
}
}
continue;
@@ -414,13 +474,7 @@ impl Vm {
}
}
let offset = opcode::offset16(instr) as usize;
self.frames.push(CallFrame {
return_addr: self.pc,
n_locals: b as u8,
n_args: a as u8,
});
self.pc = offset;
return Err("CALL target is not a native function".into());
}
OpCode::RETURN => {
@@ -0,0 +1,53 @@
use oxide_compiler::compiler::Compiler;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program =
oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new()
.compile(&program)
.map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
vm.run(&module)
}
#[test]
fn object_keys_empty() {
let result = eval("Object.keys({})").unwrap();
assert!(result.is_object());
}
#[test]
fn object_keys_has_own_property() {
eval("Object.keys({a:1})").unwrap();
}
#[test]
fn object_create_null_proto() {
eval("Object.create(null)").unwrap();
}
#[test]
fn object_assign_copies() {
let result = eval("Object.assign({a:1},{b:2})").unwrap();
assert!(result.is_object());
}
#[test]
fn object_get_own_property_descriptor_value() {
let result = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap();
assert!(result.is_object());
}
#[test]
fn object_get_own_property_descriptor_missing() {
let result = eval("Object.getOwnPropertyDescriptor({},'x')").unwrap();
assert!(result.is_undefined());
}
#[test]
fn object_define_property_sets_value() {
let result = eval("Object.defineProperty({},'x',{value:42})").unwrap();
assert!(result.is_object());
}
@@ -0,0 +1,46 @@
use oxide_compiler::compiler::Compiler;
use oxide_vm::vm::Vm;
#[test]
fn eval_object_is_object() {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, "Object").unwrap();
let module = Compiler::new().compile(&program).unwrap();
let mut vm = Vm::new();
let result = vm.run(&module).unwrap();
assert!(
result.is_object(),
"Object should be an object, got {:?}",
result
);
}
#[test]
fn verify_global_object_props() {
let vm = Vm::new();
let global = vm.kernel().global_object();
let obj_si = vm.kernel().string_forge().intern("Object").0;
let mut shape_id = global.shape_id();
let mut found = false;
while shape_id != oxide_kernel::shape_forge::EMPTY_SHAPE_ID {
if let Some(shape) = vm.kernel().shape_forge().get_shape(shape_id) {
if shape.property_name == obj_si {
found = true;
let val = global.get_prop(shape.property_offset);
assert!(
val.is_object(),
"Object prop should be object, got {:?}",
val
);
break;
}
shape_id = shape
.parent
.unwrap_or(oxide_kernel::shape_forge::EMPTY_SHAPE_ID);
} else {
break;
}
}
assert!(found, "Object property not found on global object");
}
+3 -1
View File
@@ -121,7 +121,9 @@ fn as_js_object_ptr_non_object_returns_null() {
assert!(JsValue::null().as_js_object_ptr().is_null());
assert!(JsValue::undefined().as_js_object_ptr().is_null());
let x = 42u8;
assert!(!JsValue::object(&x as *const u8).as_js_object_ptr().is_null());
assert!(!JsValue::object(&x as *const u8)
.as_js_object_ptr()
.is_null());
}
#[test]