Skip to content

Commit a63fc31

Browse files
committed
Fix arguments.callee in Function.prototype.call/apply
- src/core/eval.rs: Handle apply property access and forwarding of function objects to populate arguments.callee - src/js_class.rs: Add callee parameter to create_arguments_object helper - src/js_function.rs: Register call/apply on Function.prototype and update implementation to use helper - js-scripts/function_arguments_callee.js: Add regression test script
1 parent b3ee868 commit a63fc31

File tree

6 files changed

+154
-39
lines changed

6 files changed

+154
-39
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ jobs:
6060
cargo run --all-features --example js -- js-scripts/es6_module_test_02.js
6161
cargo run --all-features --example js -- js-scripts/es6_module_test_03.js
6262
cargo run --all-features --example js -- js-scripts/expressions_n_operators.js
63+
cargo run --all-features --example js -- js-scripts/function_arguments_callee.js
6364
cargo run --all-features --example js -- js-scripts/function_object.js
6465
cargo run --all-features --example js -- js-scripts/globel_assert.js
6566
cargo run --all-features --example js -- js-scripts/indexed_collections.js

.github/workflows/test262.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
env:
2929
FAIL_ON_FAILURE: 'true'
3030
run: |
31-
bash ci/run_test262.sh --limit 10 --fail-on-failure --focus language
31+
bash ci/run_test262.sh --limit 20 --fail-on-failure --focus language
3232
3333
- name: Upload results
3434
if: ${{ !cancelled() }}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
function testCallee() {
3+
console.log("In testCallee, arguments.callee =", arguments.callee);
4+
if (arguments.callee !== testCallee) {
5+
throw new Error("arguments.callee is not the function itself");
6+
}
7+
return "ok";
8+
}
9+
10+
console.log("=== Testing arguments.callee property ===");
11+
{
12+
// Normal call
13+
let v = testCallee();
14+
console.log("Normal call: pass", v);
15+
16+
// Call via .call
17+
testCallee.call(null);
18+
console.log("Call via .call: pass");
19+
20+
// Call via .apply
21+
testCallee.apply(null, []);
22+
console.log("Call via .apply: pass");
23+
24+
// Closure call
25+
var f = function() {
26+
if (arguments.callee !== f) {
27+
throw new Error("arguments.callee (closure) is not the function itself");
28+
}
29+
console.log("In closure f, arguments.callee =", arguments.callee);
30+
};
31+
f();
32+
console.log("Closure call: pass");
33+
f.call({});
34+
console.log("Closure .call: pass");
35+
f.apply({}, []);
36+
console.log("Closure .apply: pass");
37+
}
38+
39+
40+
console.log("=== Checking existence of Function.prototype.call, apply, bind: ===");
41+
try {
42+
console.log("Function.prototype.call: " + Function.prototype.call);
43+
console.log("Function.prototype.apply: " + Function.prototype.apply);
44+
console.log("Function.prototype.bind: " + Function.prototype.bind);
45+
46+
var f = function() {};
47+
console.log("f.call: " + f.call);
48+
console.log("f.apply: " + f.apply);
49+
50+
} catch (e) {
51+
console.log("Error: " + e);
52+
}

src/core/eval.rs

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4202,8 +4202,8 @@ fn evaluate_expr_call<'gc>(
42024202
let f_val = if let Value::Object(obj) = &obj_val {
42034203
if let Some(val) = object_get_key_value(obj, key) {
42044204
val.borrow().clone()
4205-
} else if key.as_str() == "call" && object_get_key_value(obj, "__closure__").is_some() {
4206-
Value::Function("call".to_string())
4205+
} else if (key.as_str() == "call" || key.as_str() == "apply") && object_get_key_value(obj, "__closure__").is_some() {
4206+
Value::Function(key.to_string())
42074207
} else {
42084208
Value::Undefined
42094209
}
@@ -4214,9 +4214,9 @@ fn evaluate_expr_call<'gc>(
42144214
} else if matches!(
42154215
obj_val,
42164216
Value::Closure(_) | Value::Function(_) | Value::AsyncClosure(_) | Value::GeneratorFunction(..)
4217-
) && key == "call"
4217+
) && (key == "call" || key == "apply")
42184218
{
4219-
Value::Function("call".to_string())
4219+
Value::Function(key.to_string())
42204220
} else {
42214221
get_primitive_prototype_property(mc, env, &obj_val, &key.as_str().into())?
42224222
};
@@ -4253,8 +4253,8 @@ fn evaluate_expr_call<'gc>(
42534253
let f_val = if let Value::Object(obj) = &obj_val {
42544254
if let Some(val) = object_get_key_value(obj, key) {
42554255
val.borrow().clone()
4256-
} else if key.as_str() == "call" && object_get_key_value(obj, "__closure__").is_some() {
4257-
Value::Function("call".to_string())
4256+
} else if (key.as_str() == "call" || key.as_str() == "apply") && object_get_key_value(obj, "__closure__").is_some() {
4257+
Value::Function(key.to_string())
42584258
} else {
42594259
Value::Undefined
42604260
}
@@ -4267,9 +4267,9 @@ fn evaluate_expr_call<'gc>(
42674267
} else if matches!(
42684268
obj_val,
42694269
Value::Closure(_) | Value::Function(_) | Value::AsyncClosure(_) | Value::GeneratorFunction(..)
4270-
) && key == "call"
4270+
) && (key == "call" || key == "apply")
42714271
{
4272-
Value::Function("call".to_string())
4272+
Value::Function(key.to_string())
42734273
} else {
42744274
get_primitive_prototype_property(mc, env, &obj_val, &key.as_str().into())?
42754275
};
@@ -5995,10 +5995,10 @@ pub fn call_native_function<'gc>(
59955995
args: &[Value<'gc>],
59965996
env: &JSObjectDataPtr<'gc>,
59975997
) -> Result<Option<Value<'gc>>, EvalError<'gc>> {
5998-
if name == "call" {
5998+
if name == "call" || name == "Function.prototype.call" {
59995999
let this = this_val.ok_or_else(|| EvalError::Js(raise_eval_error!("Cannot call call without this")))?;
60006000
let new_this = args.first().cloned().unwrap_or(Value::Undefined);
6001-
let rest_args = &args[1..];
6001+
let rest_args = if args.is_empty() { &[] } else { &args[1..] };
60026002
return match this {
60036003
Value::Closure(cl) => Ok(Some(call_closure(mc, &cl, Some(new_this), rest_args, env, None)?)),
60046004

@@ -6019,7 +6019,7 @@ pub fn call_native_function<'gc>(
60196019
Value::Object(obj) => {
60206020
if let Some(cl_ptr) = object_get_key_value(&obj, "__closure__") {
60216021
match &*cl_ptr.borrow() {
6022-
Value::Closure(cl) => Ok(Some(call_closure(mc, cl, Some(new_this), rest_args, env, None)?)),
6022+
Value::Closure(cl) => Ok(Some(call_closure(mc, cl, Some(new_this), rest_args, env, Some(obj))?)),
60236023

60246024
_ => Err(EvalError::Js(raise_eval_error!("Not a function"))),
60256025
}
@@ -6031,6 +6031,54 @@ pub fn call_native_function<'gc>(
60316031
};
60326032
}
60336033

6034+
if name == "apply" || name == "Function.prototype.apply" {
6035+
let this = this_val.ok_or_else(|| EvalError::Js(raise_eval_error!("Cannot call apply without this")))?;
6036+
log::debug!("call_native_function: apply called on this={:?}", this);
6037+
let new_this = args.first().cloned().unwrap_or(Value::Undefined);
6038+
let arg_array = args.get(1).cloned().unwrap_or(Value::Undefined);
6039+
6040+
let mut rest_args = Vec::new();
6041+
if let Value::Object(obj) = arg_array
6042+
&& is_array(mc, &obj)
6043+
{
6044+
let len_val = object_get_key_value(&obj, "length").unwrap_or(Gc::new(mc, GcCell::new(Value::Undefined)));
6045+
let len = if let Value::Number(n) = *len_val.borrow() { n as usize } else { 0 };
6046+
for k in 0..len {
6047+
let item = object_get_key_value(&obj, k).unwrap_or(Gc::new(mc, GcCell::new(Value::Undefined)));
6048+
rest_args.push(item.borrow().clone());
6049+
}
6050+
}
6051+
6052+
return match this {
6053+
Value::Closure(cl) => Ok(Some(call_closure(mc, &cl, Some(new_this), &rest_args, env, None)?)),
6054+
Value::Function(func_name) => {
6055+
if let Some(res) = call_native_function(mc, &func_name, Some(new_this.clone()), &rest_args, env)? {
6056+
Ok(Some(res))
6057+
} else {
6058+
let call_env = crate::core::new_js_object_data(mc);
6059+
call_env.borrow_mut(mc).prototype = Some(*env);
6060+
call_env.borrow_mut(mc).is_function_scope = true;
6061+
object_set_key_value(mc, &call_env, "this", new_this.clone()).map_err(EvalError::Js)?;
6062+
match crate::js_function::handle_global_function(mc, &func_name, &rest_args, &call_env) {
6063+
Ok(res) => Ok(Some(res)),
6064+
Err(e) => Err(EvalError::Js(e)),
6065+
}
6066+
}
6067+
}
6068+
Value::Object(obj) => {
6069+
if let Some(cl_ptr) = object_get_key_value(&obj, "__closure__") {
6070+
match &*cl_ptr.borrow() {
6071+
Value::Closure(cl) => Ok(Some(call_closure(mc, cl, Some(new_this), &rest_args, env, Some(obj))?)),
6072+
_ => Err(EvalError::Js(raise_eval_error!("Not a function"))),
6073+
}
6074+
} else {
6075+
Err(EvalError::Js(raise_eval_error!("Not a function")))
6076+
}
6077+
}
6078+
_ => Err(EvalError::Js(raise_eval_error!("Not a function"))),
6079+
};
6080+
}
6081+
60346082
if name == "toString" {
60356083
let this = this_val.unwrap_or(Value::Undefined);
60366084
let tag = match &this {
@@ -6584,6 +6632,11 @@ pub fn call_closure<'gc>(
65846632

65856633
// Minimal arguments object: expose numeric properties and length
65866634
object_set_length(mc, &args_obj, args.len())?;
6635+
6636+
if let Some(fn_ptr) = fn_obj {
6637+
object_set_key_value(mc, &args_obj, "callee", Value::Object(fn_ptr)).map_err(EvalError::Js)?;
6638+
}
6639+
65876640
env_set(mc, &call_env, "arguments", Value::Object(args_obj)).map_err(EvalError::Js)?;
65886641
}
65896642

@@ -7097,7 +7150,7 @@ fn evaluate_expr_new<'gc>(
70977150
}
70987151
}
70997152

7100-
crate::js_class::create_arguments_object(mc, &call_env, &eval_args)?;
7153+
crate::js_class::create_arguments_object(mc, &call_env, &eval_args, None)?;
71017154

71027155
let body_clone = cl.body.clone();
71037156
match evaluate_statements_with_labels(mc, &call_env, &body_clone, &[], &[])? {

src/js_class.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,20 @@ pub fn create_arguments_object<'gc>(
9494
mc: &MutationContext<'gc>,
9595
func_env: &JSObjectDataPtr<'gc>,
9696
evaluated_args: &[Value<'gc>],
97+
callee: Option<Value<'gc>>,
9798
) -> Result<(), JSError> {
99+
// log::debug!("create_arguments_object called with callee={:?}", callee.is_some());
98100
let arguments_obj = crate::js_array::create_array(mc, func_env)?;
99101
crate::js_array::set_array_length(mc, &arguments_obj, evaluated_args.len())?;
100102
for (i, arg) in evaluated_args.iter().enumerate() {
101103
object_set_key_value(mc, &arguments_obj, i, arg.clone())?;
102104
}
105+
if let Some(c) = callee {
106+
// log::debug!("Setting arguments.callee");
107+
object_set_key_value(mc, &arguments_obj, "callee", c)?;
108+
} else {
109+
// log::debug!("create_arguments_object: callee is None");
110+
}
103111
object_set_key_value(mc, func_env, "arguments", Value::Object(arguments_obj))?;
104112
Ok(())
105113
}
@@ -182,7 +190,7 @@ pub(crate) fn evaluate_new<'gc>(
182190
)?;
183191

184192
// Create the arguments object
185-
create_arguments_object(mc, &func_env, evaluated_args)?;
193+
create_arguments_object(mc, &func_env, evaluated_args, None)?;
186194

187195
// Execute constructor body
188196
evaluate_statements(mc, &func_env, body)?;
@@ -469,7 +477,7 @@ pub(crate) fn evaluate_new<'gc>(
469477
Some(env),
470478
)?;
471479

472-
create_arguments_object(mc, &func_env, evaluated_args)?;
480+
create_arguments_object(mc, &func_env, evaluated_args, None)?;
473481

474482
// Execute function body
475483
evaluate_statements(mc, &func_env, body)?;

src/js_function.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ pub fn handle_global_function<'gc>(
318318
"Function.prototype.call" => {
319319
if let Some(this_rc) = crate::core::env_get(env, "this") {
320320
let this_val = this_rc.borrow().clone();
321+
let callee_for_arguments = this_val.clone(); // The function object being called
321322
match this_val {
322323
Value::Function(func_name) => {
323324
if func_name.starts_with("Object.prototype.") || func_name.starts_with("Array.prototype.") {
@@ -354,22 +355,25 @@ pub fn handle_global_function<'gc>(
354355
Some(env),
355356
)?;
356357

357-
let arguments_obj = crate::js_array::create_array(mc, &func_env)?;
358-
crate::js_array::set_array_length(mc, &arguments_obj, evaluated_args.len())?;
359-
for (i, arg) in evaluated_args.iter().enumerate() {
360-
object_set_key_value(mc, &arguments_obj, i, arg.clone())?;
361-
}
362-
object_set_key_value(mc, &func_env, "arguments", Value::Object(arguments_obj))?;
358+
// For raw closures (without wrapper object), we don't have a stable object identity for 'callee'.
359+
// But we can check if there's a reference to the function object in the `this` binding? No.
360+
// However, Function.prototype.call is usually called as `func.call(...)`.
361+
// The `this_val` here IS the function object (or closure).
362+
// So `callee_for_arguments` holds the correct Value::Closure or Value::Object.
363+
364+
crate::js_class::create_arguments_object(mc, &func_env, &evaluated_args, Some(callee_for_arguments))?;
363365

364366
return Ok(crate::core::evaluate_statements(mc, &func_env, body)?);
365367
}
366368
Value::Object(object) => {
369+
log::debug!("Function.prototype.call on Value::Object");
367370
if let Some(cl_rc) = object_get_key_value(&object, "__closure__")
368371
&& let Value::Closure(data) = &*cl_rc.borrow()
369372
{
370373
if args.is_empty() {
371374
return Err(raise_eval_error!("call requires a receiver"));
372375
}
376+
log::debug!("Function.prototype.call calling closure with callee={:?}", callee_for_arguments);
373377
let receiver_val = args[0].clone();
374378
let forwarded = args[1..].to_vec();
375379
let evaluated_args = forwarded.to_vec();
@@ -386,12 +390,7 @@ pub fn handle_global_function<'gc>(
386390
Some(env),
387391
)?;
388392

389-
let arguments_obj = crate::js_array::create_array(mc, &func_env)?;
390-
crate::js_array::set_array_length(mc, &arguments_obj, evaluated_args.len())?;
391-
for (i, arg) in evaluated_args.iter().enumerate() {
392-
object_set_key_value(mc, &arguments_obj, i, arg.clone())?;
393-
}
394-
object_set_key_value(mc, &func_env, "arguments", Value::Object(arguments_obj))?;
393+
crate::js_class::create_arguments_object(mc, &func_env, &evaluated_args, Some(callee_for_arguments))?;
395394

396395
return Ok(crate::core::evaluate_statements(mc, &func_env, body)?);
397396
}
@@ -407,6 +406,7 @@ pub fn handle_global_function<'gc>(
407406
"Function.prototype.apply" => {
408407
if let Some(this_rc) = crate::core::env_get(env, "this") {
409408
let this_val = this_rc.borrow().clone();
409+
let callee_for_arguments = this_val.clone(); // The function object being called
410410
match this_val {
411411
Value::Function(func_name) => {
412412
if func_name.starts_with("Object.prototype.") || func_name.starts_with("Array.prototype.") {
@@ -476,12 +476,7 @@ pub fn handle_global_function<'gc>(
476476
Some(env),
477477
)?;
478478

479-
let arguments_obj = crate::js_array::create_array(mc, &func_env)?;
480-
crate::js_array::set_array_length(mc, &arguments_obj, evaluated_args.len())?;
481-
for (i, arg) in evaluated_args.iter().enumerate() {
482-
object_set_key_value(mc, &arguments_obj, i, arg.clone())?;
483-
}
484-
object_set_key_value(mc, &func_env, "arguments", Value::Object(arguments_obj))?;
479+
crate::js_class::create_arguments_object(mc, &func_env, &evaluated_args, Some(callee_for_arguments))?;
485480

486481
return Ok(crate::core::evaluate_statements(mc, &func_env, body)?);
487482
}
@@ -524,12 +519,7 @@ pub fn handle_global_function<'gc>(
524519
Some(env),
525520
)?;
526521

527-
let arguments_obj = crate::js_array::create_array(mc, &func_env)?;
528-
crate::js_array::set_array_length(mc, &arguments_obj, evaluated_args.len())?;
529-
for (i, arg) in evaluated_args.iter().enumerate() {
530-
object_set_key_value(mc, &arguments_obj, i, arg.clone())?;
531-
}
532-
object_set_key_value(mc, &func_env, "arguments", Value::Object(arguments_obj))?;
522+
crate::js_class::create_arguments_object(mc, &func_env, &evaluated_args, Some(Value::Object(object)))?;
533523

534524
return Ok(crate::core::evaluate_statements(mc, &func_env, body)?);
535525
}
@@ -1574,6 +1564,17 @@ pub fn initialize_function<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr
15741564
// Function.prototype.bind
15751565
object_set_key_value(mc, &func_proto, "bind", Value::Function("Function.prototype.bind".to_string()))?;
15761566
func_proto.borrow_mut(mc).set_non_enumerable(crate::core::PropertyKey::from("bind"));
1567+
1568+
// Function.prototype.call
1569+
object_set_key_value(mc, &func_proto, "call", Value::Function("Function.prototype.call".to_string()))?;
1570+
func_proto.borrow_mut(mc).set_non_enumerable(crate::core::PropertyKey::from("call"));
1571+
1572+
// Function.prototype.apply
1573+
object_set_key_value(mc, &func_proto, "apply", Value::Function("Function.prototype.apply".to_string()))?;
1574+
func_proto
1575+
.borrow_mut(mc)
1576+
.set_non_enumerable(crate::core::PropertyKey::from("apply"));
1577+
15771578
func_proto
15781579
.borrow_mut(mc)
15791580
.set_non_enumerable(crate::core::PropertyKey::from("constructor"));

0 commit comments

Comments
 (0)