From 4342c9ea6f117295ff015d3c8a79d0577aef87f0 Mon Sep 17 00:00:00 2001 From: Szilagyi Adam Date: Tue, 14 Apr 2020 16:57:47 +0200 Subject: [PATCH] Implement Proxy object [[OwnPropertyKeys]] internal method (#3639) The algorithm is based on ECMA-262 v6, 9.5.12 JerryScript-DCO-1.0-Signed-off-by: Adam Szilagyi aszilagy@inf.u-szeged.hu --- .../builtin-objects/ecma-builtin-reflect.c | 2 +- jerry-core/ecma/operations/ecma-conversion.c | 13 +- jerry-core/ecma/operations/ecma-conversion.h | 2 +- .../ecma/operations/ecma-proxy-object.c | 251 +++++++++++++++++- jerry-core/lit/lit-magic-strings.inc.h | 3 +- .../proxy_get_own_property_descriptor.js | 8 - tests/jerry/es2015/proxy_own_keys.js | 153 ++++++++++- 7 files changed, 413 insertions(+), 19 deletions(-) diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-reflect.c b/jerry-core/ecma/builtin-objects/ecma-builtin-reflect.c index 63faa2475..19475cde3 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-reflect.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-reflect.c @@ -198,7 +198,7 @@ ecma_builtin_reflect_dispatch_routine (uint16_t builtin_routine_id, /**< built-i return ecma_raise_type_error (ECMA_ERR_MSG ("Reflect.construct requires the second argument be an object")); } - ecma_collection_t *coll_p = ecma_op_create_list_from_array_like (arguments_list[1]); + ecma_collection_t *coll_p = ecma_op_create_list_from_array_like (arguments_list[1], false); if (coll_p == NULL) { diff --git a/jerry-core/ecma/operations/ecma-conversion.c b/jerry-core/ecma/operations/ecma-conversion.c index 2b929dec1..c6a8141c8 100644 --- a/jerry-core/ecma/operations/ecma-conversion.c +++ b/jerry-core/ecma/operations/ecma-conversion.c @@ -977,7 +977,9 @@ ecma_op_to_length (ecma_value_t value, /**< ecma value */ * NULL otherwise */ ecma_collection_t * -ecma_op_create_list_from_array_like (ecma_value_t arr) /**< array value */ +ecma_op_create_list_from_array_like (ecma_value_t arr, /**< array value */ + bool prop_names_only) /**< true - accept only property names + false - otherwise */ { /* 1. */ JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (arr)); @@ -1010,6 +1012,15 @@ ecma_op_create_list_from_array_like (ecma_value_t arr) /**< array value */ return NULL; } + if (prop_names_only + && !ecma_is_value_prop_name (next)) + { + ecma_free_value (next); + ecma_collection_free (list_ptr); + ecma_raise_type_error (ECMA_ERR_MSG ("Property name is neither Symbol nor String.")); + return NULL; + } + ecma_collection_push_back (list_ptr, next); } diff --git a/jerry-core/ecma/operations/ecma-conversion.h b/jerry-core/ecma/operations/ecma-conversion.h index 5b6b5630a..dd3dd64e6 100644 --- a/jerry-core/ecma/operations/ecma-conversion.h +++ b/jerry-core/ecma/operations/ecma-conversion.h @@ -52,7 +52,7 @@ ecma_value_t ecma_op_to_object (ecma_value_t value); ecma_value_t ecma_op_to_integer (ecma_value_t value, ecma_number_t *number_p); ecma_value_t ecma_op_to_length (ecma_value_t value, uint32_t *length); #if ENABLED (JERRY_ES2015) -ecma_collection_t *ecma_op_create_list_from_array_like (ecma_value_t arr); +ecma_collection_t *ecma_op_create_list_from_array_like (ecma_value_t arr, bool prop_names_only); #endif /* ENABLED (JERRY_ES2015) */ ecma_object_t *ecma_op_from_property_descriptor (const ecma_property_descriptor_t *src_prop_desc_p); diff --git a/jerry-core/ecma/operations/ecma-proxy-object.c b/jerry-core/ecma/operations/ecma-proxy-object.c index e4b0c21ae..2977c24b1 100644 --- a/jerry-core/ecma/operations/ecma-proxy-object.c +++ b/jerry-core/ecma/operations/ecma-proxy-object.c @@ -1131,6 +1131,119 @@ ecma_proxy_object_enumerate (ecma_object_t *obj_p) /**< proxy object */ return ecma_raise_type_error (ECMA_ERR_MSG ("UNIMPLEMENTED: Proxy.[[Enumerate]]")); } /* ecma_proxy_object_enumerate */ +/** + * Helper method for the Proxy object [[OwnPropertyKeys]] operation + * + * See also: + * ECMAScript v6, 9.5.12 steps 21. 23. + * + * @return ECMA_VALUE_ERROR - if a target key is not in the unchecked_result_keys collection + * ECMA_VALUE_EMPTY - otherwise + */ +static ecma_value_t +ecma_proxy_object_own_property_keys_helper (ecma_collection_t *target_collection, /**< target keys */ + ecma_collection_t *unchecked_result_keys, /**< unchecked keys */ + uint32_t *counter) /**< unchecked property counter */ +{ + ecma_value_t ret_value = ECMA_VALUE_EMPTY; + + for (uint32_t i = 0; i < target_collection->item_count; i++) + { + ecma_string_t *current_prop_name = ecma_get_prop_name_from_value (target_collection->buffer_p[i]); + + ret_value = ECMA_VALUE_ERROR; + + for (uint32_t j = 0; j < unchecked_result_keys->item_count; j++) + { + if (ecma_is_value_empty (unchecked_result_keys->buffer_p[j])) + { + continue; + } + + ecma_string_t *unchecked_prop_name = ecma_get_prop_name_from_value (unchecked_result_keys->buffer_p[j]); + + if (ecma_compare_ecma_strings (current_prop_name, unchecked_prop_name)) + { + ecma_deref_ecma_string (unchecked_prop_name); + ret_value = ECMA_VALUE_EMPTY; + unchecked_result_keys->buffer_p[j] = ECMA_VALUE_EMPTY; + (*counter)++; + } + } + + if (ECMA_IS_VALUE_ERROR (ret_value)) + { + break; + } + } + + return ret_value; +} /* ecma_proxy_object_own_property_keys_helper */ + +/** + * Helper method for checking the invariants in the Proxy object [[OwnPropertyKeys]] operation + * + * See also: + * ECMAScript v6, 9.5.12 steps 20-25. + * + * @return true - if none of the invariants got violated + * false - otherwise + */ +static bool +ecma_proxy_check_invariants_for_own_prop_keys (ecma_collection_t *trap_result, + ecma_collection_t *target_non_configurable_keys, + ecma_collection_t *target_configurable_keys, + ecma_value_t extensible_target) +{ + /* 20. */ + ecma_collection_t *unchecked_result_keys = ecma_new_collection (); + + ecma_collection_append (unchecked_result_keys, trap_result->buffer_p, trap_result->item_count); + + for (uint32_t i = 0; i < unchecked_result_keys->item_count; i++) + { + ecma_string_t *unchecked_prop_name = ecma_get_prop_name_from_value (unchecked_result_keys->buffer_p[i]); + ecma_ref_ecma_string (unchecked_prop_name); + } + + bool check_ok = false; + uint32_t unchecked_prop_name_counter = 0; + + /* 21. */ + if (ECMA_IS_VALUE_ERROR (ecma_proxy_object_own_property_keys_helper (target_non_configurable_keys, + unchecked_result_keys, + &unchecked_prop_name_counter))) + { + ecma_raise_type_error (ECMA_ERR_MSG ("Trap result did not include all non-configurable keys.")); + } + /* 22. */ + else if (ecma_is_value_true (extensible_target)) + { + check_ok = true; + } + /* 23. */ + else if (ECMA_IS_VALUE_ERROR (ecma_proxy_object_own_property_keys_helper (target_configurable_keys, + unchecked_result_keys, + &unchecked_prop_name_counter))) + { + ecma_raise_type_error (ECMA_ERR_MSG ("Trap result did not include all configurable keys.")); + } + /* 24. */ + else if (unchecked_result_keys->item_count != unchecked_prop_name_counter) + { + ecma_raise_type_error (ECMA_ERR_MSG ("Trap returned extra keys but proxy target is non-extensible")); + } + /* 25. */ + else + { + check_ok = true; + } + + ecma_collection_free (unchecked_result_keys); + + return check_ok; +} /* ecma_proxy_check_invariants_for_own_prop_keys */ + /** * The Proxy object [[OwnPropertyKeys]] internal routine * @@ -1147,9 +1260,141 @@ ecma_collection_t * ecma_proxy_object_own_property_keys (ecma_object_t *obj_p) /**< proxy object */ { JERRY_ASSERT (ECMA_OBJECT_IS_PROXY (obj_p)); - JERRY_UNUSED (obj_p); - ecma_raise_type_error (ECMA_ERR_MSG ("UNIMPLEMENTED: Proxy.[[OwnPropertyKeys]]")); - return NULL; + + ecma_proxy_object_t *proxy_obj_p = (ecma_proxy_object_t *) obj_p; + + /* 1. */ + ecma_value_t handler = proxy_obj_p->handler; + + /* 2-5. */ + ecma_value_t trap = ecma_validate_proxy_object (handler, LIT_MAGIC_STRING_OWN_KEYS_UL); + + /* 6. */ + if (ECMA_IS_VALUE_ERROR (trap)) + { + return NULL; + } + + ecma_value_t target = proxy_obj_p->target; + ecma_object_t *target_obj_p = ecma_get_object_from_value (target); + + /* 7. */ + if (ecma_is_value_undefined (trap)) + { + return ecma_op_object_get_property_names (target_obj_p, ECMA_LIST_SYMBOLS); + } + + ecma_object_t *func_obj_p = ecma_get_object_from_value (trap); + + /* 8. */ + ecma_value_t trap_result_array = ecma_op_function_call (func_obj_p, handler, &target, 1); + + ecma_deref_object (func_obj_p); + + if (ECMA_IS_VALUE_ERROR (trap_result_array)) + { + return NULL; + } + + /* 9. */ + ecma_collection_t *trap_result = ecma_op_create_list_from_array_like (trap_result_array, true); + + ecma_free_value (trap_result_array); + + /* 10. */ + if (trap_result == NULL) + { + return trap_result; + } + + /* 11. */ + ecma_value_t extensible_target = ecma_builtin_object_object_is_extensible (target_obj_p); + + /* 12. */ + if (ECMA_IS_VALUE_ERROR (extensible_target)) + { + ecma_collection_free (trap_result); + return NULL; + } + + /* 13. */ + ecma_collection_t *target_keys = ecma_op_object_get_property_names (target_obj_p, ECMA_LIST_SYMBOLS); + + /* 14. */ + if (target_keys == NULL) + { + ecma_collection_free (trap_result); + return target_keys; + } + + /* 16. */ + ecma_collection_t *target_configurable_keys = ecma_new_collection (); + + /* 17. */ + ecma_collection_t *target_non_configurable_keys = ecma_new_collection (); + + ecma_collection_t *ret_value = NULL; + + /* 18. */ + for (uint32_t i = 0; i < target_keys->item_count; i++) + { + ecma_property_descriptor_t target_desc; + + ecma_string_t *prop_name_p = ecma_get_prop_name_from_value (target_keys->buffer_p[i]); + + ecma_value_t status = ecma_op_object_get_own_property_descriptor (target_obj_p, + prop_name_p, + &target_desc); + + if (ECMA_IS_VALUE_ERROR (status)) + { + ecma_collection_free (trap_result); + goto free_target_collections; + } + + ecma_value_t prop_value = ecma_make_prop_name_value (prop_name_p); + + if (ecma_is_value_true (status) + && !(target_desc.flags & ECMA_PROP_IS_CONFIGURABLE)) + { + ecma_collection_push_back (target_non_configurable_keys, prop_value); + } + else + { + ecma_collection_push_back (target_configurable_keys, prop_value); + } + + if (ecma_is_value_true (status)) + { + ecma_free_property_descriptor (&target_desc); + } + } + + /* 19. */ + if (ecma_is_value_true (extensible_target) && target_non_configurable_keys->item_count == 0) + { + ret_value = trap_result; + } + /* 20-25. */ + else if (ecma_proxy_check_invariants_for_own_prop_keys (trap_result, + target_non_configurable_keys, + target_configurable_keys, + extensible_target)) + { + ret_value = trap_result; + } + else + { + JERRY_ASSERT (ret_value == NULL); + ecma_collection_free (trap_result); + } + +free_target_collections: + ecma_collection_destroy (target_keys); + ecma_collection_free (target_configurable_keys); + ecma_collection_free (target_non_configurable_keys); + + return ret_value; } /* ecma_proxy_object_own_property_keys */ /** diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index d37f261f1..5c8b6c4fc 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -470,7 +470,8 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_INDEX_OF_UL, "indexOf") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_IS_ARRAY_UL, "isArray") #endif LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_MESSAGE, "message") -#if ENABLED (JERRY_ES2015_BUILTIN_REFLECT) +#if ENABLED (JERRY_ES2015_BUILTIN_PROXY) \ +|| ENABLED (JERRY_ES2015_BUILTIN_REFLECT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_OWN_KEYS_UL, "ownKeys") #endif #if ENABLED (JERRY_BUILTIN_REGEXP) && ENABLED (JERRY_BUILTIN_STRING) \ diff --git a/tests/jerry/es2015/proxy_get_own_property_descriptor.js b/tests/jerry/es2015/proxy_get_own_property_descriptor.js index e0adc87fc..4cd88bace 100644 --- a/tests/jerry/es2015/proxy_get_own_property_descriptor.js +++ b/tests/jerry/es2015/proxy_get_own_property_descriptor.js @@ -37,14 +37,6 @@ try { assert(e instanceof TypeError); } -try { - // 19.1.2.1.5.b.iii - Object.assign({}, proxy) - assert(false); -} catch (e) { - assert(e instanceof TypeError); -} - try { // 19.1.2.6.5 Object.getOwnPropertyDescriptor(proxy) diff --git a/tests/jerry/es2015/proxy_own_keys.js b/tests/jerry/es2015/proxy_own_keys.js index e7e051067..4d839290f 100644 --- a/tests/jerry/es2015/proxy_own_keys.js +++ b/tests/jerry/es2015/proxy_own_keys.js @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO: Update these tests when the internal routine has been implemented +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. var target = {}; var handler = { ownKeys (target) { @@ -26,7 +28,7 @@ try { Array.prototype.sort.call(proxy); assert(false); } catch (e) { - assert(e instanceof TypeError); + assert(e === 42); } try { @@ -34,7 +36,7 @@ try { Object.keys(proxy); assert(false); } catch (e) { - assert(e instanceof TypeError); + assert(e === 42); } try { @@ -42,13 +44,156 @@ try { Object.getOwnPropertyNames(proxy); assert(false); } catch (e) { - assert(e instanceof TypeError); + assert(e === 42); } try { // 19.1.2.8.1 Object.getOwnPropertySymbols(proxy); assert(false); +} catch (e) { + assert(e === 42); +} + +// test basic functionality +var target = { prop1: "prop1", prop2: "prop2"}; +var handler = { + ownKeys: function(target) { + return ["foo", "bar"]; + } +} + +var proxy = new Proxy(target, handler); + +assert(JSON.stringify(Reflect.ownKeys(proxy)) === '["foo","bar"]'); +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["foo","bar"]'); +assert(JSON.stringify(Object.keys(proxy)) === '["foo","bar"]'); +assert(JSON.stringify(Object.getOwnPropertySymbols(proxy)) === '["foo","bar"]'); + +handler.ownKeys = function(target) {return Object.getOwnPropertyNames(target);}; + +assert(JSON.stringify(Reflect.ownKeys(proxy)) === '["prop1","prop2"]'); +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["prop1","prop2"]'); +assert(JSON.stringify(Object.keys(proxy)) === '["prop1","prop2"]'); +assert(JSON.stringify(Object.getOwnPropertySymbols(proxy)) === '["prop1","prop2"]'); + +// test with no trap +var target = { prop1: "prop1", prop2: "prop2"}; +var handler = {}; +var proxy = new Proxy(target, handler); + +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["prop1","prop2"]'); + +// test wtih "undefined" trap +var target = { prop1: "prop1", prop2: "prop2"}; +var handler = {ownKeys: null}; +var proxy = new Proxy(target, handler); + +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["prop1","prop2"]'); + +// test with invalid trap +var target = { prop1: "prop1", prop2: "prop2"}; +var handler = {ownKeys: 42}; +var proxy = new Proxy(target, handler); + +try { + Object.getOwnPropertyNames(proxy); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +// test when CreateListFromArrayLike called on non-object +var target = { prop1: "prop1", prop2: "prop2"}; +var handler = { + ownKeys: function(target) { + return "foo"; + } +}; + +var proxy = new Proxy(target, handler); + +try { + Object.getOwnPropertyNames(proxy); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +// test with invalid property key +var target = {}; +var handler = { + ownKeys: function(target) { + return [5]; + } +}; + +var proxy = new Proxy(target, handler); + +try { + Object.getOwnPropertyNames(proxy); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +// test with duplicated keys +var target = { prop1: "prop1", prop2: "prop2"}; +var handler = { + ownKeys: function(target) { + return ["a", "a", "a"]; + } +}; + +var proxy = new Proxy(target, handler); + +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["a","a","a"]'); + +// test when invariants gets violated +var target = { + "target_one": 1 +}; +Object.defineProperty(target, "nonconf", {value: 1, configurable: false}); + +var keys = ["foo"]; + +var handler = { + ownKeys: function(target) { + return keys; + } +}; + +var proxy = new Proxy(target, handler); + +try { + Object.getOwnPropertyNames(proxy); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +keys = ["nonconf"]; +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["nonconf"]'); + +Object.preventExtensions(target); +keys = ["foo", "nonconf"]; + +try { + Object.getOwnPropertyNames(proxy); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +keys = ["target_one", "nonconf"]; + +assert(JSON.stringify(Object.getOwnPropertyNames(proxy)) === '["target_one","nonconf"]'); + +keys = ["target_one", "nonconf", "foo"]; + +try { + Object.getOwnPropertyNames(proxy); + assert(false); } catch (e) { assert(e instanceof TypeError); }