From bb1a98d809e3180ec224d1657433a761dee6e564 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 3 Jul 2021 22:57:21 +0100 Subject: [PATCH] LibJS: Add new PropertyDescriptor class and related abstract operations This is an implementation of 'The Property Descriptor Specification Type' and related abstract operations, namely: - IsAccessorDescriptor - IsDataDescriptor - IsGenericDescriptor - FromPropertyDescriptor - ToPropertyDescriptor - CompletePropertyDescriptor It works with Optional to enable omitting certain fields, which will eventually replace the Attribute::Has{Getter,Setter,Configurable, Enumerable,Writable} bit flags, which are awkward to work with - being able to use an initializer list with any of the possible attributes is much more convenient. Parts of the current PropertyAttributes implementation as well as the much simpler PropertyDescriptor struct in Object.h will eventually be replaced with this and completely go away. Property storage will still use the PropertyAttributes bit flags, this is for the layers above. Note that this is currently guarded behind an #if 0 as if conflicts with the existing PropertyDescriptor struct, but it's known to compile and work just fine - I simply want to have this in a separate commit, the primary object rewrite commit will be large enough as is. --- Userland/Libraries/LibJS/CMakeLists.txt | 1 + Userland/Libraries/LibJS/Forward.h | 2 + .../LibJS/Runtime/PropertyDescriptor.cpp | 195 ++++++++++++++++++ .../LibJS/Runtime/PropertyDescriptor.h | 73 +++++++ 4 files changed, 271 insertions(+) create mode 100644 Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index fb7af79148..1d384b1685 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -98,6 +98,7 @@ set(SOURCES Runtime/PromiseReaction.cpp Runtime/PromiseResolvingFunction.cpp Runtime/PropertyAttributes.cpp + Runtime/PropertyDescriptor.cpp Runtime/ProxyConstructor.cpp Runtime/ProxyObject.cpp Runtime/Reference.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index e10a0f74e9..ef249ac01a 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -144,6 +144,8 @@ class PrimitiveString; class PromiseReaction; class PromiseReactionJob; class PromiseResolveThenableJob; +class PropertyAttributes; +class PropertyDescriptor; class PropertyName; class Reference; class ScopeNode; diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp new file mode 100644 index 0000000000..ef69b75a7e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace JS { + +// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit, +// but the names conflict - this is the easiest solution. :^) +#if 0 +// 6.2.5.1 IsAccessorDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isaccessordescriptor +bool PropertyDescriptor::is_accessor_descriptor() const +{ + // 1. If Desc is undefined, return false. + + // 2. If both Desc.[[Get]] and Desc.[[Set]] are absent, return false. + if (!get.has_value() && !set.has_value()) + return false; + + // 3. Return true. + return true; +} + +// 6.2.5.2 IsDataDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isdatadescriptor +bool PropertyDescriptor::is_data_descriptor() const +{ + // 1. If Desc is undefined, return false. + + // 2. If both Desc.[[Value]] and Desc.[[Writable]] are absent, return false. + if (!value.has_value() && !writable.has_value()) + return false; + + // 3. Return true. + return true; +} + +// 6.2.5.3 IsGenericDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isgenericdescriptor +bool PropertyDescriptor::is_generic_descriptor() const +{ + // 1. If Desc is undefined, return false. + + // 2. If IsAccessorDescriptor(Desc) and IsDataDescriptor(Desc) are both false, return true. + if (!is_accessor_descriptor() && !is_data_descriptor()) + return true; + + // 3. Return false. + return false; +} + +// 6.2.5.4 FromPropertyDescriptor ( Desc ), https://tc39.es/ecma262/#sec-frompropertydescriptor +Value from_property_descriptor(GlobalObject& global_object, Optional const& property_descriptor) +{ + if (!property_descriptor.has_value()) + return js_undefined(); + auto& vm = global_object.vm(); + auto* object = Object::create(global_object, global_object.object_prototype()); + if (property_descriptor->value.has_value()) + object->create_data_property_or_throw(vm.names.value, *property_descriptor->value); + if (property_descriptor->writable.has_value()) + object->create_data_property_or_throw(vm.names.writable, Value(*property_descriptor->writable)); + if (property_descriptor->get.has_value()) + object->create_data_property_or_throw(vm.names.get, *property_descriptor->get ? Value(*property_descriptor->get) : js_undefined()); + if (property_descriptor->set.has_value()) + object->create_data_property_or_throw(vm.names.set, *property_descriptor->set ? Value(*property_descriptor->set) : js_undefined()); + if (property_descriptor->enumerable.has_value()) + object->create_data_property_or_throw(vm.names.enumerable, Value(*property_descriptor->enumerable)); + if (property_descriptor->configurable.has_value()) + object->create_data_property_or_throw(vm.names.configurable, Value(*property_descriptor->configurable)); + return object; +} + +// 6.2.5.5 ToPropertyDescriptor ( Obj ), https://tc39.es/ecma262/#sec-topropertydescriptor +PropertyDescriptor to_property_descriptor(GlobalObject& global_object, Value argument) +{ + auto& vm = global_object.vm(); + if (!argument.is_object()) { + vm.throw_exception(global_object, ErrorType::NotAnObject, argument.to_string_without_side_effects()); + return {}; + } + auto& object = argument.as_object(); + PropertyDescriptor descriptor; + auto has_enumerable = object.has_property(vm.names.enumerable); + if (vm.exception()) + return {}; + if (has_enumerable) { + auto enumerable = object.get(vm.names.enumerable); + if (vm.exception()) + return {}; + descriptor.enumerable = enumerable.to_boolean(); + } + auto has_configurable = object.has_property(vm.names.configurable); + if (vm.exception()) + return {}; + if (has_configurable) { + auto configurable = object.get(vm.names.configurable); + if (vm.exception()) + return {}; + descriptor.configurable = configurable.to_boolean(); + } + auto has_value = object.has_property(vm.names.value); + if (vm.exception()) + return {}; + if (has_value) { + auto value = object.get(vm.names.value); + if (vm.exception()) + return {}; + descriptor.value = value; + } + auto has_writable = object.has_property(vm.names.writable); + if (vm.exception()) + return {}; + if (has_writable) { + auto writable = object.get(vm.names.writable); + if (vm.exception()) + return {}; + descriptor.writable = writable.to_boolean(); + } + auto has_get = object.has_property(vm.names.get); + if (vm.exception()) + return {}; + if (has_get) { + auto getter = object.get(vm.names.get); + if (vm.exception()) + return {}; + if (!getter.is_function() && !getter.is_undefined()) { + vm.throw_exception(global_object, ErrorType::AccessorBadField, "get"); + return {}; + } + descriptor.get = getter.is_function() ? &getter.as_function() : nullptr; + } + auto has_set = object.has_property(vm.names.set); + if (vm.exception()) + return {}; + if (has_set) { + auto setter = object.get(vm.names.set); + if (vm.exception()) + return {}; + if (!setter.is_function() && !setter.is_undefined()) { + vm.throw_exception(global_object, ErrorType::AccessorBadField, "set"); + return {}; + } + descriptor.set = setter.is_function() ? &setter.as_function() : nullptr; + } + if (descriptor.get.has_value() || descriptor.set.has_value()) { + if (descriptor.value.has_value() || descriptor.writable.has_value()) { + vm.throw_exception(global_object, ErrorType::AccessorValueOrWritable); + return {}; + } + } + return descriptor; +} + +// 6.2.5.6 CompletePropertyDescriptor ( Desc ), https://tc39.es/ecma262/#sec-completepropertydescriptor +void PropertyDescriptor::complete() +{ + if (is_generic_descriptor() || is_data_descriptor()) { + if (!value.has_value()) + value = Value {}; + if (!writable.has_value()) + writable = false; + } else { + if (!get.has_value()) + get = nullptr; + if (!set.has_value()) + set = nullptr; + } + if (!enumerable.has_value()) + enumerable = false; + if (!configurable.has_value()) + configurable = false; +} + +// Non-standard, just a convenient way to get from three Optional to PropertyAttributes. +PropertyAttributes PropertyDescriptor::attributes() const +{ + u8 attributes = 0; + if (writable.value_or(false)) + attributes |= Attribute::Writable; + if (enumerable.value_or(false)) + attributes |= Attribute::Enumerable; + if (configurable.value_or(false)) + attributes |= Attribute::Configurable; + return { attributes }; +} +#endif + +} diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h new file mode 100644 index 0000000000..f505b94a21 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS { + +// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit, +// but the names conflict - this is the easiest solution. :^) +#if 0 +// 6.2.5 The Property Descriptor Specification Type, https://tc39.es/ecma262/#sec-property-descriptor-specification-type + +Value from_property_descriptor(GlobalObject&, Optional const&); +PropertyDescriptor to_property_descriptor(GlobalObject&, Value); + +class PropertyDescriptor { +public: + [[nodiscard]] bool is_accessor_descriptor() const; + [[nodiscard]] bool is_data_descriptor() const; + [[nodiscard]] bool is_generic_descriptor() const; + + [[nodiscard]] PropertyAttributes attributes() const; + + void complete(); + + // Not a standard abstract operation, but "If every field in Desc is absent". + [[nodiscard]] bool is_empty() const + { + return !value.has_value() && !get.has_value() && !set.has_value() && !writable.has_value() && !enumerable.has_value() && !configurable.has_value(); + } + + Optional value {}; + Optional get {}; + Optional set {}; + Optional writable {}; + Optional enumerable {}; + Optional configurable {}; +}; + +} + +namespace AK { + +template<> +struct Formatter : Formatter { + void format(FormatBuilder& builder, JS::PropertyDescriptor const& property_descriptor) + { + Vector parts; + if (property_descriptor.value.has_value()) + parts.append(String::formatted("[[Value]]: {}", property_descriptor.value->to_string_without_side_effects())); + if (property_descriptor.get.has_value()) + parts.append(String::formatted("[[Get]]: JS::Function* @ {:p}", *property_descriptor.get)); + if (property_descriptor.set.has_value()) + parts.append(String::formatted("[[Set]]: JS::Function* @ {:p}", *property_descriptor.set)); + if (property_descriptor.writable.has_value()) + parts.append(String::formatted("[[Writable]]: {}", *property_descriptor.writable)); + if (property_descriptor.enumerable.has_value()) + parts.append(String::formatted("[[Enumerable]]: {}", *property_descriptor.enumerable)); + if (property_descriptor.configurable.has_value()) + parts.append(String::formatted("[[Configurable]]: {}", *property_descriptor.configurable)); + Formatter::format(builder, String::formatted("PropertyDescriptor {{ {} }}", String::join(", ", parts))); + } +}; +#endif + +}