
This patch adds FunctionEnvironmentRecord as a subclass of the existing DeclarativeEnvironmentRecord. Things that are specific to function environment records move into there, simplifying the base. Most of the abstract operations related to function environment records are rewritten to match the spec exactly. I also had to implement GetThisEnvironment() and GetSuperConstructor() to keep tests working after the changes, so that's nice as well. :^)
487 lines
18 KiB
C++
487 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
|
|
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibJS/Runtime/AbstractOperations.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>
|
|
|
|
namespace JS {
|
|
|
|
bool static is_compatible_property_descriptor(bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional)
|
|
{
|
|
if (!current_descriptor_optional.has_value())
|
|
return is_extensible;
|
|
auto current_descriptor = current_descriptor_optional.value();
|
|
if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter)
|
|
return true;
|
|
if (!current_descriptor.attributes.is_configurable()) {
|
|
if (new_descriptor.attributes.is_configurable())
|
|
return false;
|
|
if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable())
|
|
return false;
|
|
}
|
|
if (new_descriptor.is_generic_descriptor())
|
|
return true;
|
|
if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable())
|
|
return false;
|
|
if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) {
|
|
if (new_descriptor.attributes.is_writable())
|
|
return false;
|
|
return new_descriptor.value.is_empty() && same_value(new_descriptor.value, current_descriptor.value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler)
|
|
{
|
|
return global_object.heap().allocate<ProxyObject>(global_object, target, handler, *global_object.object_prototype());
|
|
}
|
|
|
|
ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype)
|
|
: Function(prototype)
|
|
, m_target(target)
|
|
, m_handler(handler)
|
|
{
|
|
}
|
|
|
|
ProxyObject::~ProxyObject()
|
|
{
|
|
}
|
|
|
|
Object* ProxyObject::prototype()
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return nullptr;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.getPrototypeOf);
|
|
if (vm.exception())
|
|
return nullptr;
|
|
if (!trap)
|
|
return m_target.prototype();
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target));
|
|
if (vm.exception())
|
|
return nullptr;
|
|
if (!trap_result.is_object() && !trap_result.is_null()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfReturn);
|
|
return nullptr;
|
|
}
|
|
if (m_target.is_extensible()) {
|
|
if (vm.exception())
|
|
return nullptr;
|
|
if (trap_result.is_null())
|
|
return nullptr;
|
|
return &trap_result.as_object();
|
|
}
|
|
auto target_proto = m_target.prototype();
|
|
if (vm.exception())
|
|
return nullptr;
|
|
if (!same_value(trap_result, Value(target_proto))) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfNonExtensible);
|
|
return nullptr;
|
|
}
|
|
return &trap_result.as_object();
|
|
}
|
|
|
|
const Object* ProxyObject::prototype() const
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return nullptr;
|
|
}
|
|
return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype());
|
|
}
|
|
|
|
bool ProxyObject::set_prototype(Object* object)
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.setPrototypeOf);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.set_prototype(object);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), Value(object));
|
|
if (vm.exception() || !trap_result.to_boolean())
|
|
return false;
|
|
if (m_target.is_extensible())
|
|
return true;
|
|
auto* target_proto = m_target.prototype();
|
|
if (vm.exception())
|
|
return false;
|
|
if (!same_value(Value(object), Value(target_proto))) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetPrototypeOfNonExtensible);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ProxyObject::is_extensible() const
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.isExtensible);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.is_extensible();
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target));
|
|
if (vm.exception())
|
|
return false;
|
|
if (trap_result.to_boolean() != m_target.is_extensible()) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyIsExtensibleReturn);
|
|
return false;
|
|
}
|
|
return trap_result.to_boolean();
|
|
}
|
|
|
|
bool ProxyObject::prevent_extensions()
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.preventExtensions);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.prevent_extensions();
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target));
|
|
if (vm.exception())
|
|
return false;
|
|
if (trap_result.to_boolean() && m_target.is_extensible()) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyPreventExtensionsReturn);
|
|
return false;
|
|
}
|
|
return trap_result.to_boolean();
|
|
}
|
|
|
|
Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(const PropertyName& name) const
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return {};
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.getOwnPropertyDescriptor);
|
|
if (vm.exception())
|
|
return {};
|
|
if (!trap)
|
|
return m_target.get_own_property_descriptor(name);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm));
|
|
if (vm.exception())
|
|
return {};
|
|
if (!trap_result.is_object() && !trap_result.is_undefined()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorReturn);
|
|
return {};
|
|
}
|
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
|
if (vm.exception())
|
|
return {};
|
|
if (trap_result.is_undefined()) {
|
|
if (!target_desc.has_value())
|
|
return {};
|
|
if (!target_desc.value().attributes.is_configurable()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorNonConfigurable);
|
|
return {};
|
|
}
|
|
if (!m_target.is_extensible()) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorUndefinedReturn);
|
|
return {};
|
|
}
|
|
return {};
|
|
}
|
|
auto result_desc = PropertyDescriptor::from_dictionary(vm, trap_result.as_object());
|
|
if (vm.exception())
|
|
return {};
|
|
if (!is_compatible_property_descriptor(m_target.is_extensible(), result_desc, target_desc)) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidDescriptor);
|
|
return {};
|
|
}
|
|
if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidNonConfig);
|
|
return {};
|
|
}
|
|
return result_desc;
|
|
}
|
|
|
|
bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions)
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.defineProperty);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.define_property(property_name, descriptor, throw_exceptions);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), property_name.to_value(vm), Value(const_cast<Object*>(&descriptor)));
|
|
if (vm.exception() || !trap_result.to_boolean())
|
|
return false;
|
|
auto target_desc = m_target.get_own_property_descriptor(property_name);
|
|
if (vm.exception())
|
|
return false;
|
|
bool setting_config_false = false;
|
|
if (descriptor.has_property(vm.names.configurable) && !descriptor.get(vm.names.configurable).to_boolean())
|
|
setting_config_false = true;
|
|
if (vm.exception())
|
|
return false;
|
|
if (!target_desc.has_value()) {
|
|
if (!m_target.is_extensible()) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonExtensible);
|
|
return false;
|
|
}
|
|
if (setting_config_false) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonConfigurableNonExisting);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!is_compatible_property_descriptor(m_target.is_extensible(), PropertyDescriptor::from_dictionary(vm, descriptor), target_desc)) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropIncompatibleDescriptor);
|
|
return false;
|
|
}
|
|
if (setting_config_false && target_desc.value().attributes.is_configurable()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropExistingConfigurable);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ProxyObject::has_property(const PropertyName& name) const
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.has);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.has_property(name);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm));
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap_result.to_boolean()) {
|
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
|
if (vm.exception())
|
|
return false;
|
|
if (target_desc.has_value()) {
|
|
if (!target_desc.value().attributes.is_configurable()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonConfigurable);
|
|
return false;
|
|
}
|
|
if (!m_target.is_extensible()) {
|
|
if (!vm.exception())
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonExtensible);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return trap_result.to_boolean();
|
|
}
|
|
|
|
Value ProxyObject::get(const PropertyName& name, Value receiver, AllowSideEffects allow_side_effects) const
|
|
{
|
|
auto& vm = this->vm();
|
|
if (allow_side_effects == AllowSideEffects::No) {
|
|
// Sorry, we're not going to call anything on this proxy.
|
|
return js_string(vm, "<ProxyObject>");
|
|
}
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return {};
|
|
}
|
|
if (receiver.is_empty())
|
|
receiver = Value(const_cast<ProxyObject*>(this));
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.get);
|
|
if (vm.exception())
|
|
return {};
|
|
if (!trap)
|
|
return m_target.get(name, receiver);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm), receiver);
|
|
if (vm.exception())
|
|
return {};
|
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
|
if (target_desc.has_value()) {
|
|
if (vm.exception())
|
|
return {};
|
|
if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(trap_result, target_desc.value().value)) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetImmutableDataProperty);
|
|
return {};
|
|
}
|
|
if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetNonConfigurableAccessor);
|
|
return {};
|
|
}
|
|
}
|
|
return trap_result;
|
|
}
|
|
|
|
bool ProxyObject::put(const PropertyName& name, Value value, Value receiver)
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
if (receiver.is_empty())
|
|
receiver = Value(const_cast<ProxyObject*>(this));
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.set);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.put(name, value, receiver);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm), value, receiver);
|
|
if (vm.exception() || !trap_result.to_boolean())
|
|
return false;
|
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
|
if (vm.exception())
|
|
return false;
|
|
if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) {
|
|
if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(value, target_desc.value().value)) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetImmutableDataProperty);
|
|
return false;
|
|
}
|
|
if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetNonConfigurableAccessor);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ProxyObject::delete_property(const PropertyName& name)
|
|
{
|
|
auto& vm = this->vm();
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return false;
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.deleteProperty);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap)
|
|
return m_target.delete_property(name);
|
|
auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm));
|
|
if (vm.exception())
|
|
return false;
|
|
if (!trap_result.to_boolean())
|
|
return false;
|
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
|
if (vm.exception())
|
|
return false;
|
|
if (!target_desc.has_value())
|
|
return true;
|
|
if (!target_desc.value().attributes.is_configurable()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDeleteNonConfigurable);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ProxyObject::visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
Function::visit_edges(visitor);
|
|
visitor.visit(&m_target);
|
|
visitor.visit(&m_handler);
|
|
}
|
|
|
|
Value ProxyObject::call()
|
|
{
|
|
auto& vm = this->vm();
|
|
if (!is_function()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, Value(this).to_string_without_side_effects());
|
|
return {};
|
|
}
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return {};
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.apply);
|
|
if (vm.exception())
|
|
return {};
|
|
if (!trap)
|
|
return static_cast<Function&>(m_target).call();
|
|
MarkedValueList arguments(heap());
|
|
arguments.append(Value(&m_target));
|
|
arguments.append(Value(&m_handler));
|
|
// FIXME: Pass global object
|
|
auto arguments_array = Array::create(global_object());
|
|
vm.for_each_argument([&](auto& argument) {
|
|
arguments_array->indexed_properties().append(argument);
|
|
});
|
|
arguments.append(arguments_array);
|
|
|
|
return vm.call(*trap, Value(&m_handler), move(arguments));
|
|
}
|
|
|
|
Value ProxyObject::construct(Function& new_target)
|
|
{
|
|
auto& vm = this->vm();
|
|
if (!is_function()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, Value(this).to_string_without_side_effects());
|
|
return {};
|
|
}
|
|
if (m_is_revoked) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
|
|
return {};
|
|
}
|
|
auto trap = get_method(global_object(), Value(&m_handler), vm.names.construct);
|
|
if (vm.exception())
|
|
return {};
|
|
if (!trap)
|
|
return static_cast<Function&>(m_target).construct(new_target);
|
|
MarkedValueList arguments(vm.heap());
|
|
arguments.append(Value(&m_target));
|
|
auto arguments_array = Array::create(global_object());
|
|
vm.for_each_argument([&](auto& argument) {
|
|
arguments_array->indexed_properties().append(argument);
|
|
});
|
|
arguments.append(arguments_array);
|
|
arguments.append(Value(&new_target));
|
|
auto result = vm.call(*trap, Value(&m_handler), move(arguments));
|
|
if (!result.is_object()) {
|
|
vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructBadReturnType);
|
|
return {};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const FlyString& ProxyObject::name() const
|
|
{
|
|
VERIFY(is_function());
|
|
return static_cast<Function&>(m_target).name();
|
|
}
|
|
|
|
FunctionEnvironmentRecord* ProxyObject::create_environment_record()
|
|
{
|
|
VERIFY(is_function());
|
|
return static_cast<Function&>(m_target).create_environment_record();
|
|
}
|
|
|
|
}
|