From b7ca0974365cb2cc9184ab19c8f3b76d1211ff75 Mon Sep 17 00:00:00 2001 From: Daniel Balla Date: Thu, 29 Oct 2020 11:10:21 +0100 Subject: [PATCH] Property key filter API (#4311) Co-authored-by: Robert Fancsik JerryScript-DCO-1.0-Signed-off-by: Daniel Balla dballa@inf.u-szeged.hu --- docs/02.API-REFERENCE.md | 54 +++++ jerry-core/api/jerry.c | 180 ++++++++++++++++ jerry-core/include/jerryscript-core.h | 26 +++ .../test-api-object-property-names.c | 202 ++++++++++++++++++ 4 files changed, 462 insertions(+) create mode 100644 tests/unit-core/test-api-object-property-names.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index d520f707c..43215bb47 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -80,6 +80,22 @@ Enum that contains JerryScript **iterator** value types: *New in version [[NEXT_RELEASE]]*. +## jerry_property_filter_t + +Enum that contains JerryScript **property filter** options bits: + + - JERRY_PROPERTY_FILTER_ALL - List all property keys independently from key type or property value attributes (equivalent to Reflect.ownKeys call) + - JERRY_PROPERTY_FILTER_TRAVERSE_PROTOTYPE_CHAIN - Include keys from the objects's prototype chain as well + - JERRY_PROPERTY_FILTER_EXLCUDE_NON_CONFIGURABLE - Exclude property key if the property is non-configurable + - JERRY_PROPERTY_FILTER_EXLCUDE_NON_ENUMERABLE - Exclude property key if the property is non-enumerable + - JERRY_PROPERTY_FILTER_EXLCUDE_NON_WRITABLE - Exclude property key if the property is non-writable + - JERRY_PROPERTY_FILTER_EXLCUDE_STRINGS - Exclude property key if it is a string + - JERRY_PROPERTY_FILTER_EXLCUDE_SYMBOLS - Exclude property key if it is a symbol + - JERRY_PROPERTY_FILTER_EXLCUDE_INTEGER_INDICES - Exclude property key if it is an integer index + - JERRY_PROPERTY_FILTER_INTEGER_INDICES_AS_NUMBER - By default integer index property keys are converted to string. Enabling this flags keeps integer index property keys as numbers + +*New in version [[NEXT_RELEASE]]*. + ## jerry_error_t Possible types of an error: @@ -7263,6 +7279,44 @@ best-practice example. - [jerry_object_native_info_t](#jerry_object_native_info_t) +## jerry_object_get_property_names + +**Summary** + +Gets the property keys for the given object using the selected filters. + +**Prototype** + +```c +jerry_value_t +jerry_object_get_property_names (jerry_value_t obj_val, + jerry_property_filter_t filter); +``` + +- `obj_val` - object value +- `filter` - any combination of [jerry_property_filter_t](#jerry_property_filter_t) options +- return value + - array containing the filtered property keys in successful operation + - error marked with error flag, otherwise + +**Example** + +```c +{ + jerry_value_t global_object = jerry_get_global_object (); + jerry_value_t keys = jerry_object_get_property_names (object, JERRY_PROPERTY_FILTER_ALL); + + ... // usage of keys + + jerry_release_value (keys); + jerry_release_value (global_object); +} +``` + +**See also** + +- [jerry_property_filter_t](#jerry_property_filter_t) + ## jerry_foreach_object_property **Summary** diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 6ae534df0..3301375d0 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -3661,6 +3661,186 @@ jerry_foreach_object_property (const jerry_value_t obj_val, /**< object value */ return false; } /* jerry_foreach_object_property */ +/** + * Gets the property keys for the given object using the selected filters. + * + * @return array containing the filtered property keys in successful operation + * value marked with error flag - otherwise + */ +jerry_value_t +jerry_object_get_property_names (const jerry_value_t obj_val, /**< object */ + jerry_property_filter_t filter) /**< property filter options */ +{ + jerry_assert_api_available (); + + if (!ecma_is_value_object (obj_val)) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (wrong_args_msg_p))); + } + + ecma_object_t *obj_p = ecma_get_object_from_value (obj_val); + ecma_object_t *obj_iter_p = obj_p; + ecma_collection_t *result_p = ecma_new_collection (); + + while (true) + { + /* Step 1. Get Object.[[OwnKeys]] */ + ecma_collection_t *prop_names_p = ecma_op_object_own_property_keys (obj_iter_p); + +#if ENABLED (JERRY_BUILTIN_PROXY) + if (prop_names_p == NULL) + { + return jerry_throw (ECMA_VALUE_ERROR); + } +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + + for (uint32_t i = 0; i < prop_names_p->item_count; i++) + { + ecma_value_t key = prop_names_p->buffer_p[i]; + ecma_string_t *key_p = ecma_get_prop_name_from_value (key); + uint32_t index = ecma_string_get_array_index (key_p); + + /* Step 2. Filter by key type */ + if (filter & (JERRY_PROPERTY_FILTER_EXLCUDE_STRINGS + | JERRY_PROPERTY_FILTER_EXLCUDE_SYMBOLS + | JERRY_PROPERTY_FILTER_EXLCUDE_INTEGER_INDICES)) + { + if (ecma_is_value_symbol (key)) + { + if (filter & JERRY_PROPERTY_FILTER_EXLCUDE_SYMBOLS) + { + continue; + } + } + else if (index != ECMA_STRING_NOT_ARRAY_INDEX) + { + if ((filter & JERRY_PROPERTY_FILTER_EXLCUDE_INTEGER_INDICES) + || ((filter & JERRY_PROPERTY_FILTER_EXLCUDE_STRINGS) + && !(filter & JERRY_PROPERTY_FILTER_INTEGER_INDICES_AS_NUMBER))) + { + continue; + } + } + else if (filter & JERRY_PROPERTY_FILTER_EXLCUDE_STRINGS) + { + continue; + } + } + + /* Step 3. Filter property attributes */ + if (filter & (JERRY_PROPERTY_FILTER_EXLCUDE_NON_CONFIGURABLE + | JERRY_PROPERTY_FILTER_EXLCUDE_NON_ENUMERABLE + | JERRY_PROPERTY_FILTER_EXLCUDE_NON_WRITABLE)) + { + ecma_property_descriptor_t prop_desc; + ecma_value_t status = ecma_op_object_get_own_property_descriptor (obj_iter_p, key_p, &prop_desc); + +#if ENABLED (JERRY_BUILTIN_PROXY) + if (ECMA_IS_VALUE_ERROR (status)) + { + ecma_collection_free (prop_names_p); + ecma_collection_free (result_p); + return jerry_throw (ECMA_VALUE_ERROR); + } +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + + JERRY_ASSERT (ecma_is_value_true (status)); + uint16_t flags = prop_desc.flags; + ecma_free_property_descriptor (&prop_desc); + + if ((!(flags & ECMA_PROP_IS_CONFIGURABLE) + && (filter & JERRY_PROPERTY_FILTER_EXLCUDE_NON_CONFIGURABLE)) + || (!(flags & ECMA_PROP_IS_ENUMERABLE) + && (filter & JERRY_PROPERTY_FILTER_EXLCUDE_NON_ENUMERABLE)) + || (!(flags & ECMA_PROP_IS_WRITABLE) + && (filter & JERRY_PROPERTY_FILTER_EXLCUDE_NON_WRITABLE))) + { + continue; + } + } + + if (index != ECMA_STRING_NOT_ARRAY_INDEX + && (filter & JERRY_PROPERTY_FILTER_INTEGER_INDICES_AS_NUMBER)) + { + ecma_deref_ecma_string (key_p); + key = ecma_make_uint32_value (index); + } + else + { + ecma_ref_ecma_string (key_p); + } + + if ((filter & JERRY_PROPERTY_FILTER_TRAVERSE_PROTOTYPE_CHAIN) && obj_iter_p != obj_p) + { + uint32_t duplicate_idx = 0; + while (duplicate_idx < result_p->item_count) + { + ecma_value_t value = result_p->buffer_p[duplicate_idx]; + JERRY_ASSERT (ecma_is_value_prop_name (value) || ecma_is_value_number (value)); + if (JERRY_UNLIKELY (ecma_is_value_number (value))) + { + if (ecma_get_number_from_value (value) == ecma_get_number_from_value (key)) + { + break; + } + } + else if (ecma_compare_ecma_strings (ecma_get_prop_name_from_value (value), key_p)) + { + break; + } + + duplicate_idx++; + } + + if (duplicate_idx == result_p->item_count) + { + ecma_collection_push_back (result_p, key); + } + } + else + { + ecma_collection_push_back (result_p, key); + } + } + + ecma_collection_free (prop_names_p); + + /* Step 4: Traverse prototype chain */ + jmem_cpointer_t parent_cp = JMEM_CP_NULL; + + if (filter & JERRY_PROPERTY_FILTER_TRAVERSE_PROTOTYPE_CHAIN) + { +#if ENABLED (JERRY_BUILTIN_PROXY) + if (ECMA_OBJECT_IS_PROXY (obj_iter_p)) + { + ecma_value_t parent = ecma_proxy_object_get_prototype_of (obj_iter_p); + + if (ECMA_IS_VALUE_ERROR (parent)) + { + ecma_collection_free (result_p); + return jerry_throw (ECMA_VALUE_ERROR); + } + + parent_cp = ecma_proxy_object_prototype_to_cp (parent); + } + else +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + { + parent_cp = ecma_op_ordinary_object_get_prototype_of (obj_iter_p); + } + } + + if (parent_cp == JMEM_CP_NULL) + { + break; + } + + obj_iter_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, parent_cp); + } + + return ecma_op_new_array_object_from_collection (result_p, false); +} /* jerry_object_get_property_names */ + /** * Resolve or reject the promise with an argument. * diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 0d7f07313..85999c79c 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -459,6 +459,30 @@ typedef enum JERRY_ITERATOR_TYPE_SET, /**< Set iterator */ } jerry_iterator_type_t; +/** + * JerryScript object property filter options. + */ +typedef enum +{ + JERRY_PROPERTY_FILTER_ALL = 0, /**< List all property keys independently + * from key type or property value attributes + * (equivalent to Reflect.ownKeys call) */ + JERRY_PROPERTY_FILTER_TRAVERSE_PROTOTYPE_CHAIN = (1 << 0), /**< Include keys from the objects's + * prototype chain as well */ + JERRY_PROPERTY_FILTER_EXLCUDE_NON_CONFIGURABLE = (1 << 1), /**< Exclude property key if + * the property is non-configurable */ + JERRY_PROPERTY_FILTER_EXLCUDE_NON_ENUMERABLE = (1 << 2), /**< Exclude property key if + * the property is non-enumerable */ + JERRY_PROPERTY_FILTER_EXLCUDE_NON_WRITABLE = (1 << 3), /**< Exclude property key if + * the property is non-writable */ + JERRY_PROPERTY_FILTER_EXLCUDE_STRINGS = (1 << 4), /**< Exclude property key if it is a string */ + JERRY_PROPERTY_FILTER_EXLCUDE_SYMBOLS = (1 << 5), /**< Exclude property key if it is a symbol */ + JERRY_PROPERTY_FILTER_EXLCUDE_INTEGER_INDICES = (1 << 6), /**< Exclude property key if it is an integer index */ + JERRY_PROPERTY_FILTER_INTEGER_INDICES_AS_NUMBER = (1 << 7), /**< By default integer index property keys are + * converted to string. Enabling this flags keeps + * integer index property keys as numbers. */ +} jerry_property_filter_t; + jerry_type_t jerry_value_get_type (const jerry_value_t value); jerry_object_type_t jerry_object_get_type (const jerry_value_t value); jerry_function_type_t jerry_function_get_type (const jerry_value_t value); @@ -628,6 +652,8 @@ bool jerry_objects_foreach_by_native_info (const jerry_object_native_info_t *nat bool jerry_foreach_object_property (const jerry_value_t obj_val, jerry_object_property_foreach_t foreach_p, void *user_data_p); +jerry_value_t jerry_object_get_property_names (const jerry_value_t obj_val, jerry_property_filter_t filter); + /** * Promise functions. */ diff --git a/tests/unit-core/test-api-object-property-names.c b/tests/unit-core/test-api-object-property-names.c new file mode 100644 index 000000000..5dbf5bca7 --- /dev/null +++ b/tests/unit-core/test-api-object-property-names.c @@ -0,0 +1,202 @@ +/* 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. + */ + +#include "jerryscript.h" +#include "jerryscript-port.h" +#include "jerryscript-port-default.h" +#include "test-common.h" + +static const char *prop_names[] = +{ + "val1", + "val2", + "val3", + "val4", + "val5", + "37", + "symbol" +}; + +static jerry_char_t buffer[256] = { 0 }; + +static void create_and_set_property (const jerry_value_t object, const char *prop_name) +{ + jerry_value_t jprop_name = jerry_create_string ((const jerry_char_t *) prop_name); + jerry_value_t ret_val = jerry_set_property (object, jprop_name, jerry_create_undefined ()); + + jerry_release_value (jprop_name); + jerry_release_value (ret_val); +} /* create_and_set_property */ + +static void compare_prop_name (const jerry_value_t object, const char *prop_name, uint32_t idx) +{ + jerry_value_t name = jerry_get_property_by_index (object, idx); + TEST_ASSERT (jerry_value_is_string (name) || jerry_value_is_number (name)); + if (jerry_value_is_string (name)) + { + jerry_size_t name_size = jerry_get_string_size (name); + TEST_ASSERT (name_size < sizeof (buffer)); + jerry_size_t ret_size = jerry_string_to_char_buffer (name, buffer, sizeof (buffer)); + TEST_ASSERT (name_size == ret_size); + buffer[name_size] = '\0'; + TEST_ASSERT (strcmp ((const char *) buffer, prop_name) == 0); + } + else + { + TEST_ASSERT ((int) jerry_get_number_value (name) == atoi (prop_name)); + } + + jerry_release_value (name); +} /* compare_prop_name */ + +static void define_property (const jerry_value_t object, + const char *prop_name, + jerry_property_descriptor_t *prop_desc_p, + bool is_symbol) +{ + jerry_value_t jname = jerry_create_string ((const jerry_char_t *) prop_name); + jerry_value_t ret_val; + if (is_symbol) + { + jerry_value_t symbol = jerry_create_symbol (jname); + ret_val = jerry_define_own_property (object, symbol, prop_desc_p); + jerry_release_value (symbol); + } + else + { + ret_val = jerry_define_own_property (object, jname, prop_desc_p); + } + + jerry_release_value (jname); + jerry_release_value (ret_val); +} /* define_property */ + +int +main (void) +{ + if (!jerry_is_feature_enabled (JERRY_FEATURE_SYMBOL)) + { + return 0; + } + + TEST_INIT (); + jerry_init (JERRY_INIT_EMPTY); + + jerry_value_t error_value = jerry_object_get_property_names (jerry_create_undefined (), JERRY_PROPERTY_FILTER_ALL); + TEST_ASSERT (jerry_value_is_error (error_value) && jerry_get_error_type (error_value) == JERRY_ERROR_TYPE); + jerry_release_value (error_value); + + jerry_value_t test_object = jerry_create_object (); + create_and_set_property (test_object, prop_names[0]); + create_and_set_property (test_object, prop_names[1]); + + jerry_value_t names; + + jerry_property_descriptor_t prop_desc; + jerry_init_property_descriptor_fields (&prop_desc); + prop_desc.is_configurable_defined = true; + prop_desc.is_configurable = true; + prop_desc.is_writable_defined = true; + prop_desc.is_writable = true; + prop_desc.is_enumerable_defined = true; + + // Test enumerable - non-enumerable filter + prop_desc.is_enumerable = false; + define_property (test_object, prop_names[2], &prop_desc, false); + names = jerry_object_get_property_names (test_object, + JERRY_PROPERTY_FILTER_ALL | JERRY_PROPERTY_FILTER_EXLCUDE_NON_ENUMERABLE); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 2); + jerry_release_value (names); + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 3); + compare_prop_name (names, prop_names[2], 2); + jerry_release_value (names); + prop_desc.is_enumerable = true; + + // Test configurable - non-configurable filter + prop_desc.is_configurable = false; + define_property (test_object, prop_names[3], &prop_desc, false); + names = jerry_object_get_property_names (test_object, + JERRY_PROPERTY_FILTER_ALL | JERRY_PROPERTY_FILTER_EXLCUDE_NON_CONFIGURABLE); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 3); + jerry_release_value (names); + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 4); + compare_prop_name (names, prop_names[3], 3); + jerry_release_value (names); + prop_desc.is_configurable = true; + + // Test writable - non-writable filter + prop_desc.is_writable = false; + define_property (test_object, prop_names[4], &prop_desc, false); + names = jerry_object_get_property_names (test_object, + JERRY_PROPERTY_FILTER_ALL | JERRY_PROPERTY_FILTER_EXLCUDE_NON_WRITABLE); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 4); + jerry_release_value (names); + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 5); + compare_prop_name (names, prop_names[4], 4); + jerry_release_value (names); + prop_desc.is_writable = true; + + // Test all property filter + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL); + jerry_length_t array_len = jerry_get_array_length (names); + TEST_ASSERT (array_len == (uint32_t) 5); + + for (uint32_t i = 0; i < array_len; i++) + { + compare_prop_name (names, prop_names[i], i); + } + + jerry_release_value (names); + + // Test number and string index exclusion + define_property (test_object, prop_names[5], &prop_desc, false); + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL + | JERRY_PROPERTY_FILTER_EXLCUDE_STRINGS + | JERRY_PROPERTY_FILTER_INTEGER_INDICES_AS_NUMBER); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 1); + compare_prop_name (names, prop_names[5], 0); + jerry_release_value (names); + names = jerry_object_get_property_names (test_object, + JERRY_PROPERTY_FILTER_ALL | JERRY_PROPERTY_FILTER_EXLCUDE_INTEGER_INDICES); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 5); + jerry_release_value (names); + + // Test prototype chain traversion + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 6); + jerry_release_value (names); + names = jerry_object_get_property_names (test_object, + JERRY_PROPERTY_FILTER_ALL | JERRY_PROPERTY_FILTER_TRAVERSE_PROTOTYPE_CHAIN); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 18); + jerry_release_value (names); + + // Test symbol exclusion + define_property (test_object, prop_names[6], &prop_desc, true); + names = jerry_object_get_property_names (test_object, + JERRY_PROPERTY_FILTER_ALL | JERRY_PROPERTY_FILTER_EXLCUDE_SYMBOLS); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 6); + jerry_release_value (names); + names = jerry_object_get_property_names (test_object, JERRY_PROPERTY_FILTER_ALL); + TEST_ASSERT (jerry_get_array_length (names) == (uint32_t) 7); + jerry_release_value (names); + + jerry_free_property_descriptor_fields (&prop_desc); + jerry_release_value (test_object); + jerry_cleanup (); + return 0; +} /* main */