Quellcode durchsuchen

LibJS: Implement Proxy [[Call]] and [[Construct]] traps

In order to do this, Proxy now extends Function rather than Object, and
whether or not it returns true for is_function() depends on it's
m_target.
Matthew Olsson vor 5 Jahren
Ursprung
Commit
98323e19e5

+ 9 - 0
Libraries/LibJS/Interpreter.h

@@ -136,6 +136,15 @@ public:
 
     bool in_strict_mode() const { return m_scope_stack.last().scope_node->in_strict_mode(); }
 
+    template<typename Callback>
+    void for_each_argument(Callback callback)
+    {
+        if (m_call_stack.is_empty())
+            return;
+        for (auto& value : m_call_stack.last().arguments)
+            callback(value);
+    }
+
     size_t argument_count() const
     {
         if (m_call_stack.is_empty())

+ 2 - 0
Libraries/LibJS/Runtime/ErrorTypes.h

@@ -68,6 +68,8 @@
         "Object prototype must not be %s on a super property access")                                  \
     M(ObjectPrototypeWrongType, "Prototype must be an object or null")                                 \
     M(ProxyCallWithNew, "Proxy must be called with the 'new' operator")                                \
+    M(ProxyConstructBadReturnType, "Proxy handler's construct trap violates invariant: must return"    \
+        "an object")                                                                                   \
     M(ProxyConstructorBadType, "Expected %s argument of Proxy constructor to be object, got %s")       \
     M(ProxyDefinePropExistingConfigurable, "Proxy handler's defineProperty trap violates "             \
         "invariant: a property cannot be defined as non-configurable if it already exists on the "     \

+ 1 - 1
Libraries/LibJS/Runtime/Function.h

@@ -69,7 +69,7 @@ protected:
     Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments);
 
 private:
-    virtual bool is_function() const final { return true; }
+    virtual bool is_function() const override { return true; }
     Value m_bound_this;
     Vector<Value> m_bound_arguments;
     Value m_home_object;

+ 76 - 2
Libraries/LibJS/Runtime/ProxyObject.cpp

@@ -26,6 +26,7 @@
 
 #include <LibJS/Interpreter.h>
 #include <LibJS/Runtime/Accessor.h>
+#include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/ProxyObject.h>
@@ -63,7 +64,7 @@ ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Ob
 }
 
 ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype)
-    : Object(prototype)
+    : Function(prototype)
     , m_target(target)
     , m_handler(handler)
 {
@@ -466,9 +467,82 @@ Value ProxyObject::delete_property(PropertyName name)
 
 void ProxyObject::visit_children(Cell::Visitor& visitor)
 {
-    Object::visit_children(visitor);
+    Function::visit_children(visitor);
     visitor.visit(&m_target);
     visitor.visit(&m_handler);
 }
 
+Value ProxyObject::call(Interpreter& interpreter) {
+    if (!is_function())
+        return interpreter.throw_exception<TypeError>(ErrorType::NotAFunction, Value(this).to_string_without_side_effects().characters());
+
+    if (m_is_revoked) {
+        interpreter.throw_exception<TypeError>(ErrorType::ProxyRevoked);
+        return {};
+    }
+    auto trap = m_handler.get("apply");
+    if (interpreter.exception())
+        return {};
+    if (trap.is_empty() || trap.is_undefined() || trap.is_null())
+        return static_cast<Function&>(m_target).call(interpreter);
+    if (!trap.is_function())
+        return interpreter.throw_exception<TypeError>(ErrorType::ProxyInvalidTrap, "apply");
+
+    MarkedValueList arguments(interpreter.heap());
+    arguments.values().append(Value(&m_target));
+    arguments.values().append(Value(&m_handler));
+    // FIXME: Pass global object
+    auto arguments_array = Array::create(interpreter.global_object());
+    interpreter.for_each_argument([&](auto& argument) {
+        arguments_array->indexed_properties().append(argument);
+    });
+    arguments.values().append(arguments_array);
+
+    return interpreter.call(trap.as_function(), Value(&m_handler), move(arguments));
+}
+
+Value ProxyObject::construct(Interpreter& interpreter) {
+    if (!is_function())
+        return interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, Value(this).to_string_without_side_effects().characters());
+
+    if (m_is_revoked) {
+        interpreter.throw_exception<TypeError>(ErrorType::ProxyRevoked);
+        return {};
+    }
+    auto trap = m_handler.get("construct");
+    if (interpreter.exception())
+        return {};
+    if (trap.is_empty() || trap.is_undefined() || trap.is_null())
+        return static_cast<Function&>(m_target).construct(interpreter);
+    if (!trap.is_function())
+        return interpreter.throw_exception<TypeError>(ErrorType::ProxyInvalidTrap, "construct");
+
+    MarkedValueList arguments(interpreter.heap());
+    arguments.values().append(Value(&m_target));
+    auto arguments_array = Array::create(interpreter.global_object());
+    interpreter.for_each_argument([&](auto& argument) {
+        arguments_array->indexed_properties().append(argument);
+    });
+    arguments.values().append(arguments_array);
+    // FIXME: We need access to the actual newTarget property here. This is just
+    // a quick fix
+    arguments.values().append(Value(this));
+    auto result = interpreter.call(trap.as_function(), Value(&m_handler), move(arguments));
+    if (!result.is_object())
+        return interpreter.throw_exception<TypeError>(ErrorType::ProxyConstructBadReturnType);
+    return result;
+}
+
+const FlyString& ProxyObject::name() const
+{
+    ASSERT(is_function());
+    return static_cast<Function&>(m_target).name();
+}
+
+LexicalEnvironment* ProxyObject::create_environment()
+{
+    ASSERT(is_function());
+    return static_cast<Function&>(m_target).create_environment();
+}
+
 }

+ 10 - 3
Libraries/LibJS/Runtime/ProxyObject.h

@@ -26,12 +26,12 @@
 
 #pragma once
 
-#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Function.h>
 
 namespace JS {
 
-class ProxyObject : public Object {
-    JS_OBJECT(ProxyObject, Object);
+class ProxyObject final : public Function {
+    JS_OBJECT(ProxyObject, Function);
 
 public:
     static ProxyObject* create(GlobalObject&, Object& target, Object& handler);
@@ -39,6 +39,11 @@ public:
     ProxyObject(Object& target, Object& handler, Object& prototype);
     virtual ~ProxyObject() override;
 
+    virtual Value call(Interpreter&) override;
+    virtual Value construct(Interpreter&) override;
+    virtual const FlyString& name() const override;
+    virtual LexicalEnvironment* create_environment() override;
+
     const Object& target() const { return m_target; }
     const Object& handler() const { return m_handler; }
 
@@ -59,6 +64,8 @@ public:
 private:
     virtual void visit_children(Visitor&) override;
     virtual bool is_proxy_object() const override { return true; }
+    virtual bool is_function() const override { return m_target.is_function(); }
+
     virtual bool is_array() const override { return m_target.is_array(); };
 
     Object& m_target;