/* * Copyright (c) 2020, Matthew Olsson * Copyright (c) 2021, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace JS { bool static is_compatible_property_descriptor(bool is_extensible, PropertyDescriptor new_descriptor, Optional 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(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(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(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(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(global_object(), ErrorType::ProxyRevoked); return nullptr; } return const_cast(const_cast(this)->prototype()); } bool ProxyObject::set_prototype(Object* object) { auto& vm = this->vm(); if (m_is_revoked) { vm.throw_exception(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(global_object(), ErrorType::ProxySetPrototypeOfNonExtensible); return false; } return true; } bool ProxyObject::is_extensible() const { auto& vm = this->vm(); if (m_is_revoked) { vm.throw_exception(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(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(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(global_object(), ErrorType::ProxyPreventExtensionsReturn); return false; } return trap_result.to_boolean(); } Optional ProxyObject::get_own_property_descriptor(const PropertyName& name) const { auto& vm = this->vm(); if (m_is_revoked) { vm.throw_exception(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(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(global_object(), ErrorType::ProxyGetOwnDescriptorNonConfigurable); return {}; } if (!m_target.is_extensible()) { if (!vm.exception()) vm.throw_exception(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(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidDescriptor); return {}; } if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) { vm.throw_exception(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(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(&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(global_object(), ErrorType::ProxyDefinePropNonExtensible); return false; } if (setting_config_false) { vm.throw_exception(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(global_object(), ErrorType::ProxyDefinePropIncompatibleDescriptor); return false; } if (setting_config_false && target_desc.value().attributes.is_configurable()) { vm.throw_exception(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(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(global_object(), ErrorType::ProxyHasExistingNonConfigurable); return false; } if (!m_target.is_extensible()) { if (!vm.exception()) vm.throw_exception(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, ""); } if (m_is_revoked) { vm.throw_exception(global_object(), ErrorType::ProxyRevoked); return {}; } if (receiver.is_empty()) receiver = Value(const_cast(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(global_object(), ErrorType::ProxyGetImmutableDataProperty); return {}; } if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) { vm.throw_exception(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(global_object(), ErrorType::ProxyRevoked); return false; } if (receiver.is_empty()) receiver = Value(const_cast(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(global_object(), ErrorType::ProxySetImmutableDataProperty); return false; } if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) { vm.throw_exception(global_object(), ErrorType::ProxySetNonConfigurableAccessor); } } return true; } bool ProxyObject::delete_property(PropertyName const& name, bool force_throw_exception) { auto& vm = this->vm(); if (m_is_revoked) { vm.throw_exception(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, force_throw_exception); 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(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(global_object(), ErrorType::NotAFunction, Value(this).to_string_without_side_effects()); return {}; } if (m_is_revoked) { vm.throw_exception(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(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(global_object(), ErrorType::NotAConstructor, Value(this).to_string_without_side_effects()); return {}; } if (m_is_revoked) { vm.throw_exception(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(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(global_object(), ErrorType::ProxyConstructBadReturnType); return {}; } return result; } const FlyString& ProxyObject::name() const { VERIFY(is_function()); return static_cast(m_target).name(); } FunctionEnvironmentRecord* ProxyObject::create_environment_record() { VERIFY(is_function()); return static_cast(m_target).create_environment_record(); } }