mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
528f893e63
- message/name 属性改非枚举(CreateNonEnumerableDataPropertyOrThrow), Object.keys/for-in/JSON.stringify 不再泄漏错误信息 - 新增 Error 家族对象标签,Object.prototype.toString 输出 [object Error], 含子类型与用户子类(沿原型链判定) - Error 家族构造器补 length=1,函数调用忽略 this 一律新建对象 - JSON.stringify 序列化仅含可枚举自身属性(规范 EnumerableOwnPropertyNames)
452 lines
15 KiB
Rust
452 lines
15 KiB
Rust
use oxide_builtins::error;
|
|
use oxide_compiler::compiler::Compiler;
|
|
use oxide_types::mem::P;
|
|
use oxide_types::value::JsValue;
|
|
use oxide_vm::vm::Vm;
|
|
|
|
fn make_vm() -> Vm {
|
|
Vm::new()
|
|
}
|
|
|
|
fn eval(source: &str) -> Result<JsValue, String> {
|
|
let allocator = oxide_parser::Allocator::default();
|
|
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
|
|
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
|
|
let mut vm = make_vm();
|
|
vm.run(&module)
|
|
}
|
|
|
|
/// 在既有 Vm 内编译执行:结果字符串须在同一 Vm 上下文解析(session 串随 Vm 释放)。
|
|
fn eval_in(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
|
|
let allocator = oxide_parser::Allocator::default();
|
|
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
|
|
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
|
|
vm.run(&module)
|
|
}
|
|
|
|
#[test]
|
|
fn error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn error_has_message() {
|
|
let mut vm = make_vm();
|
|
let msg = vm.new_string("test message");
|
|
vm.set_reg(1, msg);
|
|
let result = error::error_constructor(&mut vm, &[0u8, 1u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn type_error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::type_error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn reference_error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::reference_error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn range_error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::range_error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn syntax_error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::syntax_error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn uri_error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::uri_error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn eval_error_constructor_creates_object() {
|
|
let mut vm = make_vm();
|
|
let result = error::eval_error_constructor(&mut vm, &[0u8]).unwrap();
|
|
assert!(result.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn create_type_error_returns_jsvalue() {
|
|
let mut vm = make_vm();
|
|
let err = error::create_type_error(&mut vm, "something went wrong");
|
|
assert!(err.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn create_range_error_returns_jsvalue() {
|
|
let mut vm = make_vm();
|
|
let err = error::create_range_error(&mut vm, "out of bounds");
|
|
assert!(err.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn create_reference_error_returns_jsvalue() {
|
|
let mut vm = make_vm();
|
|
let err = error::create_reference_error(&mut vm, "not defined");
|
|
assert!(err.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn create_syntax_error_returns_jsvalue() {
|
|
let mut vm = make_vm();
|
|
let err = error::create_syntax_error(&mut vm, "invalid syntax");
|
|
assert!(err.is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn error_proto_chain_subtype_points_to_error() {
|
|
let mut vm = make_vm();
|
|
let err = error::create_type_error(&mut vm, "msg");
|
|
let obj = unsafe { &*err.as_js_object_ptr() };
|
|
assert!(obj.proto().is_object());
|
|
}
|
|
|
|
#[test]
|
|
fn error_name_is_string_property() {
|
|
let mut vm = make_vm();
|
|
let err = error::error_constructor(&mut vm, &[0u8]).unwrap();
|
|
let obj = unsafe { &*err.as_js_object_ptr() };
|
|
let n = obj.prop_count();
|
|
assert_eq!(n, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_returns_string() {
|
|
let result = eval("new Error('test').toString()").unwrap();
|
|
assert!(result.is_string());
|
|
}
|
|
|
|
#[test]
|
|
fn type_error_is_defined() {
|
|
let result = eval("typeof TypeError").unwrap();
|
|
assert!(result.is_string());
|
|
}
|
|
|
|
#[test]
|
|
fn reference_error_is_defined() {
|
|
let result = eval("typeof ReferenceError").unwrap();
|
|
assert!(result.is_string());
|
|
}
|
|
|
|
#[test]
|
|
fn error_subtype_constructors_produce_named_objects() {
|
|
assert_eq!(format!("{}", eval("new Error('boom').name == 'Error'").unwrap()), "true");
|
|
assert_eq!(format!("{}", eval("new TypeError('boom').name == 'TypeError'").unwrap()), "true");
|
|
assert_eq!(format!("{}", eval("new SyntaxError('boom').name == 'SyntaxError'").unwrap()), "true");
|
|
}
|
|
|
|
// ── 原型属性与构造器修复测试 ──
|
|
|
|
#[test]
|
|
fn error_prototype_has_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some(), "Error.prototype should have 'name' property");
|
|
}
|
|
|
|
#[test]
|
|
fn error_prototype_has_message() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let msg_si = vm.kernel_core().perm_interner().intern("message").0;
|
|
let proto_ptr = P::as_ptr(&bw.error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), msg_si);
|
|
assert!(si.is_some(), "Error.prototype should have 'message' property");
|
|
}
|
|
|
|
#[test]
|
|
fn type_error_prototype_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.type_error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some(), "TypeError.prototype should have 'name'");
|
|
}
|
|
|
|
#[test]
|
|
fn reference_error_prototype_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.reference_error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn range_error_prototype_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.range_error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn syntax_error_prototype_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.syntax_error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn uri_error_prototype_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.uri_error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn eval_error_prototype_name() {
|
|
let vm = make_vm();
|
|
let bw = vm.session().builtin_world();
|
|
let name_si = vm.kernel_core().perm_interner().intern("name").0;
|
|
let proto_ptr = P::as_ptr(&bw.eval_error_proto) as *mut oxide_types::object::JsObject;
|
|
let si = vm
|
|
.kernel_core()
|
|
.shape_forge()
|
|
.lookup_position(unsafe { &*proto_ptr }.shape_id(), name_si);
|
|
assert!(si.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn error_constructor_no_own_props() {
|
|
let mut vm = make_vm();
|
|
let err = error::error_constructor(&mut vm, &[0u8]).unwrap();
|
|
let obj = unsafe { &*err.as_js_object_ptr() };
|
|
assert_eq!(obj.prop_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn error_constructor_with_message() {
|
|
let mut vm = make_vm();
|
|
let msg = vm.new_string("boom");
|
|
vm.set_reg(1, msg);
|
|
let err = error::error_constructor(&mut vm, &[0u8, 1u8]).unwrap();
|
|
let obj = unsafe { &*err.as_js_object_ptr() };
|
|
assert_eq!(obj.prop_count(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn create_type_error_no_own_name() {
|
|
let mut vm = make_vm();
|
|
let err = error::create_type_error(&mut vm, "something broken");
|
|
let obj = unsafe { &*err.as_js_object_ptr() };
|
|
assert_eq!(obj.prop_count(), 1);
|
|
}
|
|
|
|
// ── toString 测试 ──
|
|
|
|
#[test]
|
|
fn error_to_string_basic() {
|
|
let result = eval("new Error('test').toString()").unwrap();
|
|
assert!(result.is_string());
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_empty_message() {
|
|
let mut vm = make_vm();
|
|
let result = eval_in(&mut vm, "new Error().toString()").unwrap();
|
|
let s = vm.lookup_str(result);
|
|
assert_eq!(s, Some("Error".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_type_error() {
|
|
let mut vm = make_vm();
|
|
let result = eval_in(&mut vm, "new TypeError('bad').toString()").unwrap();
|
|
let s = vm.lookup_str(result);
|
|
assert_eq!(s, Some("TypeError: bad".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_name_only() {
|
|
let mut vm = make_vm();
|
|
let result = eval_in(&mut vm, "Error.prototype.toString.call({name: 'E'})").unwrap();
|
|
let s = vm.lookup_str(result);
|
|
assert_eq!(s, Some("E".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_non_object_throws() {
|
|
let mut vm = make_vm();
|
|
vm.set_reg(0, JsValue::int(42));
|
|
let result = error::error_to_string(&mut vm, &[0u8]);
|
|
match result {
|
|
oxide_runtime_api::NativeResult::Err(_) => {}
|
|
_ => panic!("expected Err, got Ok or TailCall"),
|
|
}
|
|
}
|
|
|
|
// ── 错误对象语义修复测试 ──
|
|
|
|
#[test]
|
|
fn error_message_property_non_enumerable() {
|
|
let mut vm = make_vm();
|
|
// message 描述符 enumerable=false(规范 CreateNonEnumerableDataPropertyOrThrow)。
|
|
assert_eq!(
|
|
format!(
|
|
"{}",
|
|
eval_in(&mut vm, "Object.getOwnPropertyDescriptor(new Error('msg'), 'message').enumerable").unwrap()
|
|
),
|
|
"false"
|
|
);
|
|
// 可枚举自身键为空:Object.keys / JSON.stringify 不再泄漏 message。
|
|
assert_eq!(format!("{}", eval_in(&mut vm, "Object.keys(new Error('msg')).length").unwrap()), "0");
|
|
let r = eval_in(&mut vm, "JSON.stringify(new Error('secret'))").unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("{}".to_string()));
|
|
// Error.prototype 的 name/message 同样非枚举。
|
|
assert_eq!(format!("{}", eval_in(&mut vm, "Object.keys(Error.prototype).length").unwrap()), "0");
|
|
// 子类型原型上的 name/constructor 非枚举,for-in 不泄漏。
|
|
assert_eq!(
|
|
format!(
|
|
"{}",
|
|
eval_in(&mut vm, "var s=[]; for (var k in new TypeError('t')) s.push(k); s.length").unwrap()
|
|
),
|
|
"0"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_tag_is_error() {
|
|
let mut vm = make_vm();
|
|
// Error 家族(含子类型与用户子类)经 Object.prototype.toString 得 `[object Error]`。
|
|
let r = eval_in(&mut vm, "Object.prototype.toString.call(new Error('x'))").unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
|
let r = eval_in(&mut vm, "Object.prototype.toString.call(new TypeError('x'))").unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
|
let r = eval_in(
|
|
&mut vm,
|
|
"class MyError extends Error {}; Object.prototype.toString.call(new MyError('x'))",
|
|
)
|
|
.unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
|
// Error.prototype 自身同样判定为 Error。
|
|
let r = eval_in(&mut vm, "Object.prototype.toString.call(Error.prototype)").unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn error_to_string_primitive_this_throws() {
|
|
let mut vm = make_vm();
|
|
// 规范:Error.prototype.toString 对非对象 this 抛 TypeError(不兜底为 "Error")。
|
|
let r = eval_in(&mut vm, "try { Error.prototype.toString.call(1); 'no-throw' } catch (e) { e.name }").unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("TypeError".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn error_constructor_length_is_one() {
|
|
let mut vm = make_vm();
|
|
assert_eq!(format!("{}", eval_in(&mut vm, "Error.length").unwrap()), "1");
|
|
assert_eq!(format!("{}", eval_in(&mut vm, "TypeError.length").unwrap()), "1");
|
|
assert_eq!(format!("{}", eval_in(&mut vm, "EvalError.length").unwrap()), "1");
|
|
}
|
|
|
|
#[test]
|
|
fn error_ctor_as_function_call_creates_new_object() {
|
|
let mut vm = make_vm();
|
|
// Error.call(obj) 忽略传入 this,总是返回新 Error 对象。
|
|
assert_eq!(format!("{}", eval_in(&mut vm, "var o = {x:1}; Error.call(o) === o").unwrap()), "false");
|
|
let r = eval_in(&mut vm, "var o = {x:1}; TypeError.call(o).name").unwrap();
|
|
assert_eq!(vm.lookup_str(r), Some("TypeError".to_string()));
|
|
}
|
|
|
|
// ── format_error_message 测试 ──
|
|
|
|
#[test]
|
|
fn format_error_message_both() {
|
|
let result = oxide_runtime_api::format_error_message("TypeError", "bad arg");
|
|
assert_eq!(result, "TypeError: bad arg");
|
|
}
|
|
|
|
#[test]
|
|
fn format_error_message_empty_msg() {
|
|
let result = oxide_runtime_api::format_error_message("Error", "");
|
|
assert_eq!(result, "Error");
|
|
}
|
|
|
|
#[test]
|
|
fn format_error_message_empty_name() {
|
|
let result = oxide_runtime_api::format_error_message("", "msg");
|
|
assert_eq!(result, "msg");
|
|
}
|
|
|
|
// ── stack 测试 ──
|
|
|
|
#[test]
|
|
fn error_stack_is_string() {
|
|
let allocator = oxide_parser::Allocator::default();
|
|
let program = oxide_parser::parse(&allocator, "typeof new Error().stack()").unwrap();
|
|
let module = Compiler::new().compile(&program).unwrap();
|
|
let mut vm = make_vm();
|
|
let result = vm.run(&module).unwrap();
|
|
// typeof 结果复用进程级静态串,同 VM 上读取(跨 VM 指针存活由实现保证,不做假设)。
|
|
let s = vm.lookup_str(result);
|
|
assert_eq!(s, Some("string".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn error_stack_starts_with_header() {
|
|
let mut vm = make_vm();
|
|
let result = eval_in(&mut vm, "new Error().stack()").unwrap();
|
|
let s = vm.lookup_str(result).unwrap();
|
|
assert!(s.starts_with("Error"), "stack should start with 'Error', got: {}", s);
|
|
}
|
|
|
|
#[test]
|
|
fn error_stack_frame_format() {
|
|
let mut vm = make_vm();
|
|
let result = eval_in(&mut vm, "(function foo() { return new Error('boom').stack(); })()").unwrap();
|
|
let s = vm.lookup_str(result).unwrap();
|
|
assert!(s.contains(" at "), "stack should have 4-space indent, got: {}", s);
|
|
}
|