mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 修复 Function call/apply/bind 调用非原生函数问题
This commit is contained in:
@@ -525,8 +525,8 @@ fn discover_tests(test262_root: &Path) -> Vec<PathBuf> {
|
||||
/// exactly one `TestResult`. Worker-owned state (`kernel`, `harness_sources`,
|
||||
/// `harness_cache`) never crosses a thread boundary.
|
||||
fn process_path(
|
||||
path: &Path, filter: &Option<String>, no_skip: bool, kernel: &Arc<OxideKernel>,
|
||||
harness_sources: &HarnessSources, harness_cache: &mut HarnessPrefixCache,
|
||||
path: &Path, filter: &Option<String>, no_skip: bool, kernel: &Arc<OxideKernel>, harness_sources: &HarnessSources,
|
||||
harness_cache: &mut HarnessPrefixCache,
|
||||
) -> TestResult {
|
||||
let source = match std::fs::read_to_string(path) {
|
||||
Ok(s) => s,
|
||||
@@ -691,7 +691,10 @@ fn run_tests() -> bool {
|
||||
// own kernel + harness-source registry + prefix cache. Only `PathBuf` and
|
||||
// `TestResult` (both `Send`) cross thread boundaries. Workers pull test
|
||||
// indices from a shared atomic cursor for dynamic load balancing.
|
||||
let workers = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1).min(total.max(1));
|
||||
let workers = std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1)
|
||||
.min(total.max(1));
|
||||
|
||||
eprintln!("running on {workers} worker thread(s)");
|
||||
|
||||
@@ -748,7 +751,10 @@ fn run_tests() -> bool {
|
||||
})
|
||||
.collect();
|
||||
|
||||
handles.into_iter().map(|h| h.join().expect("test262 worker thread panicked")).collect()
|
||||
handles
|
||||
.into_iter()
|
||||
.map(|h| h.join().expect("test262 worker thread panicked"))
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Reduce partial stats and restore discovery order for deterministic output.
|
||||
|
||||
@@ -2,34 +2,42 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::native::{NativeFn, NativeResult};
|
||||
use crate::native::NativeResult;
|
||||
use crate::vm::Vm;
|
||||
|
||||
/// Rebuild a typed JS error object from `call_function_sync`'s `String` error.
|
||||
///
|
||||
/// `call_function_sync` collapses thrown exceptions to a `String` shaped like
|
||||
/// `"TypeError: message"`. Native builtins must return `Err(JsValue)`, and
|
||||
/// test262 negative tests match on the error's `name`, so we parse the prefix
|
||||
/// back into the right error kind instead of flattening everything to `Error`.
|
||||
fn error_from_text(vm: &mut Vm, text: &str) -> JsValue {
|
||||
use crate::builtins::error;
|
||||
let text = text.strip_prefix("uncaught ").unwrap_or(text);
|
||||
let (kind, msg) = match text.split_once(": ") {
|
||||
Some((k, m)) => (k, m),
|
||||
None => ("", text),
|
||||
};
|
||||
match kind {
|
||||
"TypeError" => error::create_type_error(vm, msg),
|
||||
"ReferenceError" => error::create_reference_error(vm, msg),
|
||||
"RangeError" => error::create_range_error(vm, msg),
|
||||
"SyntaxError" => error::create_syntax_error(vm, msg),
|
||||
_ => error::create_error(vm, text),
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke any callee (native or user/bytecode function) with an explicit
|
||||
/// receiver and register-sourced arguments.
|
||||
///
|
||||
/// Delegates to `Vm::call_function_sync`, the engine's unified synchronous call
|
||||
/// path (also used by getter/setter dispatch). The previous hand-rolled version
|
||||
/// only supported native targets, so `Function.prototype.call/apply/bind` threw
|
||||
/// on user functions — a major test262 harness blocker.
|
||||
fn invoke_target(vm: &mut Vm, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
|
||||
if !target_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
}
|
||||
let tgt_ptr = target_val.as_js_object_ptr();
|
||||
if tgt_ptr.is_null() {
|
||||
return Err(JsValue::undefined());
|
||||
}
|
||||
let tgt = unsafe { &*tgt_ptr };
|
||||
if !tgt.is_function() || tgt.native_fn().is_none() {
|
||||
return Err(JsValue::undefined());
|
||||
}
|
||||
let func: NativeFn = unsafe { std::mem::transmute(tgt.native_fn().unwrap()) };
|
||||
|
||||
let base = 230u8;
|
||||
let n = arg_regs.len();
|
||||
let mut args_buf = [0u8; 64];
|
||||
args_buf[0] = base;
|
||||
vm.set_reg(base, this_val);
|
||||
for (i, ®) in arg_regs.iter().enumerate().take(n.min(63)) {
|
||||
vm.set_reg(base + 1 + i as u8, vm.reg(reg));
|
||||
args_buf[i + 1] = base + 1 + i as u8;
|
||||
}
|
||||
|
||||
func(vm, &args_buf[..n + 1])
|
||||
let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect();
|
||||
vm.call_function_sync(target_val, this_val, &args)
|
||||
.map_err(|msg| error_from_text(vm, &msg))
|
||||
}
|
||||
|
||||
fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -44,29 +52,9 @@ fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.and_then(|v| v.get(1).map(|b| **b))
|
||||
.unwrap_or(JsValue::undefined());
|
||||
|
||||
let n = args.len().saturating_sub(1);
|
||||
let base = 230u8;
|
||||
let mut args_buf = [0u8; 64];
|
||||
args_buf[0] = base;
|
||||
vm.set_reg(base, bound_this);
|
||||
for (i, ®) in args.iter().skip(1).enumerate().take(n.min(63)) {
|
||||
vm.set_reg(base + 1 + i as u8, vm.reg(reg));
|
||||
args_buf[i + 1] = base + 1 + i as u8;
|
||||
}
|
||||
|
||||
if !bound_target.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
}
|
||||
let tgt_ptr = bound_target.as_js_object_ptr();
|
||||
if tgt_ptr.is_null() {
|
||||
return Err(JsValue::undefined());
|
||||
}
|
||||
let tgt = unsafe { &*tgt_ptr };
|
||||
if !tgt.is_function() || tgt.native_fn().is_none() {
|
||||
return Err(JsValue::undefined());
|
||||
}
|
||||
let func: NativeFn = unsafe { std::mem::transmute(tgt.native_fn().unwrap()) };
|
||||
func(vm, &args_buf[..n + 1])
|
||||
// Forward bound call arguments (skip args[0], the bound-wrapper receiver).
|
||||
let arg_regs: Vec<u8> = args.iter().skip(1).copied().collect();
|
||||
invoke_target(vm, bound_target, bound_this, &arg_regs)
|
||||
}
|
||||
|
||||
pub fn function_call(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
@@ -445,7 +445,9 @@ impl Vm {
|
||||
})
|
||||
}
|
||||
|
||||
fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result<JsValue, String> {
|
||||
pub(crate) fn call_function_sync(
|
||||
&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue],
|
||||
) -> Result<JsValue, String> {
|
||||
if !callee.is_object() {
|
||||
return Err("TypeError: accessor is not callable".into());
|
||||
}
|
||||
@@ -861,7 +863,12 @@ impl Vm {
|
||||
}
|
||||
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
|
||||
let kind_str = self.pending_error_kind.take().unwrap_or("Error");
|
||||
let msg = format!("uncaught {kind_str}: {exc}");
|
||||
let exc_text = self.error_text(exc);
|
||||
let msg = if exc_text.starts_with(kind_str) {
|
||||
format!("uncaught {exc_text}")
|
||||
} else {
|
||||
format!("uncaught {kind_str}: {exc_text}")
|
||||
};
|
||||
Err(msg)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,34 @@ fn function_bind_creates_wrapper() {
|
||||
assert!((result.as_double() - 5.0).abs() < 0.0001);
|
||||
}
|
||||
|
||||
#[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!((result.as_double() - 5.0).abs() < 0.0001);
|
||||
}
|
||||
|
||||
#[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!((result.as_double() - 5.0).abs() < 0.0001);
|
||||
}
|
||||
|
||||
#[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!((result.as_double() - 3.0).abs() < 0.0001);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
@@ -83,7 +83,7 @@ fn regression_throw_statement_errors() {
|
||||
#[test]
|
||||
fn regression_throw_statement_preserves_type_error_kind() {
|
||||
assert_eq!(eval("try { throw new TypeError('boom') } catch (e) { e.name == 'TypeError' }"), "true");
|
||||
assert_eq!(eval("throw new TypeError('boom')"), "vm error: uncaught TypeError: {object}");
|
||||
assert_eq!(eval("throw new TypeError('boom')"), "vm error: uncaught TypeError: boom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -92,7 +92,7 @@ fn regression_throw_statement_preserves_syntax_error_kind() {
|
||||
eval("try { throw new SyntaxError('boom') } catch (e) { e.name == 'SyntaxError' }"),
|
||||
"true"
|
||||
);
|
||||
assert_eq!(eval("throw new SyntaxError('boom')"), "vm error: uncaught SyntaxError: {object}");
|
||||
assert_eq!(eval("throw new SyntaxError('boom')"), "vm error: uncaught SyntaxError: boom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -137,11 +137,17 @@ fn regression_new_expression_bytecode_constructor_error_is_catchable() {
|
||||
#[test]
|
||||
fn regression_delete_static_property_throws_type_error() {
|
||||
assert_eq!(eval("try { var o={x:1}; delete o.x; 0 } catch (e) { e.name == 'TypeError' }"), "true");
|
||||
assert_eq!(eval("var o={x:1}; delete o.x"), "vm error: uncaught TypeError: {object}");
|
||||
assert_eq!(
|
||||
eval("var o={x:1}; delete o.x"),
|
||||
"vm error: uncaught TypeError: property deletion not supported"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_delete_dynamic_property_throws_type_error() {
|
||||
assert_eq!(eval("try { var o={x:1}; delete o['x']; 0 } catch (e) { e.name == 'TypeError' }"), "true");
|
||||
assert_eq!(eval("var o={x:1}; var k='x'; delete o[k]"), "vm error: uncaught TypeError: {object}");
|
||||
assert_eq!(
|
||||
eval("var o={x:1}; var k='x'; delete o[k]"),
|
||||
"vm error: uncaught TypeError: property deletion not supported"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user