mirror of
https://github.com/RGBCube/serenity
synced 2025-05-30 22:58:12 +00:00
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.
This commit is contained in:
parent
ed683663cd
commit
98323e19e5
5 changed files with 98 additions and 6 deletions
|
@ -136,6 +136,15 @@ public:
|
||||||
|
|
||||||
bool in_strict_mode() const { return m_scope_stack.last().scope_node->in_strict_mode(); }
|
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
|
size_t argument_count() const
|
||||||
{
|
{
|
||||||
if (m_call_stack.is_empty())
|
if (m_call_stack.is_empty())
|
||||||
|
|
|
@ -68,6 +68,8 @@
|
||||||
"Object prototype must not be %s on a super property access") \
|
"Object prototype must not be %s on a super property access") \
|
||||||
M(ObjectPrototypeWrongType, "Prototype must be an object or null") \
|
M(ObjectPrototypeWrongType, "Prototype must be an object or null") \
|
||||||
M(ProxyCallWithNew, "Proxy must be called with the 'new' operator") \
|
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(ProxyConstructorBadType, "Expected %s argument of Proxy constructor to be object, got %s") \
|
||||||
M(ProxyDefinePropExistingConfigurable, "Proxy handler's defineProperty trap violates " \
|
M(ProxyDefinePropExistingConfigurable, "Proxy handler's defineProperty trap violates " \
|
||||||
"invariant: a property cannot be defined as non-configurable if it already exists on the " \
|
"invariant: a property cannot be defined as non-configurable if it already exists on the " \
|
||||||
|
|
|
@ -69,7 +69,7 @@ protected:
|
||||||
Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments);
|
Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool is_function() const final { return true; }
|
virtual bool is_function() const override { return true; }
|
||||||
Value m_bound_this;
|
Value m_bound_this;
|
||||||
Vector<Value> m_bound_arguments;
|
Vector<Value> m_bound_arguments;
|
||||||
Value m_home_object;
|
Value m_home_object;
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <LibJS/Interpreter.h>
|
#include <LibJS/Interpreter.h>
|
||||||
#include <LibJS/Runtime/Accessor.h>
|
#include <LibJS/Runtime/Accessor.h>
|
||||||
|
#include <LibJS/Runtime/Array.h>
|
||||||
#include <LibJS/Runtime/Error.h>
|
#include <LibJS/Runtime/Error.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/ProxyObject.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)
|
ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype)
|
||||||
: Object(prototype)
|
: Function(prototype)
|
||||||
, m_target(target)
|
, m_target(target)
|
||||||
, m_handler(handler)
|
, m_handler(handler)
|
||||||
{
|
{
|
||||||
|
@ -466,9 +467,82 @@ Value ProxyObject::delete_property(PropertyName name)
|
||||||
|
|
||||||
void ProxyObject::visit_children(Cell::Visitor& visitor)
|
void ProxyObject::visit_children(Cell::Visitor& visitor)
|
||||||
{
|
{
|
||||||
Object::visit_children(visitor);
|
Function::visit_children(visitor);
|
||||||
visitor.visit(&m_target);
|
visitor.visit(&m_target);
|
||||||
visitor.visit(&m_handler);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <LibJS/Runtime/Object.h>
|
#include <LibJS/Runtime/Function.h>
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
class ProxyObject : public Object {
|
class ProxyObject final : public Function {
|
||||||
JS_OBJECT(ProxyObject, Object);
|
JS_OBJECT(ProxyObject, Function);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ProxyObject* create(GlobalObject&, Object& target, Object& handler);
|
static ProxyObject* create(GlobalObject&, Object& target, Object& handler);
|
||||||
|
@ -39,6 +39,11 @@ public:
|
||||||
ProxyObject(Object& target, Object& handler, Object& prototype);
|
ProxyObject(Object& target, Object& handler, Object& prototype);
|
||||||
virtual ~ProxyObject() override;
|
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& target() const { return m_target; }
|
||||||
const Object& handler() const { return m_handler; }
|
const Object& handler() const { return m_handler; }
|
||||||
|
|
||||||
|
@ -59,6 +64,8 @@ public:
|
||||||
private:
|
private:
|
||||||
virtual void visit_children(Visitor&) override;
|
virtual void visit_children(Visitor&) override;
|
||||||
virtual bool is_proxy_object() const override { return true; }
|
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(); };
|
virtual bool is_array() const override { return m_target.is_array(); };
|
||||||
|
|
||||||
Object& m_target;
|
Object& m_target;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue