Procházet zdrojové kódy

LibJS: Implement a very hackish "arguments" object

We now lazily create an "arguments" array inside functions when code
tries to access it.

This doesn't follow the spec at all but still covers a lot of the
basic uses of arguments, i.e "arguments.length" and "arguments[n]"
Andreas Kling před 4 roky
rodič
revize
cc14b5a6d7

+ 1 - 0
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)                                 \

+ 21 - 0
Libraries/LibJS/Runtime/VM.cpp

@@ -27,6 +27,7 @@
 #include <AK/ScopeGuard.h>
 #include <AK/StringBuilder.h>
 #include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Reference.h>
@@ -116,6 +117,8 @@ void VM::gather_roots(HashTable<Cell*>& 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())

+ 1 - 0
Libraries/LibJS/Runtime/VM.h

@@ -59,6 +59,7 @@ struct CallFrame {
     FlyString function_name;
     Value this_value;
     Vector<Value> arguments;
+    Array* arguments_object { nullptr };
     ScopeObject* scope { nullptr };
     bool is_strict_mode { false };
 };

+ 16 - 0
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);
+});
+