/* * Copyright (c) 2020, Matthew Olsson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include namespace JS { bool static is_compatible_property_descriptor(Interpreter& interpreter, 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(interpreter, 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.proxy_prototype()); } ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype) : Function(prototype) , m_target(target) , m_handler(handler) { } ProxyObject::~ProxyObject() { } Object* ProxyObject::prototype() { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return nullptr; } auto trap = m_handler.get("getPrototypeOf"); if (interpreter().exception()) return nullptr; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.prototype(); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "getPrototypeOf"); return nullptr; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)); if (interpreter().exception()) return nullptr; if (!trap_result.is_object() && !trap_result.is_null()) { interpreter().throw_exception(ErrorType::ProxyGetPrototypeOfReturn); return nullptr; } if (m_target.is_extensible()) { if (interpreter().exception()) return nullptr; if (trap_result.is_null()) return nullptr; return &trap_result.as_object(); } auto target_proto = m_target.prototype(); if (interpreter().exception()) return nullptr; if (!same_value(interpreter(), trap_result, Value(target_proto))) { interpreter().throw_exception(ErrorType::ProxyGetPrototypeOfNonExtensible); return nullptr; } return &trap_result.as_object(); } const Object* ProxyObject::prototype() const { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return nullptr; } return const_cast(const_cast(this)->prototype()); } bool ProxyObject::set_prototype(Object* object) { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return false; } auto trap = m_handler.get("setPrototypeOf"); if (interpreter().exception()) return false; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.set_prototype(object); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "setPrototypeOf"); return false; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(Value(object)); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception() || !trap_result) return false; if (m_target.is_extensible()) return true; auto* target_proto = m_target.prototype(); if (interpreter().exception()) return false; if (!same_value(interpreter(), Value(object), Value(target_proto))) { interpreter().throw_exception(ErrorType::ProxySetPrototypeOfNonExtensible); return false; } return true; } bool ProxyObject::is_extensible() const { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return false; } auto trap = m_handler.get("isExtensible"); if (interpreter().exception()) return false; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.is_extensible(); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "isExtensible"); return {}; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception()) return false; if (trap_result != m_target.is_extensible()) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyIsExtensibleReturn); return false; } return trap_result; } bool ProxyObject::prevent_extensions() { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return false; } auto trap = m_handler.get("preventExtensions"); if (interpreter().exception()) return false; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.prevent_extensions(); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "preventExtensions"); return {}; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception()) return false; if (trap_result && m_target.is_extensible()) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyPreventExtensionsReturn); return false; } return trap_result; } Optional ProxyObject::get_own_property_descriptor(const PropertyName& name) const { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return {}; } auto trap = m_handler.get("getOwnPropertyDescriptor"); if (interpreter().exception()) return {}; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.get_own_property_descriptor(name); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "getOwnPropertyDescriptor"); return {}; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(js_string(interpreter(), name.to_string())); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)); if (interpreter().exception()) return {}; if (!trap_result.is_object() && !trap_result.is_undefined()) { interpreter().throw_exception(ErrorType::ProxyGetOwnDescriptorReturn); return {}; } auto target_desc = m_target.get_own_property_descriptor(name); if (interpreter().exception()) return {}; if (trap_result.is_undefined()) { if (!target_desc.has_value()) return {}; if (!target_desc.value().attributes.is_configurable()) { interpreter().throw_exception(ErrorType::ProxyGetOwnDescriptorNonConfigurable); return {}; } if (!m_target.is_extensible()) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyGetOwnDescriptorUndefReturn); return {}; } return {}; } auto result_desc = PropertyDescriptor::from_dictionary(interpreter(), trap_result.as_object()); if (interpreter().exception()) return {}; if (!is_compatible_property_descriptor(interpreter(), m_target.is_extensible(), result_desc, target_desc)) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyGetOwnDescriptorInvalidDescriptor); return {}; } if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) { interpreter().throw_exception(ErrorType::ProxyGetOwnDescriptorInvalidNonConfig); return {}; } return result_desc; } bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return false; } auto trap = m_handler.get("defineProperty"); if (interpreter().exception()) return false; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.define_property(property_name, descriptor, throw_exceptions); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "defineProperty"); return false; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(property_name.to_value(interpreter())); arguments.append(Value(const_cast(&descriptor))); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception() || !trap_result) return false; auto target_desc = m_target.get_own_property_descriptor(property_name); if (interpreter().exception()) return false; bool setting_config_false = false; if (descriptor.has_property("configurable") && !descriptor.get("configurable").to_boolean()) setting_config_false = true; if (interpreter().exception()) return false; if (!target_desc.has_value()) { if (!m_target.is_extensible()) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyDefinePropNonExtensible); return false; } if (setting_config_false) { interpreter().throw_exception(ErrorType::ProxyDefinePropNonConfigurableNonExisting); return false; } } else { if (!is_compatible_property_descriptor(interpreter(), m_target.is_extensible(), PropertyDescriptor::from_dictionary(interpreter(), descriptor), target_desc)) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyDefinePropIncompatibleDescriptor); return false; } if (setting_config_false && target_desc.value().attributes.is_configurable()) { interpreter().throw_exception(ErrorType::ProxyDefinePropExistingConfigurable); return false; } } return true; } bool ProxyObject::has_property(const PropertyName& name) const { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return false; } auto trap = m_handler.get("has"); if (interpreter().exception()) return false; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.has_property(name); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "has"); return false; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(js_string(interpreter(), name.to_string())); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception()) return false; if (!trap_result) { auto target_desc = m_target.get_own_property_descriptor(name); if (interpreter().exception()) return false; if (target_desc.has_value()) { if (!target_desc.value().attributes.is_configurable()) { interpreter().throw_exception(ErrorType::ProxyHasExistingNonConfigurable); return false; } if (!m_target.is_extensible()) { if (!interpreter().exception()) interpreter().throw_exception(ErrorType::ProxyHasExistingNonExtensible); return false; } } } return trap_result; } Value ProxyObject::get(const PropertyName& name, Value) const { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return {}; } auto trap = m_handler.get("get"); if (interpreter().exception()) return {}; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.get(name); if (!trap.is_function()) return interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "get"); MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(js_string(interpreter(), name.to_string())); arguments.append(Value(const_cast(this))); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)); if (interpreter().exception()) return {}; auto target_desc = m_target.get_own_property_descriptor(name); if (target_desc.has_value()) { if (interpreter().exception()) return {}; if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(interpreter(), trap_result, target_desc.value().value)) return interpreter().throw_exception(ErrorType::ProxyGetImmutableDataProperty); if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) return interpreter().throw_exception(ErrorType::ProxyGetNonConfigurableAccessor); } return trap_result; } bool ProxyObject::put(const PropertyName& name, Value value, Value) { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return false; } auto trap = m_handler.get("set"); if (interpreter().exception()) return false; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.put(name, value); if (!trap.is_function()) { interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "set"); return false; } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(js_string(interpreter(), name.to_string())); arguments.append(value); arguments.append(Value(const_cast(this))); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception() || !trap_result) return false; auto target_desc = m_target.get_own_property_descriptor(name); if (interpreter().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(interpreter(), value, target_desc.value().value)) { interpreter().throw_exception(ErrorType::ProxySetImmutableDataProperty); return false; } if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) { interpreter().throw_exception(ErrorType::ProxySetNonConfigurableAccessor); } } return true; } Value ProxyObject::delete_property(const PropertyName& name) { if (m_is_revoked) { interpreter().throw_exception(ErrorType::ProxyRevoked); return {}; } auto trap = m_handler.get("deleteProperty"); if (interpreter().exception()) return {}; if (trap.is_empty() || trap.is_undefined() || trap.is_null()) return m_target.delete_property(name); if (!trap.is_function()) return interpreter().throw_exception(ErrorType::ProxyInvalidTrap, "deleteProperty"); MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); arguments.append(js_string(interpreter(), name.to_string())); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception()) return {}; if (!trap_result) return Value(false); auto target_desc = m_target.get_own_property_descriptor(name); if (interpreter().exception()) return {}; if (!target_desc.has_value()) return Value(true); if (!target_desc.value().attributes.is_configurable()) return interpreter().throw_exception(ErrorType::ProxyDeleteNonConfigurable); return Value(true); } void ProxyObject::visit_children(Cell::Visitor& 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.append(Value(&m_target)); arguments.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.append(arguments_array); return interpreter.call(trap.as_function(), Value(&m_handler), move(arguments)); } Value ProxyObject::construct(Interpreter& interpreter, Function& new_target) { 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, new_target); if (!trap.is_function()) return interpreter.throw_exception(ErrorType::ProxyInvalidTrap, "construct"); MarkedValueList arguments(interpreter.heap()); arguments.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.append(arguments_array); arguments.append(Value(&new_target)); 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(); } }