fix: 修复测试悬空引用与 this 误转字符串问题

This commit is contained in:
爱特每羊队
2026-06-09 00:40:01 +08:00
parent e6ae7fbc46
commit 046b59fa5e
4 changed files with 119 additions and 64 deletions
+45 -7
View File
@@ -160,15 +160,53 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult {
} }
pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
if !this_val.is_object() {
return Err(crate::builtins::error::create_type_error(
vm,
"Function.prototype.toString called on non-function",
));
}
let func = unsafe { &*this_val.as_js_object_ptr() };
if !func.is_function() {
return Err(crate::builtins::error::create_type_error(
vm,
"Function.prototype.toString called on non-function",
));
}
let name_si = vm.kernel().string_forge().intern("name").0;
let name = vm let name = vm
.kernel() .resolve_property(func, name_si)
.string_forge() .and_then(|v| {
.lookup(vm.reg(args[0]).as_string_index()) if v.is_string() {
.unwrap_or_default(); vm.kernel().string_forge().lookup(v.as_string_index())
let result = if name.is_empty() { } else {
"function () { [native code] }".to_string() None
}
})
.unwrap_or_else(|| {
let sub_idx = func.sub_module_index();
if sub_idx > 0 && (sub_idx as usize) <= vm.sub_modules.len() {
vm.sub_modules[sub_idx as usize - 1]
.function_name
.clone()
.unwrap_or_default()
} else {
String::new()
}
});
let body = if func.native_fn().is_some() {
"[native code]"
} else { } else {
format!("function {}() {{ [native code] }}", name) "[bytecode]"
};
let result = if name.is_empty() {
format!("function () {{ {body} }}")
} else {
format!("function {name}() {{ {body} }}")
}; };
let si = vm.kernel().string_forge().intern(&result).0; let si = vm.kernel().string_forge().intern(&result).0;
Ok(JsValue::string(si, 0)) Ok(JsValue::string(si, 0))
+46 -44
View File
@@ -1,8 +1,9 @@
use oxide_compiler::compiler::Compiler; use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::builtins::array::{array_constructor, array_push}; use oxide_vm::builtins::array::{array_constructor, array_push};
use oxide_vm::vm::Vm; use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> { fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default(); let allocator = oxide_parser::Allocator::default();
let program = let program =
oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
@@ -10,20 +11,21 @@ fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> {
.compile(&program) .compile(&program)
.map_err(|e| format!("Compile error: {}", e))?; .map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new(); let mut vm = Vm::new();
vm.run(&module) let result = vm.run(&module)?;
Ok((vm, result))
} }
#[test] #[test]
fn array_push_adds_element() { fn array_push_adds_element() {
let result = eval("var a = [1,2]; a.push(3); a").unwrap(); let (_vm, result) = eval("var a = [1,2]; a.push(3); a").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3); assert_eq!(obj.prop_count(), 3);
assert_eq!(obj.get_prop_at(2), oxide_types::value::JsValue::int(3)); assert_eq!(obj.get_prop_at(2), JsValue::int(3));
} }
#[test] #[test]
fn array_push_returns_length() { fn array_push_returns_length() {
let result = eval("[1,2,3].push(4)").unwrap(); let (_vm, result) = eval("[1,2,3].push(4)").unwrap();
assert_eq!(result.as_int(), 4); assert_eq!(result.as_int(), 4);
} }
@@ -40,13 +42,13 @@ fn array_push_returns_true_length_beyond_31() {
#[test] #[test]
fn array_length_tracks_more_than_31_elements() { fn array_length_tracks_more_than_31_elements() {
let result = eval("var a=[]; for (var i=0;i<40;i=i+1) a.push(i); a.length").unwrap(); let (_vm, result) = eval("var a=[]; for (var i=0;i<40;i=i+1) a.push(i); a.length").unwrap();
assert_eq!(result.as_int(), 40); assert_eq!(result.as_int(), 40);
} }
#[test] #[test]
fn array_push_preserves_slots_beyond_255() { fn array_push_preserves_slots_beyond_255() {
let result = eval("var a=[]; for (var i=0;i<257;i=i+1) a.push(i); a").unwrap(); let (_vm, result) = eval("var a=[]; for (var i=0;i<257;i=i+1) a.push(i); a").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 257); assert_eq!(obj.prop_count(), 257);
let tail = obj.get_prop_at(256); let tail = obj.get_prop_at(256);
@@ -55,126 +57,126 @@ fn array_push_preserves_slots_beyond_255() {
#[test] #[test]
fn array_pop_returns_last() { fn array_pop_returns_last() {
let result = eval("[1,2,3].pop()").unwrap(); let (_vm, result) = eval("[1,2,3].pop()").unwrap();
assert_eq!(result.as_int(), 3); assert_eq!(result.as_int(), 3);
} }
#[test] #[test]
fn array_pop_empty_returns_undefined() { fn array_pop_empty_returns_undefined() {
let result = eval("[].pop()").unwrap(); let (_vm, result) = eval("[].pop()").unwrap();
assert!(result.is_undefined()); assert!(result.is_undefined());
} }
#[test] #[test]
fn array_pop_reduces_length() { fn array_pop_reduces_length() {
let result = eval("var a = [1,2,3]; a.pop(); a").unwrap(); let (_vm, result) = eval("var a = [1,2,3]; a.pop(); a").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2); assert_eq!(obj.prop_count(), 2);
} }
#[test] #[test]
fn array_slice_returns_subarray() { fn array_slice_returns_subarray() {
let result = eval("[1,2,3,4].slice(1,3)").unwrap(); let (_vm, result) = eval("[1,2,3,4].slice(1,3)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2); assert_eq!(obj.prop_count(), 2);
assert_eq!(obj.get_prop_at(0), oxide_types::value::JsValue::int(2)); assert_eq!(obj.get_prop_at(0), JsValue::int(2));
assert_eq!(obj.get_prop_at(1), oxide_types::value::JsValue::int(3)); assert_eq!(obj.get_prop_at(1), JsValue::int(3));
} }
#[test] #[test]
fn array_slice_no_args_copies() { fn array_slice_no_args_copies() {
let result = eval("[1,2,3].slice()").unwrap(); let (_vm, result) = eval("[1,2,3].slice()").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3); assert_eq!(obj.prop_count(), 3);
} }
#[test] #[test]
fn array_concat_combines() { fn array_concat_combines() {
let result = eval("[1,2].concat([3,4])").unwrap(); let (_vm, result) = eval("[1,2].concat([3,4])").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 4); assert_eq!(obj.prop_count(), 4);
assert_eq!(obj.get_prop_at(3), oxide_types::value::JsValue::int(4)); assert_eq!(obj.get_prop_at(3), JsValue::int(4));
} }
#[test] #[test]
fn array_concat_non_array() { fn array_concat_non_array() {
let result = eval("[1,2].concat(3)").unwrap(); let (_vm, result) = eval("[1,2].concat(3)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3); assert_eq!(obj.prop_count(), 3);
} }
#[test] #[test]
fn array_join_with_dash() { fn array_join_with_dash() {
let result = eval("['a','b','c'].join('-')").unwrap(); let (_vm, result) = eval("['a','b','c'].join('-')").unwrap();
assert!(result.is_string()); assert!(result.is_string());
} }
#[test] #[test]
fn array_index_of_found() { fn array_index_of_found() {
let result = eval("[10,20,30].indexOf(20)").unwrap(); let (_vm, result) = eval("[10,20,30].indexOf(20)").unwrap();
assert_eq!(result.as_int(), 1); assert_eq!(result.as_int(), 1);
} }
#[test] #[test]
fn array_index_of_not_found() { fn array_index_of_not_found() {
let result = eval("[1,2,3].indexOf(99)").unwrap(); let (_vm, result) = eval("[1,2,3].indexOf(99)").unwrap();
assert_eq!(result.as_int(), -1); assert_eq!(result.as_int(), -1);
} }
#[test] #[test]
fn array_includes_true() { fn array_includes_true() {
let result = eval("[1,2,3].includes(2)").unwrap(); let (_vm, result) = eval("[1,2,3].includes(2)").unwrap();
assert_eq!(result.as_bool(), true); assert_eq!(result.as_bool(), true);
} }
#[test] #[test]
fn array_includes_false() { fn array_includes_false() {
let result = eval("[1,2,3].includes(99)").unwrap(); let (_vm, result) = eval("[1,2,3].includes(99)").unwrap();
assert_eq!(result.as_bool(), false); assert_eq!(result.as_bool(), false);
} }
#[test] #[test]
fn array_reverse_mutates() { fn array_reverse_mutates() {
let result = eval("var a = [1,2,3]; a.reverse(); a").unwrap(); let (_vm, result) = eval("var a = [1,2,3]; a.reverse(); a").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.get_prop_at(0), oxide_types::value::JsValue::int(3)); assert_eq!(obj.get_prop_at(0), JsValue::int(3));
assert_eq!(obj.get_prop_at(1), oxide_types::value::JsValue::int(2)); assert_eq!(obj.get_prop_at(1), JsValue::int(2));
assert_eq!(obj.get_prop_at(2), oxide_types::value::JsValue::int(1)); assert_eq!(obj.get_prop_at(2), JsValue::int(1));
} }
#[test] #[test]
fn array_splice_remove() { fn array_splice_remove() {
let result = eval("[1,2,3,4].splice(1,2)").unwrap(); let (_vm, result) = eval("[1,2,3,4].splice(1,2)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2); assert_eq!(obj.prop_count(), 2);
assert_eq!(obj.get_prop_at(0), oxide_types::value::JsValue::int(2)); assert_eq!(obj.get_prop_at(0), JsValue::int(2));
assert_eq!(obj.get_prop_at(1), oxide_types::value::JsValue::int(3)); assert_eq!(obj.get_prop_at(1), JsValue::int(3));
} }
#[test] #[test]
fn array_splice_insert() { fn array_splice_insert() {
let result = eval("var a = [1,4]; a.splice(1,0,2,3); a").unwrap(); let (_vm, result) = eval("var a = [1,4]; a.splice(1,0,2,3); a").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 4); assert_eq!(obj.prop_count(), 4);
} }
#[test] #[test]
fn array_flat_noop() { fn array_flat_noop() {
let result = eval("[1,2,3].flat()").unwrap(); let (_vm, result) = eval("[1,2,3].flat()").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3); assert_eq!(obj.prop_count(), 3);
} }
#[test] #[test]
fn array_flat_one_level() { fn array_flat_one_level() {
let result = eval("[1,[2,3],4].flat()").unwrap(); let (_vm, result) = eval("[1,[2,3],4].flat()").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 4); assert_eq!(obj.prop_count(), 4);
} }
#[test] #[test]
fn array_literal_creates_array() { fn array_literal_creates_array() {
let result = eval("[1,2,3]").unwrap(); let (_vm, result) = eval("[1,2,3]").unwrap();
assert!(result.is_object()); assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert!(obj.is_array()); assert!(obj.is_array());
@@ -182,7 +184,7 @@ fn array_literal_creates_array() {
#[test] #[test]
fn array_empty_literal() { fn array_empty_literal() {
let result = eval("[]").unwrap(); let (_vm, result) = eval("[]").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert!(obj.is_array()); assert!(obj.is_array());
assert_eq!(obj.prop_count(), 0); assert_eq!(obj.prop_count(), 0);
@@ -190,63 +192,63 @@ fn array_empty_literal() {
#[test] #[test]
fn array_shift_removes_first() { fn array_shift_removes_first() {
let result = eval("[1,2,3].shift()").unwrap(); let (_vm, result) = eval("[1,2,3].shift()").unwrap();
assert_eq!(result.as_int(), 1); assert_eq!(result.as_int(), 1);
} }
#[test] #[test]
fn array_unshift_adds_front() { fn array_unshift_adds_front() {
let result = eval("[3].unshift(1,2)").unwrap(); let (_vm, result) = eval("[3].unshift(1,2)").unwrap();
assert_eq!(result.as_int(), 3); assert_eq!(result.as_int(), 3);
} }
#[test] #[test]
fn array_fill_replaces() { fn array_fill_replaces() {
let result = eval("[1,2,3,4].fill(0,1,3)").unwrap(); let (_vm, result) = eval("[1,2,3,4].fill(0,1,3)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 4); assert_eq!(obj.prop_count(), 4);
} }
#[test] #[test]
fn array_at_positive() { fn array_at_positive() {
let result = eval("[10,20,30].at(1)").unwrap(); let (_vm, result) = eval("[10,20,30].at(1)").unwrap();
assert_eq!(result.as_int(), 20); assert_eq!(result.as_int(), 20);
} }
#[test] #[test]
fn array_at_negative() { fn array_at_negative() {
let result = eval("[10,20,30].at(-1)").unwrap(); let (_vm, result) = eval("[10,20,30].at(-1)").unwrap();
assert_eq!(result.as_int(), 30); assert_eq!(result.as_int(), 30);
} }
#[test] #[test]
fn array_last_index_of_found() { fn array_last_index_of_found() {
let result = eval("[1,2,3,2].lastIndexOf(2)").unwrap(); let (_vm, result) = eval("[1,2,3,2].lastIndexOf(2)").unwrap();
assert_eq!(result.as_int(), 3); assert_eq!(result.as_int(), 3);
} }
#[test] #[test]
fn array_last_index_of_not_found() { fn array_last_index_of_not_found() {
let result = eval("[1,2,3].lastIndexOf(99)").unwrap(); let (_vm, result) = eval("[1,2,3].lastIndexOf(99)").unwrap();
assert_eq!(result.as_int(), -1); assert_eq!(result.as_int(), -1);
} }
#[test] #[test]
fn array_sort_default() { fn array_sort_default() {
let result = eval("[3,1,2].sort()").unwrap(); let (_vm, result) = eval("[3,1,2].sort()").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3); assert_eq!(obj.prop_count(), 3);
} }
#[test] #[test]
fn array_copy_within_copies() { fn array_copy_within_copies() {
let result = eval("[1,2,3,4,5].copyWithin(0,3)").unwrap(); let (_vm, result) = eval("[1,2,3,4,5].copyWithin(0,3)").unwrap();
assert!(result.is_object()); assert!(result.is_object());
} }
#[test] #[test]
fn array_fill_single() { fn array_fill_single() {
let result = eval("[1,2,3].fill(0)").unwrap(); let (_vm, result) = eval("[1,2,3].fill(0)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.get_prop_at(0).as_int(), 0); assert_eq!(obj.get_prop_at(0).as_int(), 0);
} }
@@ -38,4 +38,17 @@ fn function_to_string_includes_function() {
let mut vm = Vm::new(); let mut vm = Vm::new();
let result = eval(&mut vm, "Math.max.toString()").unwrap(); let result = eval(&mut vm, "Math.max.toString()").unwrap();
assert!(result.is_string()); assert!(result.is_string());
let rendered = vm
.kernel()
.string_forge()
.lookup(result.as_string_index())
.unwrap_or_default();
assert!(rendered.contains("function"), "got: {}", rendered);
}
#[test]
fn function_to_string_non_function_throws() {
let mut vm = Vm::new();
let err = eval(&mut vm, "Function.prototype.toString.call(1)").unwrap_err();
assert!(err.contains("TypeError"), "got: {}", err);
} }
+15 -13
View File
@@ -1,7 +1,8 @@
use oxide_compiler::compiler::Compiler; use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm; use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> { fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default(); let allocator = oxide_parser::Allocator::default();
let program = let program =
oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
@@ -9,12 +10,13 @@ fn eval(source: &str) -> Result<oxide_types::value::JsValue, String> {
.compile(&program) .compile(&program)
.map_err(|e| format!("Compile error: {}", e))?; .map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new(); let mut vm = Vm::new();
vm.run(&module) let result = vm.run(&module)?;
Ok((vm, result))
} }
#[test] #[test]
fn object_keys_empty() { fn object_keys_empty() {
let result = eval("Object.keys({})").unwrap(); let (_vm, result) = eval("Object.keys({})").unwrap();
assert!(result.is_object()); assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 0); assert_eq!(obj.prop_count(), 0);
@@ -22,55 +24,55 @@ fn object_keys_empty() {
#[test] #[test]
fn object_keys_has_own_property() { fn object_keys_has_own_property() {
let result = eval("Object.keys({a:1,b:2})").unwrap(); let (_vm, result) = eval("Object.keys({a:1,b:2})").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2); assert_eq!(obj.prop_count(), 2);
} }
#[test] #[test]
fn object_create_null_proto() { fn object_create_null_proto() {
eval("Object.create(null)").unwrap(); let (_vm, _result) = eval("Object.create(null)").unwrap();
} }
#[test] #[test]
fn object_assign_copies() { fn object_assign_copies() {
let result = eval("Object.assign({a:1},{b:2})").unwrap(); let (_vm, result) = eval("Object.assign({a:1},{b:2})").unwrap();
assert!(result.is_object()); assert!(result.is_object());
} }
#[test] #[test]
fn object_get_own_property_descriptor_value() { fn object_get_own_property_descriptor_value() {
let result = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap(); let (_vm, result) = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap();
assert!(result.is_object()); assert!(result.is_object());
} }
#[test] #[test]
fn object_get_own_property_descriptor_missing() { fn object_get_own_property_descriptor_missing() {
let result = eval("Object.getOwnPropertyDescriptor({},'x')").unwrap(); let (_vm, result) = eval("Object.getOwnPropertyDescriptor({},'x')").unwrap();
assert!(result.is_undefined()); assert!(result.is_undefined());
} }
#[test] #[test]
fn object_define_property_sets_value() { fn object_define_property_sets_value() {
let result = eval("Object.defineProperty({},'x',{value:42})").unwrap(); let (_vm, result) = eval("Object.defineProperty({},'x',{value:42})").unwrap();
assert!(result.is_object()); assert!(result.is_object());
} }
#[test] #[test]
fn object_get_prototype_of() { fn object_get_prototype_of() {
let result = eval("Object.getPrototypeOf({})").unwrap(); let (_vm, result) = eval("Object.getPrototypeOf({})").unwrap();
assert!(result.is_object() || result.is_null()); assert!(result.is_object() || result.is_null());
} }
#[test] #[test]
fn object_has_own_true() { fn object_has_own_true() {
let result = eval("Object.hasOwn({a:1}, 'a')").unwrap(); let (_vm, result) = eval("Object.hasOwn({a:1}, 'a')").unwrap();
assert!(result.is_bool()); assert!(result.is_bool());
} }
#[test] #[test]
fn object_entries_returns_array() { fn object_entries_returns_array() {
let result = eval("Object.entries({a:1,b:2})").unwrap(); let (_vm, result) = eval("Object.entries({a:1,b:2})").unwrap();
assert!(result.is_object()); assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2); assert_eq!(obj.prop_count(), 2);
@@ -78,7 +80,7 @@ fn object_entries_returns_array() {
#[test] #[test]
fn object_values_returns_array() { fn object_values_returns_array() {
let result = eval("Object.values({a:1,b:2})").unwrap(); let (_vm, result) = eval("Object.values({a:1,b:2})").unwrap();
assert!(result.is_object()); assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() }; let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2); assert_eq!(obj.prop_count(), 2);