mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
389 lines
16 KiB
Rust
389 lines
16 KiB
Rust
use std::sync::Arc;
|
||
|
||
use oxide_compiler::compiler::Compiler;
|
||
use oxide_compiler::DefaultCompilerService;
|
||
use oxide_types::value::JsValue;
|
||
use oxide_vm::vm::Vm;
|
||
|
||
fn eval(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 error: {:?}", e))?;
|
||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||
vm.run(&Arc::new(module))
|
||
}
|
||
|
||
/// 数值断言:整数运算保 int,容忍 int/double 两种表示。
|
||
fn assert_num(result: JsValue, expected: f64) {
|
||
let actual = if result.is_int() { result.as_int() as f64 } else { result.as_double() };
|
||
assert!((actual - expected).abs() < 0.0001, "expected {expected}, got {actual}");
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_changes_this() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "Math.max.call(null, 10, 5)").unwrap();
|
||
assert!((result.as_double() - 10.0).abs() < 0.0001);
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_changes_this() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "Math.max.apply(null, [10, 5])").unwrap();
|
||
assert!((result.as_double() - 10.0).abs() < 0.0001);
|
||
}
|
||
|
||
#[test]
|
||
fn function_bind_creates_wrapper() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var b = Math.max.bind(null, 1); b(5)").unwrap();
|
||
assert!((result.as_double() - 5.0).abs() < 0.0001);
|
||
}
|
||
|
||
#[test]
|
||
fn function_constructor_is_global() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "typeof Function").unwrap();
|
||
let rendered = vm.lookup_str(result).unwrap_or_default();
|
||
assert_eq!(rendered, "function");
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_bind_supports_uncurried_native_methods() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var __push = Function.prototype.call.bind(Array.prototype.push); var a = []; __push(a, 'x'); a.length",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result, JsValue::int(1));
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_invokes_bytecode_function() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "function add(a, b) { return a + b; } add.call(null, 2, 3)").unwrap();
|
||
assert_num(result, 5.0);
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_invokes_bytecode_function() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "function add(a, b) { return a + b; } add.apply(null, [2, 3])").unwrap();
|
||
assert_num(result, 5.0);
|
||
}
|
||
|
||
#[test]
|
||
fn function_bind_invokes_bytecode_function() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "function add1(a) { return a + 1; } var bound = add1.bind(null); bound(2)").unwrap();
|
||
assert_num(result, 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_preserves_bytecode_throw_kind() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "function fail() { throw new TypeError('boom'); } fail.call(null)").unwrap_err();
|
||
assert!(err.contains("uncaught TypeError: boom"), "got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn function_to_string_includes_function() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "Math.max.toString()").unwrap();
|
||
assert!(result.is_string());
|
||
let rendered = vm.lookup_str(result).unwrap_or_default();
|
||
assert_eq!(rendered, "function max() { [native code] }");
|
||
}
|
||
|
||
#[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);
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_returns_object_stays_valid() {
|
||
// 回归:call_function_sync 曾在一个带独立 epoch 的子 VM 中运行字节码;
|
||
// 子 VM epoch 中分配的对象在子 VM 销毁后成为悬垂指针。
|
||
// 本测试强制在调用返回后解引用返回对象。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "function f() { return {x: 42}; } f.call(null).x").unwrap();
|
||
assert_eq!(result, JsValue::int(42));
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_returns_object_stays_valid() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "function f(a) { return {v: a + 1}; } f.apply(null, [9]).v").unwrap();
|
||
assert_num(result, 10.0);
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_passes_large_arg_array_to_bytecode_target() {
|
||
// 回归:apply 曾把实参数组静默截断到 55 个;大参数集必须完整到达目标函数。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var a = []; for (var i = 0; i < 1000; i++) { a[i] = i; } (function(){ return arguments.length; }).apply(null, a)",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result, JsValue::int(1000));
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_large_args_reach_last_element() {
|
||
// 大参数集经 spill 溢出区送达后,末位实参可被目标读取(不止计数正确)。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var a = []; for (var i = 0; i < 300; i++) { a[i] = i; } (function(){ return arguments[299]; }).apply(null, a)",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 299.0);
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_large_args_to_native_from_code_point() {
|
||
// native 目标(String.fromCodePoint)接收大实参集:整串完整拼接,验证
|
||
// harness `String.fromCodePoint.apply(null, codePoints)` 不再截断。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var a = []; for (var i = 0; i < 1000; i++) { a[i] = 65 + (i % 26); } String.fromCodePoint.apply(null, a).length",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result, JsValue::int(1000));
|
||
}
|
||
|
||
#[test]
|
||
fn getter_returns_object_stays_valid() {
|
||
// 同类 bug:字节码 accessor 经 ordinary_get 同步路径(target_reg=None)在子 VM
|
||
// 中运行,返回对象在子 VM 销毁后被释放。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var o = { get p() { return {z: 7}; } }; o.p.z").unwrap();
|
||
assert_eq!(result, JsValue::int(7));
|
||
}
|
||
|
||
#[test]
|
||
fn function_symbol_has_instance_bound() {
|
||
let mut vm = Vm::new();
|
||
// @@hasInstance 已绑定在 Function.prototype 上且可调用。
|
||
let result = eval(&mut vm, "typeof Function.prototype[Symbol.hasInstance]").unwrap();
|
||
assert_eq!(vm.lookup_str(result).unwrap_or_default(), "function");
|
||
// 非对象 / 非可调用 this → false(不抛)。
|
||
let cases = [
|
||
("Function.prototype[Symbol.hasInstance].call(42, {})", false),
|
||
("Function.prototype[Symbol.hasInstance].call({}, {})", false),
|
||
("Function.prototype[Symbol.hasInstance].call()", false),
|
||
// 左操作数非对象 → false。
|
||
("(function(){}).constructor[Symbol.hasInstance](42)", false),
|
||
// 原型链命中 / 未命中。
|
||
("var f = function(){}; var o = new f(); f[Symbol.hasInstance](o)", true),
|
||
("var f = function(){}; f[Symbol.hasInstance]({})", false),
|
||
("var f = function(){}; var o = Object.create(new f()); f[Symbol.hasInstance](o)", true),
|
||
// bound 递归到 target。
|
||
("var BC = function(){}; var bc = new BC(); BC.bind()[Symbol.hasInstance](bc)", true),
|
||
("function C(){} C.bind(null)[Symbol.hasInstance]({})", false),
|
||
];
|
||
for (src, expected) in cases {
|
||
let result = eval(&mut vm, src).unwrap();
|
||
assert_eq!(result.as_bool(), expected, "for {}", src);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn function_symbol_has_instance_poisoned_prototype_throws() {
|
||
let mut vm = Vm::new();
|
||
// 可调用但 prototype 非对象 → TypeError(OrdinaryHasInstance 唯一抛错点)。
|
||
let err = eval(
|
||
&mut vm,
|
||
"var f = function(){}; f.prototype = 1; try { f[Symbol.hasInstance]({}) } catch (e) { e }",
|
||
)
|
||
.unwrap();
|
||
let name = eval(
|
||
&mut vm,
|
||
"var f = function(){}; f.prototype = null; try { f[Symbol.hasInstance]({}) } catch (e) { e.name }",
|
||
)
|
||
.unwrap();
|
||
let _ = err;
|
||
assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError");
|
||
}
|
||
|
||
#[test]
|
||
fn bound_function_construct_semantics() {
|
||
let mut vm = Vm::new();
|
||
let num_cases = [
|
||
// 绑定实参 + 新对象 this。
|
||
("function C(v){ this.v = v; } var B = C.bind({}, 1); new B().v", 1.0),
|
||
("function C(v){ this.v = v; } var B = C.bind({}, 1); new B(2).v", 1.0),
|
||
("function C(a, b){ this.s = a + b; } var B = C.bind(null, 2); new B(3).s", 5.0),
|
||
// 多层 bound 链:绑定实参按 内层先、外层后 拼接。
|
||
(
|
||
"function C(v){ this.v = v; } var B = C.bind({}, 1); var D = B.bind({}, 2); new D().v",
|
||
1.0,
|
||
),
|
||
// native 构造器 target。
|
||
("var arr = Array.bind(null); new arr(3).length", 3.0),
|
||
];
|
||
for (src, expected) in num_cases {
|
||
let result = eval(&mut vm, src).unwrap();
|
||
assert_num(result, expected);
|
||
}
|
||
let bool_cases = [
|
||
// 构造实例原型链指向 target.prototype(new.target 替换为 target)。
|
||
("function C(){} var B = C.bind({}); new B() instanceof C", true),
|
||
// instanceof bound 递归到 target:newB 链含 C.prototype,故对 B 也为 true。
|
||
("function C(){} var B = C.bind({}); new B() instanceof B", true),
|
||
("function C(){} var B = C.bind({}); B instanceof Function", true),
|
||
// native 构造器 target。
|
||
("var arr = Array.bind(null); new arr(3) instanceof Array", true),
|
||
];
|
||
for (src, expected) in bool_cases {
|
||
let result = eval(&mut vm, src).unwrap();
|
||
assert_eq!(result.as_bool(), expected, "for {}", src);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_non_callable_this_throws_catchable_type_error() {
|
||
// 不可调用 this(实例原型链含函数)经 apply 转发:抛可捕获 TypeError,
|
||
// 不得绕过 try/catch 成为引擎级错误。
|
||
let mut vm = Vm::new();
|
||
vm.set_compiler_service(Arc::new(DefaultCompilerService));
|
||
let name = eval(
|
||
&mut vm,
|
||
"function FACTORY(){} FACTORY.prototype = Function(); var o = new FACTORY(); try { o.apply(); } catch (e) { e.name }",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError");
|
||
}
|
||
|
||
#[test]
|
||
fn function_apply_non_object_argarray_throws_type_error() {
|
||
// CreateListFromArrayLike:非对象 argArray(非 nullish)抛可捕获 TypeError;
|
||
// 目标非可调用先抛(先于 argArray 检查);null/undefined argArray 为无实参不抛。
|
||
let mut vm = Vm::new();
|
||
for src in [
|
||
"try { function f(){} f.apply(null, true) } catch (e) { e.name }",
|
||
"try { function f(){} f.apply(null, NaN) } catch (e) { e.name }",
|
||
"try { function f(){} f.apply(null, '1,2,3') } catch (e) { e.name }",
|
||
"try { function f(){} f.apply(null, Symbol()) } catch (e) { e.name }",
|
||
"try { Function.prototype.apply.call(undefined, {}, true) } catch (e) { e.name }",
|
||
] {
|
||
let name = eval(&mut vm, src).unwrap();
|
||
assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError", "for {}", src);
|
||
}
|
||
let result = eval(&mut vm, "function f(){ return arguments.length; } f.apply(null, null)").unwrap();
|
||
assert_num(result, 0.0);
|
||
let result = eval(&mut vm, "function f(){ return arguments.length; } f.apply(null, undefined)").unwrap();
|
||
assert_num(result, 0.0);
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_non_callable_target_keeps_type_error_kind() {
|
||
// call 转发到非可调用目标(原始值 thisArg):内层递归无原值可恢复时,
|
||
// 错误种类仍须保持 TypeError。
|
||
let mut vm = Vm::new();
|
||
for src in [
|
||
"try { Function.prototype.call.call(undefined, {}) } catch (e) { e.name }",
|
||
"try { Function.prototype.call.call(null, {}) } catch (e) { e.name }",
|
||
"try { Function.prototype.call.call({}, {}) } catch (e) { e.name }",
|
||
"try { Function.prototype.call.call(undefined) } catch (e) { e.name }",
|
||
] {
|
||
let name = eval(&mut vm, src).unwrap();
|
||
assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError", "for {}", src);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn function_call_callable_native_target_still_works() {
|
||
// 守卫不得误伤合法 native 目标:经 call/apply 转发 native 函数照常执行。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "Math.max.call(null, 3, 9)").unwrap();
|
||
assert_num(result, 9.0);
|
||
let result = eval(&mut vm, "Math.max.apply(null, [3, 9])").unwrap();
|
||
assert_num(result, 9.0);
|
||
}
|
||
|
||
#[test]
|
||
fn bound_function_construct_errors() {
|
||
let mut vm = Vm::new();
|
||
// 不可构造 target(arrow / native 方法)经 bound 构造 → TypeError。
|
||
let err = eval(&mut vm, "var a = (()=>{}).bind(null); try { new a() } catch (e) { e.name }").unwrap();
|
||
assert_eq!(vm.lookup_str(err).unwrap_or_default(), "TypeError");
|
||
let err = eval(&mut vm, "var m = Math.max.bind(null); try { new m() } catch (e) { e.name }").unwrap();
|
||
assert_eq!(vm.lookup_str(err).unwrap_or_default(), "TypeError");
|
||
// 派生类构造器经 bound 构造:super() 装配 this,实例属派生类。
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { constructor(v){ this.v = v; } } class D extends A { constructor(){ super(9); } } var B = D.bind({}); new B().v",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 9.0);
|
||
}
|
||
|
||
#[test]
|
||
fn bound_function_newtarget_pins() {
|
||
let mut vm = Vm::new();
|
||
// Reflect.construct 以 bound 包装为构造器:new.target 逐层替换到最内层
|
||
// target(SameValue(F, newTarget) → target),三种 newTarget 形态同值。
|
||
for src in [
|
||
"var nt; function A() { nt = new.target; } var B = A.bind(); var C = B.bind(); Reflect.construct(C, [], C); nt === A",
|
||
"var nt; function A() { nt = new.target; } var B = A.bind(); var C = B.bind(); Reflect.construct(C, [], A); nt === A",
|
||
"var nt; function A() { nt = new.target; } var B = A.bind(); var C = B.bind(); Reflect.construct(C, [], B); nt === A",
|
||
] {
|
||
let result = eval(&mut vm, src).unwrap();
|
||
assert!(result.as_bool(), "for {}", src);
|
||
}
|
||
// 构造实例原型链指向最内层 target 的 prototype。
|
||
let result = eval(
|
||
&mut vm,
|
||
"function A() {} var B = A.bind(); var C = B.bind(); Object.getPrototypeOf(Reflect.construct(C, [], C)) === A.prototype",
|
||
)
|
||
.unwrap();
|
||
assert!(result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn bound_function_length_name_pins() {
|
||
let mut vm = Vm::new();
|
||
// length:非自身 length(原型链 42 泄漏形态)→ 0;自身非 Number → 0;
|
||
// 自身 Number 经 ToIntegerOrInfinity 减绑定实参数。
|
||
let result = eval(
|
||
&mut vm,
|
||
"function bar() {} Object.setPrototypeOf(bar, {length: 42}); delete bar.length; Function.prototype.bind.call(bar, null, 1).length",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 0.0);
|
||
let result = eval(
|
||
&mut vm,
|
||
"function t() {} Object.defineProperty(t, 'length', {value: '5'}); t.bind(null, 1).length",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 0.0);
|
||
let result = eval(
|
||
&mut vm,
|
||
"function t() {} Object.defineProperty(t, 'length', {value: 3.66}); t.bind().length",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 3.0);
|
||
let result = eval(
|
||
&mut vm,
|
||
"function t() {} Object.defineProperty(t, 'length', {value: Infinity}); t.bind().length",
|
||
)
|
||
.unwrap();
|
||
assert!(result.as_double() == f64::INFINITY);
|
||
// name:getter 抛错须原值传播;chained 读 bound 包装自身 name。
|
||
let result = eval(
|
||
&mut vm,
|
||
"var threw = false; try { Object.defineProperty(function(){}, 'name', {get: function() { throw new Error('x'); }}).bind(); } catch (e) { threw = e.message === 'x'; } threw",
|
||
)
|
||
.unwrap();
|
||
assert!(result.as_bool());
|
||
let result = eval(&mut vm, "function target() {} target.bind().name").unwrap();
|
||
assert_eq!(vm.lookup_str(result).unwrap_or_default(), "bound target");
|
||
let result = eval(&mut vm, "function target() {} target.bind().bind().name").unwrap();
|
||
assert_eq!(vm.lookup_str(result).unwrap_or_default(), "bound bound target");
|
||
}
|