From 8fdbc6a85b5f1f4109560c8483a1d93724fd0203 Mon Sep 17 00:00:00 2001 From: Roland Takacs <1487864+rtakacs@users.noreply.github.com> Date: Tue, 14 Jul 2020 15:59:40 +0200 Subject: [PATCH] Implement Object.{entries, values} built-in methods (#3993) Object.keys also updated to follow the ES11 standard. JerryScript-DCO-1.0-Signed-off-by: Roland Takacs rtakacs@inf.u-szeged.hu --- jerry-core/ecma/base/ecma-globals.h | 12 ++ .../builtin-objects/ecma-builtin-helpers.c | 3 +- .../builtin-objects/ecma-builtin-object.c | 39 ++++- .../builtin-objects/ecma-builtin-object.inc.h | 4 + jerry-core/ecma/operations/ecma-objects.c | 112 +++++++++++++++ jerry-core/ecma/operations/ecma-objects.h | 2 + tests/jerry/es.next/object-entries.js | 134 ++++++++++++++++++ tests/jerry/es.next/object-keys.js | 46 ++++++ tests/jerry/es.next/object-values.js | 127 +++++++++++++++++ 9 files changed, 470 insertions(+), 9 deletions(-) create mode 100644 tests/jerry/es.next/object-entries.js create mode 100644 tests/jerry/es.next/object-keys.js create mode 100644 tests/jerry/es.next/object-values.js diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 64eb1b8d5..8027fa9fb 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -357,6 +357,18 @@ typedef enum * the fast access mode array back to normal array */ } ecma_list_properties_options_t; +/** + * Enumerable property name listing options. + */ +typedef enum +{ + ECMA_ENUMERABLE_PROPERTY_KEYS, /**< List only property names */ + ECMA_ENUMERABLE_PROPERTY_VALUES, /**< List only property values */ + ECMA_ENUMERABLE_PROPERTY_ENTRIES, /**< List both propery names and values */ + + ECMA_ENUMERABLE_PROPERTY__COUNT /**< Number of enumerable property listing types */ +} ecma_enumerable_property_names_options_t; + /** * List enumerable properties and include the prototype chain. */ diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c index 616df69b9..47439d42a 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c @@ -227,11 +227,10 @@ ecma_builtin_helper_get_to_locale_string_at_index (ecma_object_t *obj_p, /**< th } /* ecma_builtin_helper_get_to_locale_string_at_index */ /** - * The Object.keys and Object.getOwnPropertyNames routine's common part. + * The Object's 'getOwnPropertyNames' routine. * * See also: * ECMA-262 v5, 15.2.3.4 steps 2-5 - * ECMA-262 v5, 15.2.3.14 steps 3-6 * * @return ecma value - Array of property names. * Returned value must be freed with ecma_free_value. diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c index 3c3dd2565..e52ad78a5 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c @@ -14,6 +14,7 @@ */ #include "ecma-alloc.h" +#include "ecma-array-object.h" #include "ecma-builtin-helpers.h" #include "ecma-builtins.h" #include "ecma-conversion.h" @@ -58,6 +59,8 @@ enum ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_SYMBOLS, ECMA_OBJECT_ROUTINE_GET_PROTOTYPE_OF, ECMA_OBJECT_ROUTINE_KEYS, + ECMA_OBJECT_ROUTINE_VALUES, + ECMA_OBJECT_ROUTINE_ENTRIES, /* These should be in this order. */ ECMA_OBJECT_ROUTINE_FREEZE, @@ -695,19 +698,35 @@ ecma_builtin_object_object_is_extensible (ecma_object_t *obj_p) /**< routine's a } /* ecma_builtin_object_object_is_extensible */ /** - * The Object object's 'keys' routine + * Common implementation of the Object object's 'keys', 'values', 'entries' routines * * See also: - * ECMA-262 v5, 15.2.3.14 + * ECMA-262 v11, 19.1.2.17 + * ECMA-262 v11, 19.1.2.22 + * ECMA-262 v11, 19.1.2.5 * * @return ecma value * Returned value must be freed with ecma_free_value. */ static ecma_value_t -ecma_builtin_object_object_keys (ecma_object_t *obj_p) /**< routine's argument */ +ecma_builtin_object_object_keys_values_helper (ecma_object_t *obj_p, /**< routine's first argument */ + ecma_enumerable_property_names_options_t option) /**< listing option */ { - return ecma_builtin_helper_object_get_properties (obj_p, ECMA_LIST_ENUMERABLE); -} /* ecma_builtin_object_object_keys */ + /* 2. */ + ecma_collection_t *props_p = ecma_op_object_get_enumerable_property_names (obj_p, option); + + if (props_p == NULL) + { + return ECMA_VALUE_ERROR; + } + + ecma_value_t *names_buffer_p = props_p->buffer_p; + /* 3. */ + ecma_value_t array_value = ecma_op_create_array_object (names_buffer_p, props_p->item_count, false); + ecma_collection_free (props_p); + + return array_value; +} /* ecma_builtin_object_object_keys_values_helper */ /** * The Object object's 'getOwnPropertyDescriptor' routine @@ -1228,7 +1247,7 @@ ecma_builtin_object_dispatch_routine (uint16_t builtin_routine_id, /**< built-in JERRY_ASSERT (builtin_routine_id == ECMA_OBJECT_ROUTINE_DEFINE_PROPERTIES); return ecma_builtin_object_object_define_properties (obj_p, arg2); } - else if (builtin_routine_id <= ECMA_OBJECT_ROUTINE_KEYS) + else if (builtin_routine_id <= ECMA_OBJECT_ROUTINE_ENTRIES) { #if ENABLED (JERRY_ESNEXT) ecma_value_t object = ecma_op_to_object (arg1); @@ -1266,10 +1285,16 @@ ecma_builtin_object_dispatch_routine (uint16_t builtin_routine_id, /**< built-in result = ecma_builtin_object_object_get_own_property_symbols (obj_p); break; } + case ECMA_OBJECT_ROUTINE_ENTRIES: + case ECMA_OBJECT_ROUTINE_VALUES: #endif /* ENABLED (JERRY_ESNEXT) */ case ECMA_OBJECT_ROUTINE_KEYS: { - result = ecma_builtin_object_object_keys (obj_p); + JERRY_ASSERT (builtin_routine_id - ECMA_OBJECT_ROUTINE_KEYS < ECMA_ENUMERABLE_PROPERTY__COUNT); + + const int option = builtin_routine_id - ECMA_OBJECT_ROUTINE_KEYS; + result = ecma_builtin_object_object_keys_values_helper (obj_p, + (ecma_enumerable_property_names_options_t) option); break; } case ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_DESCRIPTOR: diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h index 17380c098..735d450b2 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h @@ -53,6 +53,10 @@ ROUTINE (LIT_MAGIC_STRING_PREVENT_EXTENSIONS_UL, ECMA_OBJECT_ROUTINE_PREVENT_EXT ROUTINE (LIT_MAGIC_STRING_IS_SEALED_UL, ECMA_OBJECT_ROUTINE_IS_SEALED, 1, 1) ROUTINE (LIT_MAGIC_STRING_IS_FROZEN_UL, ECMA_OBJECT_ROUTINE_IS_FROZEN, 1, 1) ROUTINE (LIT_MAGIC_STRING_IS_EXTENSIBLE, ECMA_OBJECT_ROUTINE_IS_EXTENSIBLE, 1, 1) +#if ENABLED (JERRY_ESNEXT) +ROUTINE (LIT_MAGIC_STRING_ENTRIES, ECMA_OBJECT_ROUTINE_ENTRIES, 1, 1) +ROUTINE (LIT_MAGIC_STRING_VALUES, ECMA_OBJECT_ROUTINE_VALUES, 1, 1) +#endif /* ENABLED (JERRY_ESNEXT) */ ROUTINE (LIT_MAGIC_STRING_KEYS, ECMA_OBJECT_ROUTINE_KEYS, 1, 1) ROUTINE (LIT_MAGIC_STRING_GET_OWN_PROPERTY_DESCRIPTOR_UL, ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_DESCRIPTOR, 2, 2) #if ENABLED (JERRY_ESNEXT) diff --git a/jerry-core/ecma/operations/ecma-objects.c b/jerry-core/ecma/operations/ecma-objects.c index 09ba5fdbc..390c5da08 100644 --- a/jerry-core/ecma/operations/ecma-objects.c +++ b/jerry-core/ecma/operations/ecma-objects.c @@ -1983,6 +1983,118 @@ ecma_op_object_is_prototype_of (ecma_object_t *base_p, /**< base object */ } while (true); } /* ecma_op_object_is_prototype_of */ +/** + * Object's EnumerableOwnPropertyNames operation + * + * See also: + * ECMA-262 v11, 7.3.23 + * + * @return NULL - if operation fails + * collection of property names / values / name-value pairs - otherwise + */ +ecma_collection_t * +ecma_op_object_get_enumerable_property_names (ecma_object_t *obj_p, /**< routine's first argument */ + ecma_enumerable_property_names_options_t option) /**< listing option */ +{ + /* 2. */ + ecma_collection_t *prop_names_p = ecma_op_object_get_property_names (obj_p, ECMA_LIST_NO_OPTS); + +#if ENABLED (JERRY_BUILTIN_PROXY) + if (JERRY_UNLIKELY (prop_names_p == NULL)) + { + return prop_names_p; + } +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + + ecma_value_t *names_buffer_p = prop_names_p->buffer_p; + /* 3. */ + ecma_collection_t *properties_p = ecma_new_collection (); + + /* 4. */ + for (uint32_t i = 0; i < prop_names_p->item_count; i++) + { + /* 4.a */ + JERRY_ASSERT (ecma_is_value_string (names_buffer_p[i])); + + ecma_string_t *key_p = ecma_get_string_from_value (names_buffer_p[i]); + + /* 4.a.i */ + ecma_property_descriptor_t prop_desc; + ecma_value_t status; + +#if ENABLED (JERRY_BUILTIN_PROXY) + if (ECMA_OBJECT_IS_PROXY (obj_p)) + { + status = ecma_proxy_object_get_own_property_descriptor (obj_p, key_p, &prop_desc); + } + else +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + { + status = ecma_op_object_get_own_property_descriptor (obj_p, key_p, &prop_desc); + } + + if (ECMA_IS_VALUE_ERROR (status)) + { + ecma_collection_free (prop_names_p); + ecma_collection_free (properties_p); + + return NULL; + } + + /* 4.a.ii */ + if ((prop_desc.flags & ECMA_PROP_IS_ENUMERABLE) != 0) + { + /* 4.a.ii.1 */ + if (option == ECMA_ENUMERABLE_PROPERTY_KEYS) + { + ecma_collection_push_back (properties_p, ecma_copy_value (names_buffer_p[i])); + } + else + { + /* 4.a.ii.2.a */ + ecma_value_t value = ecma_op_object_get (obj_p, key_p); + + if (ECMA_IS_VALUE_ERROR (value)) + { + ecma_collection_free (prop_names_p); + ecma_collection_free (properties_p); + + return NULL; + } + + /* 4.a.ii.2.b */ + if (option == ECMA_ENUMERABLE_PROPERTY_VALUES) + { + ecma_collection_push_back (properties_p, value); + } + else + { + /* 4.a.ii.2.c.i */ + JERRY_ASSERT (option == ECMA_ENUMERABLE_PROPERTY_ENTRIES); + + /* 4.a.ii.2.c.ii */ + ecma_object_t *entry_p = ecma_op_new_fast_array_object (2); + ecma_fast_array_set_property (entry_p, 0, names_buffer_p[i]); + ecma_fast_array_set_property (entry_p, 1, value); + ecma_free_value (value); + + /* 4.a.ii.2.c.iii */ + ecma_collection_push_back (properties_p, ecma_make_object_value (entry_p)); + } + } + } + + if (ecma_is_value_true (status)) + { + ecma_free_property_descriptor (&prop_desc); + } + } + + ecma_collection_free (prop_names_p); + + return properties_p; +} /* ecma_op_object_get_enumerable_property_names */ + /** * Get collection of property names * diff --git a/jerry-core/ecma/operations/ecma-objects.h b/jerry-core/ecma/operations/ecma-objects.h index 8c7bc4ca6..ee32dda83 100644 --- a/jerry-core/ecma/operations/ecma-objects.h +++ b/jerry-core/ecma/operations/ecma-objects.h @@ -68,6 +68,8 @@ ecma_value_t ecma_op_object_get_own_property_descriptor (ecma_object_t *object_p ecma_value_t ecma_op_object_has_instance (ecma_object_t *obj_p, ecma_value_t value); ecma_value_t ecma_op_object_is_prototype_of (ecma_object_t *base_p, ecma_object_t *target_p); ecma_collection_t * ecma_op_object_get_property_names (ecma_object_t *obj_p, uint32_t opts); +ecma_collection_t * ecma_op_object_get_enumerable_property_names (ecma_object_t *obj_p, + ecma_enumerable_property_names_options_t option); lit_magic_string_id_t ecma_object_get_class_name (ecma_object_t *obj_p); bool ecma_object_class_is (ecma_object_t *object_p, uint32_t class_id); diff --git a/tests/jerry/es.next/object-entries.js b/tests/jerry/es.next/object-entries.js new file mode 100644 index 000000000..9d3505035 --- /dev/null +++ b/tests/jerry/es.next/object-entries.js @@ -0,0 +1,134 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var object = {}; +var symbol = Symbol("symbol"); + +Object.defineProperties(object, { + a: { + value: 42, + enumerable: true + }, + b: { + value: "foo", + enumerable: false + }, + [symbol]: { + value: "symbol", + enumerable: true + } +}); + +// Object.keys and Object.entries should have the same keys +var keys = Object.keys(object); +var entries = Object.entries(object); + +assert(keys.length === entries.length); +for (let i = 0; i < keys.length; i++) { + assert(keys[i] === entries[i][0]); +} + +// Test object entries +var entries = Object.entries(object); +assert(entries instanceof Array); +assert(entries.length === 1); +assert(entries[0].length === 2); +assert(entries[0][0] === "a"); +assert(entries[0][1] === 42); + +// Test array entries +var array = [1, 2, "three"]; +var entries = Object.entries(array); + +assert(entries instanceof Array); +assert(entries.length === array.length); + +for (let i = 0; i < entries.length; i++) { + assert(entries[i][0] === i + ""); + assert(entries[i][1] === array[i]); +} + +// Test prototype chain +function Parent() {} +Parent.prototype.inheritedMethod = function() {}; + +function method() {}; +function Child() { + this.prop = 5; + this.method = method; +} + +Child.prototype = new Parent; +Child.prototype.prototypeMethod = function() {}; + +var entries = Object.entries (new Child()); +assert(entries.length === 2); +assert(entries[0][0] === "prop"); +assert(entries[0][1] === 5); +assert(entries[1][0] === "method"); +assert(entries[1][1] === method); + +// Test with primitive values +var entries = Object.entries(true); +assert(entries instanceof Array); +assert(entries.length === 0); + +try { + Object.entries(undefined); + assert(false); +} catch (e) { + assert(e instanceof TypeError) +} + +try { + Object.entries(null); + assert(false); +} catch (e) { + assert(e instanceof TypeError) +} + +// Test proxy object +var object = {}; + +Object.defineProperties(object, { + a: { + value: "foo", + enumerable: false + }, + b: { + value: "bar", + enumerable: true, + writable: false + } +}); + +var proxy = new Proxy(object, { + getOwnPropertyDescriptor: function(o, v) { + handlers.push("D"); + return Object.getOwnPropertyDescriptor(o, v); + }, + get: function(o, v) { + handlers.push("G"); + return o[v]; + } +}); + +var handlers = []; +var entries = Object.entries(proxy); + +assert(entries.length === 1); +assert(entries[0][0] === "b"); +assert(entries[0][1] === "bar"); +assert(handlers.length === 3); +assert(handlers.toString() === "D,D,G"); diff --git a/tests/jerry/es.next/object-keys.js b/tests/jerry/es.next/object-keys.js new file mode 100644 index 000000000..a45d985c1 --- /dev/null +++ b/tests/jerry/es.next/object-keys.js @@ -0,0 +1,46 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var object = {}; + +Object.defineProperties(object, { + a: { + value: "foo", + enumerable: false + }, + b: { + value: "bar", + enumerable: true, + writable: false + } +}); + +var proxy = new Proxy(object, { + getOwnPropertyDescriptor: function(o, v) { + handlers.push("D"); + return Object.getOwnPropertyDescriptor(o, v); + }, + get: function(o, v) { + handlers.push("G"); + return o[v]; + } +}); + +var handlers = []; +var keys = Object.keys(proxy); + +assert(keys.length === 1); +assert(keys[0] === "b"); +assert(handlers.length === 2); +assert(handlers.toString() === "D,D"); diff --git a/tests/jerry/es.next/object-values.js b/tests/jerry/es.next/object-values.js new file mode 100644 index 000000000..42223f83f --- /dev/null +++ b/tests/jerry/es.next/object-values.js @@ -0,0 +1,127 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Test array +var arr = ["a", "b", "c"]; +var values = Object.values(arr); + +assert(values.indexOf("a") === 0); +assert(values.indexOf("b") === 1); +assert(values.indexOf("c") === 2); +assert(values.length === 3); + +// Test object +var obj = {key1: "a", key3: "b", key2: "c", key4: "d", key5: "e"}; +var values = Object.values(obj); + +assert(values.indexOf("a") === 0); +assert(values.indexOf("b") === 1); +assert(values.indexOf("c") === 2); +assert(values.indexOf("d") === 3); +assert(values.indexOf("e") === 4); +assert(values.length === 5); + +var obj2 = {}; +Object.defineProperties(obj2, { + key_one: {enumerable: true, value: "one"}, + key_two: {enumerable: false, value: "two"}, +}); + +var values = Object.values(obj2); +// values should contain: key_one +assert(values.indexOf("one") === 0); +assert(values.indexOf("two") === -1); +assert(values.length === 1); + +// Test prototype chain +function Parent() {} +Parent.prototype.inheritedMethod = function() {}; + +function method() {}; +function Child() { + this.prop = 5; + this.method = method; +} + +Child.prototype = new Parent; +Child.prototype.prototypeMethod = function() {}; + +var values = Object.values(new Child()); + +assert(values.indexOf(5) === 0); +assert(values.indexOf(method) === 1); +assert(values.length === 2); + +// Check enumerable properties +var o = {}; + +Object.defineProperty(o, "a", { + value: "OK a", + writable: true, + enumerable: true, + configurable: true +}); + +Object.defineProperty(o, "b", { + value: "NOT_OK", + writable: true, + enumerable: false, + configurable: true +}); + +Object.defineProperty(o, "c", { + value: "OK c", + writable: true, + enumerable: true, + configurable: true +}); + +var values = Object.values(o); +assert(values.length === 2); +assert(values[0] === "OK a"); +assert(values[1] === "OK c"); + +// Test proxy object +var object = {}; + +Object.defineProperties(object, { + a: { + value: "foo", + enumerable: false + }, + b: { + value: "bar", + enumerable: true, + writable: false + } +}); + +var proxy = new Proxy(object, { + getOwnPropertyDescriptor: function(o, v) { + handlers.push("D"); + return Object.getOwnPropertyDescriptor(o, v); + }, + get: function(o, v) { + handlers.push("G"); + return o[v]; + } +}); + +var handlers = []; +var values = Object.values(proxy); + +assert(values.length === 1); +assert(values[0] === "bar") +assert(handlers.length === 3); +assert(handlers.toString() === "D,D,G");