diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index ef3c274964..950b40aa3b 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/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 + 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()) diff --git a/Libraries/LibJS/Runtime/ErrorTypes.h b/Libraries/LibJS/Runtime/ErrorTypes.h index 883ed1962b..fa872355e9 100644 --- a/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/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 " \ diff --git a/Libraries/LibJS/Runtime/Function.h b/Libraries/LibJS/Runtime/Function.h index bd25fcb0e9..ada3bc81a1 100644 --- a/Libraries/LibJS/Runtime/Function.h +++ b/Libraries/LibJS/Runtime/Function.h @@ -69,7 +69,7 @@ protected: Function(Object& prototype, Value bound_this, Vector bound_arguments); private: - virtual bool is_function() const final { return true; } + virtual bool is_function() const override { return true; } Value m_bound_this; Vector m_bound_arguments; Value m_home_object; diff --git a/Libraries/LibJS/Runtime/ProxyObject.cpp b/Libraries/LibJS/Runtime/ProxyObject.cpp index 6c46dd8fa4..43959d07d5 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -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(ErrorType::NotAFunction, Value(this).to_string_without_side_effects().characters()); + + if (m_is_revoked) { + interpreter.throw_exception(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(m_target).call(interpreter); + if (!trap.is_function()) + return interpreter.throw_exception(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(ErrorType::NotAConstructor, Value(this).to_string_without_side_effects().characters()); + + if (m_is_revoked) { + interpreter.throw_exception(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(m_target).construct(interpreter); + if (!trap.is_function()) + return interpreter.throw_exception(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(ErrorType::ProxyConstructBadReturnType); + return result; +} + +const FlyString& ProxyObject::name() const +{ + ASSERT(is_function()); + return static_cast(m_target).name(); +} + +LexicalEnvironment* ProxyObject::create_environment() +{ + ASSERT(is_function()); + return static_cast(m_target).create_environment(); +} + } diff --git a/Libraries/LibJS/Runtime/ProxyObject.h b/Libraries/LibJS/Runtime/ProxyObject.h index f1b2c7bb7a..f0f1a9e3ab 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Libraries/LibJS/Runtime/ProxyObject.h @@ -26,12 +26,12 @@ #pragma once -#include +#include 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;