diff --git a/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Libraries/LibJS/Runtime/CommonPropertyNames.h index 2dd0ebe17ec..69df5905599 100644 --- a/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -62,6 +62,7 @@ namespace JS { P(abs) \ P(acosh) \ P(apply) \ + P(arguments) \ P(asIntN) \ P(asUintN) \ P(asinh) \ diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index 21da1984033..077b0a90a4a 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -116,6 +117,8 @@ void VM::gather_roots(HashTable& roots) for (auto& call_frame : m_call_stack) { if (call_frame->this_value.is_cell()) roots.set(call_frame->this_value.as_cell()); + if (call_frame->arguments_object) + roots.set(call_frame->arguments_object); for (auto& argument : call_frame->arguments) { if (argument.is_cell()) roots.set(argument.as_cell()); @@ -166,6 +169,24 @@ void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_o Value VM::get_variable(const FlyString& name, GlobalObject& global_object) { if (m_call_stack.size()) { + if (name == names.arguments) { + // HACK: Special handling for the name "arguments": + // If the name "arguments" is defined in the current scope, for example via + // a function parameter, or by a local var declaration, we use that. + // Otherwise, we return a lazily constructed Array with all the argument values. + // FIXME: Do something much more spec-compliant. + auto possible_match = current_scope()->get_from_scope(name); + if (possible_match.has_value()) + return possible_match.value().value; + if (!call_frame().arguments_object) { + call_frame().arguments_object = Array::create(global_object); + for (auto argument : call_frame().arguments) { + call_frame().arguments_object->indexed_properties().append(argument); + } + } + return call_frame().arguments_object; + } + for (auto* scope = current_scope(); scope; scope = scope->parent()) { auto possible_match = scope->get_from_scope(name); if (possible_match.has_value()) diff --git a/Libraries/LibJS/Runtime/VM.h b/Libraries/LibJS/Runtime/VM.h index 5d20a77f87a..c504d71e84c 100644 --- a/Libraries/LibJS/Runtime/VM.h +++ b/Libraries/LibJS/Runtime/VM.h @@ -59,6 +59,7 @@ struct CallFrame { FlyString function_name; Value this_value; Vector arguments; + Array* arguments_object { nullptr }; ScopeObject* scope { nullptr }; bool is_strict_mode { false }; }; diff --git a/Libraries/LibJS/Tests/arguments-object.js b/Libraries/LibJS/Tests/arguments-object.js new file mode 100644 index 00000000000..4ea19669f8d --- /dev/null +++ b/Libraries/LibJS/Tests/arguments-object.js @@ -0,0 +1,16 @@ +test("basic arguments object", () => { + function foo() { + return arguments.length; + } + expect(foo()).toBe(0); + expect(foo(1)).toBe(1); + expect(foo(1, 2)).toBe(2); + expect(foo(1, 2, 3)).toBe(3); + + function bar() { + return arguments[1]; + } + expect(bar("hello", "friends", ":^)")).toBe("friends"); + expect(bar("hello")).toBe(undefined); +}); +