diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 99f15fafbb..5ce0a5c086 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -3373,6 +3373,57 @@ jerry_free_property_descriptor_fields (const jerry_property_descriptor_t *prop_d } } /* jerry_free_property_descriptor_fields */ +/** + * Hide the getter/setter functions of an accessor property, so the property is + * returned as a data property when the property descriptor is requested. + * + * Note: + * - only ordinary objects are supported + * + * @return true - if the operation is successful + * thrown error - otherwise + */ +jerry_value_t +jerry_hide_own_accessor_property (const jerry_value_t obj_val, /**< object value */ + const jerry_value_t prop_name_val) /**< property name */ +{ + if (!ecma_is_value_object (obj_val) + || !ecma_is_value_prop_name (prop_name_val)) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (wrong_args_msg_p))); + } + + ecma_object_t *object_p = ecma_get_object_from_value (obj_val); + +#if ENABLED (JERRY_BUILTIN_PROXY) + if (ECMA_OBJECT_IS_PROXY (object_p)) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (wrong_args_msg_p))); + } +#endif /* ENABLED (JERRY_BUILTIN_PROXY) */ + + ecma_extended_property_ref_t ext_property_ref = { .property_ref.value_p = NULL, .property_p = NULL }; + ecma_property_t property; + + property = ecma_op_object_get_own_property (object_p, + ecma_get_prop_name_from_value (prop_name_val), + &ext_property_ref.property_ref, + ECMA_PROPERTY_GET_EXT_REFERENCE); + + if (property == ECMA_PROPERTY_TYPE_NOT_FOUND || property == ECMA_PROPERTY_TYPE_NOT_FOUND_AND_STOP) + { + return jerry_throw (ecma_raise_reference_error (ECMA_ERR_MSG ("Property not found"))); + } + + if (ECMA_PROPERTY_GET_TYPE (property) != ECMA_PROPERTY_TYPE_NAMEDACCESSOR) + { + return jerry_throw (ecma_raise_reference_error (ECMA_ERR_MSG ("Property is not accessor property"))); + } + + *ext_property_ref.property_p |= ECMA_PROPERTY_FLAG_HIDDEN_ACCESSOR; + return ECMA_VALUE_TRUE; +} /* jerry_hide_own_accessor_property */ + /** * Invoke function specified by a function value * diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 58b71d2025..a1cf346d8b 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -443,6 +443,11 @@ typedef enum #define ECMA_PROPERTY_ENUMERABLE_WRITABLE \ (ECMA_PROPERTY_FLAG_ENUMERABLE | ECMA_PROPERTY_FLAG_WRITABLE) +/** + * Property flag named accessor which is returned as data property. + */ +#define ECMA_PROPERTY_FLAG_HIDDEN_ACCESSOR ECMA_PROPERTY_FLAG_WRITABLE + /** * No attributes can be changed for this property. */ @@ -1182,6 +1187,7 @@ typedef enum ECMA_PROP_IS_CONFIGURABLE_DEFINED = (1 << 7), /** Is [[Configurable]] defined? */ ECMA_PROP_IS_ENUMERABLE_DEFINED = (1 << 8), /** Is [[Enumerable]] defined? */ ECMA_PROP_IS_WRITABLE_DEFINED = (1 << 9), /** Is [[Writable]] defined? */ + ECMA_PROP_IS_HIDDEN_ACCESSOR = (1 << 10), /** Is hidden accessor */ } ecma_property_descriptor_status_flags_t; /** @@ -1196,7 +1202,6 @@ typedef enum */ typedef struct { - /** any combination of ecma_property_descriptor_status_flags_t bits */ uint16_t flags; diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c index df55a1c1f1..f5c67387d1 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c @@ -725,6 +725,17 @@ ecma_builtin_object_object_get_own_property_descriptor (ecma_object_t *obj_p, /* if (ecma_is_value_true (status)) { + if (JERRY_UNLIKELY (prop_desc.flags & ECMA_PROP_IS_HIDDEN_ACCESSOR)) + { + status = ecma_op_to_data_property (obj_p, &prop_desc); + + if (ECMA_IS_VALUE_ERROR (status)) + { + ecma_free_property_descriptor (&prop_desc); + return status; + } + } + /* 4. */ ecma_object_t *desc_obj_p = ecma_op_from_property_descriptor (&prop_desc); @@ -786,6 +797,20 @@ ecma_builtin_object_object_get_own_property_descriptors (ecma_object_t *obj_p) / if (ecma_is_value_true (status)) { + if (JERRY_UNLIKELY (prop_desc.flags & ECMA_PROP_IS_HIDDEN_ACCESSOR)) + { + status = ecma_op_to_data_property (obj_p, &prop_desc); + + if (ECMA_IS_VALUE_ERROR (status)) + { + ecma_free_property_descriptor (&prop_desc); + ecma_deref_object (descriptors_p); + ecma_collection_free (prop_names_p); + + return status; + } + } + /* 4.b */ ecma_object_t *desc_obj_p = ecma_op_from_property_descriptor (&prop_desc); /* 4.c */ diff --git a/jerry-core/ecma/operations/ecma-conversion.c b/jerry-core/ecma/operations/ecma-conversion.c index ed7135fec7..e75c762e58 100644 --- a/jerry-core/ecma/operations/ecma-conversion.c +++ b/jerry-core/ecma/operations/ecma-conversion.c @@ -908,6 +908,50 @@ ecma_op_to_property_descriptor (ecma_value_t obj_value, /**< object value */ return ret_value; } /* ecma_op_to_property_descriptor */ +/** + * Convert accessor to data property. + * + * @return ECMA_VALUE_EMPTY - if the opertation is successful + * error - otherwise + */ +ecma_value_t +ecma_op_to_data_property (ecma_object_t *object_p, /**< base object */ + ecma_property_descriptor_t *prop_desc_p) /**< [in/out] property descriptor */ +{ + JERRY_ASSERT (prop_desc_p->flags & ECMA_PROP_IS_HIDDEN_ACCESSOR); + + ecma_value_t result = ECMA_VALUE_UNDEFINED; + uint16_t flags = prop_desc_p->flags; + + if ((flags & ECMA_PROP_IS_GET_DEFINED) && prop_desc_p->get_p != NULL) + { + result = ecma_op_function_call (prop_desc_p->get_p, ecma_make_object_value (object_p), NULL, 0); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + ecma_deref_object (prop_desc_p->get_p); + prop_desc_p->get_p = NULL; + } + + if ((flags & ECMA_PROP_IS_SET_DEFINED) && prop_desc_p->set_p != NULL) + { + ecma_deref_object (prop_desc_p->set_p); + prop_desc_p->set_p = NULL; + + flags |= ECMA_PROP_IS_WRITABLE; + } + + flags |= ECMA_PROP_IS_WRITABLE_DEFINED | ECMA_PROP_IS_VALUE_DEFINED; + flags &= (uint16_t) ~(ECMA_PROP_IS_GET_DEFINED | ECMA_PROP_IS_SET_DEFINED | ECMA_PROP_IS_HIDDEN_ACCESSOR); + + prop_desc_p->value = result; + prop_desc_p->flags = flags; + return ECMA_VALUE_EMPTY; +} /* ecma_op_to_data_property */ + /** * IsInteger operation. * diff --git a/jerry-core/ecma/operations/ecma-conversion.h b/jerry-core/ecma/operations/ecma-conversion.h index 32de03757a..63100f0f7f 100644 --- a/jerry-core/ecma/operations/ecma-conversion.h +++ b/jerry-core/ecma/operations/ecma-conversion.h @@ -72,6 +72,7 @@ ecma_collection_t *ecma_op_create_list_from_array_like (ecma_value_t arr, bool p ecma_object_t *ecma_op_from_property_descriptor (const ecma_property_descriptor_t *src_prop_desc_p); ecma_value_t ecma_op_to_property_descriptor (ecma_value_t obj_value, ecma_property_descriptor_t *out_prop_desc_p); +ecma_value_t ecma_op_to_data_property (ecma_object_t *object_p, ecma_property_descriptor_t *prop_desc_p); /** * @} diff --git a/jerry-core/ecma/operations/ecma-objects-general.c b/jerry-core/ecma/operations/ecma-objects-general.c index 3b87c0ff21..18b678f509 100644 --- a/jerry-core/ecma/operations/ecma-objects-general.c +++ b/jerry-core/ecma/operations/ecma-objects-general.c @@ -504,15 +504,9 @@ ecma_op_general_object_define_own_property (ecma_object_t *object_p, /**< the ob } } } - else + else if (is_current_configurable) { /* 9. */ - if (!is_current_configurable) - { - /* a. */ - return ecma_reject (property_desc_p->flags & ECMA_PROP_IS_THROW); - } - ecma_property_value_t *value_p = ext_property_ref.property_ref.value_p; if (property_desc_type == ECMA_PROPERTY_TYPE_NAMEDACCESSOR) @@ -549,6 +543,81 @@ ecma_op_general_object_define_own_property (ecma_object_t *object_p, /**< the ob prop_flags = (ecma_property_t) (prop_flags | property_desc_type); *(ext_property_ref.property_p) = prop_flags; } + else + { + /* Property is non-configurable. */ + if (current_property_type != ECMA_PROPERTY_TYPE_NAMEDACCESSOR + || !(current_prop & ECMA_PROPERTY_FLAG_HIDDEN_ACCESSOR)) + { + /* a. */ + return ecma_reject (property_desc_p->flags & ECMA_PROP_IS_THROW); + } + + /* Non-standard extension. */ + ecma_getter_setter_pointers_t *getter_setter_pair_p; + getter_setter_pair_p = ecma_get_named_accessor_property (ext_property_ref.property_ref.value_p); + + if (getter_setter_pair_p->setter_cp == JMEM_CP_NULL) + { + const uint16_t mask = ECMA_PROP_IS_WRITABLE_DEFINED | ECMA_PROP_IS_WRITABLE; + + if ((property_desc_p->flags & mask) == mask) + { + return ecma_reject (property_desc_p->flags & ECMA_PROP_IS_THROW); + } + + if (!(property_desc_p->flags & ECMA_PROP_IS_VALUE_DEFINED)) + { + return ECMA_VALUE_TRUE; + } + + ecma_value_t result = ECMA_VALUE_UNDEFINED; + + if (getter_setter_pair_p->getter_cp != JMEM_CP_NULL) + { + ecma_object_t *getter_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, getter_setter_pair_p->getter_cp); + result = ecma_op_function_call (getter_p, ecma_make_object_value (object_p), NULL, 0); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + } + + bool same_value = ecma_op_same_value (property_desc_p->value, result); + ecma_free_value (result); + + if (!same_value) + { + return ecma_reject (property_desc_p->flags & ECMA_PROP_IS_THROW); + } + return ECMA_VALUE_TRUE; + } + + if (property_desc_p->flags & ECMA_PROP_IS_VALUE_DEFINED) + { + ecma_object_t *setter_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, getter_setter_pair_p->setter_cp); + + ecma_value_t result; + result = ecma_op_function_call (setter_p, ecma_make_object_value (object_p), &property_desc_p->value, 1); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + ecma_free_value (result); + } + + /* Because the property is non-configurable, it cannot be modified. */ + if ((property_desc_p->flags & ECMA_PROP_IS_WRITABLE_DEFINED) + && !(property_desc_p->flags & ECMA_PROP_IS_WRITABLE)) + { + getter_setter_pair_p->setter_cp = JMEM_CP_NULL; + } + + return ECMA_VALUE_TRUE; + } /* 12. */ if (property_desc_type == ECMA_PROPERTY_TYPE_NAMEDDATA) diff --git a/jerry-core/ecma/operations/ecma-objects.c b/jerry-core/ecma/operations/ecma-objects.c index afee82a724..936f4bed6e 100644 --- a/jerry-core/ecma/operations/ecma-objects.c +++ b/jerry-core/ecma/operations/ecma-objects.c @@ -65,8 +65,8 @@ * See also: * ECMA-262 v5, 8.6.2; ECMA-262 v5, Table 8 * - * @return pointer to a property - if it exists, - * NULL (i.e. ecma-undefined) - otherwise. + * @return property descriptor - if it exists, + * ECMA_PROPERTY_TYPE_NOT_FOUND / ECMA_PROPERTY_TYPE_NOT_FOUND_AND_STOP - otherwise. */ ecma_property_t ecma_op_object_get_own_property (ecma_object_t *object_p, /**< the object */ @@ -1892,7 +1892,6 @@ ecma_op_object_get_own_property_descriptor (ecma_object_t *object_p, /**< the ob } else { - ecma_getter_setter_pointers_t *get_set_pair_p = ecma_get_named_accessor_property (property_ref.value_p); prop_desc_p->flags |= (ECMA_PROP_IS_GET_DEFINED | ECMA_PROP_IS_SET_DEFINED); @@ -1915,6 +1914,11 @@ ecma_op_object_get_own_property_descriptor (ecma_object_t *object_p, /**< the ob prop_desc_p->set_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, get_set_pair_p->setter_cp); ecma_ref_object (prop_desc_p->set_p); } + + if (property & ECMA_PROPERTY_FLAG_HIDDEN_ACCESSOR) + { + prop_desc_p->flags |= ECMA_PROP_IS_HIDDEN_ACCESSOR; + } } return ECMA_VALUE_TRUE; diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index e9265a4f16..6db4a7d96c 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -633,6 +633,7 @@ bool jerry_get_own_property_descriptor (const jerry_value_t obj_val, const jerry_value_t prop_name_val, jerry_property_descriptor_t *prop_desc_p); void jerry_free_property_descriptor_fields (const jerry_property_descriptor_t *prop_desc_p); +jerry_value_t jerry_hide_own_accessor_property (const jerry_value_t obj_val, const jerry_value_t prop_name_val); jerry_value_t jerry_call_function (const jerry_value_t func_obj_val, const jerry_value_t this_val, const jerry_value_t args_p[], jerry_size_t args_count); diff --git a/tests/unit-core/test-hide-accessor.c b/tests/unit-core/test-hide-accessor.c new file mode 100644 index 0000000000..a7eebba524 --- /dev/null +++ b/tests/unit-core/test-hide-accessor.c @@ -0,0 +1,226 @@ +/* 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 jerry_value_t +hide_accessor_handler (const jerry_value_t func_obj_val, /**< function object */ + const jerry_value_t this_val, /**< this arg */ + const jerry_value_t args_p[], /**< function arguments */ + const jerry_length_t args_cnt) /**< number of function arguments */ +{ + (void) func_obj_val; + (void) this_val; + + if (args_cnt < 2) + { + return jerry_create_error (JERRY_ERROR_RANGE, (const jerry_char_t *) "Two arguments expected"); + } + + return jerry_hide_own_accessor_property (args_p[0], args_p[1]); +} /* hide_accessor_handler */ + +static void +eval_and_check_error (char *script_p) /**< script source */ +{ + jerry_value_t result_value; + result_value = jerry_eval ((const jerry_char_t *) script_p, strlen (script_p), JERRY_PARSE_NO_OPTS); + + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); +} /* eval_and_check_error */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_value_t global_value = jerry_get_global_object (); + jerry_value_t function_value = jerry_create_external_function (hide_accessor_handler); + jerry_value_t name_value = jerry_create_string ((const jerry_char_t *) "hideAccessor"); + jerry_value_t result_value = jerry_set_property (global_value, name_value, function_value); + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); + jerry_release_value (name_value); + jerry_release_value (function_value); + jerry_release_value (global_value); + + eval_and_check_error ("function assert(result) {\n" + " if (result !== true)\n" + " throw new Error('Assertion failed')\n" + "}\n"); + + /* Check getting property descriptors. */ + + eval_and_check_error ("var counter = 0\n" + "var o = { get prop() { counter++; return 5.5 } }\n" + "hideAccessor(o, 'prop')\n" + "assert(o.prop === 5.5)\n" + "var d = Object.getOwnPropertyDescriptor(o, 'prop')\n" + "assert(d.value === 5.5)\n" + "assert(d.writable === false)\n" + "assert(!d.hasOwnProperty('get'))\n" + "assert(!d.hasOwnProperty('set'))\n" + "assert(counter === 2)\n"); + + eval_and_check_error ("var counter = 0\n" + "var o = { get prop() { counter++; return this.v },\n" + " set prop(v) { counter++; this.v = v } }\n" + "hideAccessor(o, 'prop')\n" + "o.prop = 'X'\n" + "assert(o.v === 'X')\n" + "var d = Object.getOwnPropertyDescriptor(o, 'prop')\n" + "assert(d.value === 'X')\n" + "assert(d.writable === true)\n" + "assert(!d.hasOwnProperty('get'))\n" + "assert(!d.hasOwnProperty('set'))\n" + "assert(counter === 2)\n"); + + eval_and_check_error ("var counter = 0\n" + "var o = { get prop() { counter++; return o },\n" + " set prop(v) { counter++ } }\n" + "hideAccessor(o, 'prop')\n" + "var d = Object.getOwnPropertyDescriptors(o)\n" + "assert(d.prop.value === o)\n" + "assert(d.prop.writable === true)\n" + "assert(!d.prop.hasOwnProperty('get'))\n" + "assert(!d.prop.hasOwnProperty('set'))\n" + "assert(counter === 1)\n"); + + /* Check getting property descriptor errors. */ + + eval_and_check_error ("var counter = 0\n" + "var o = { get prop() { counter++; throw 'E1' } }\n" + "hideAccessor(o, 'prop')\n" + "try {" + " var d = Object.getOwnPropertyDescriptor(o, 'prop')\n" + " throw 'E2'\n" + "} catch (e) {\n" + " assert(e === 'E1')" + "}\n" + "assert(counter === 1)\n"); + + eval_and_check_error ("var counter = 0\n" + "var o = { p1:4,\n" + " p2:5,\n" + " get prop() { counter++; throw 'E1' } }\n" + "hideAccessor(o, 'prop')\n" + "try {" + " var d = Object.getOwnPropertyDescriptors(o)\n" + " throw 'E2'\n" + "} catch (e) {\n" + " assert(e === 'E1')" + "}\n" + "assert(counter === 1)\n"); + + /* Check accessor hiding errors. */ + + eval_and_check_error ("var counter = 0\n" + "try {\n" + " hideAccessor(null, 'x')\n" + "} catch (e) {\n" + " assert(e instanceof TypeError)\n" + " counter++" + "}\n" + "try {\n" + " hideAccessor({}, null)\n" + "} catch (e) {\n" + " assert(e instanceof TypeError)\n" + " counter++" + "}\n" + "assert(counter === 2)\n"); + + eval_and_check_error ("var counter = 0\n" + "try {\n" + " hideAccessor({ x:5 }, 'x')\n" + "} catch (e) {\n" + " assert(e instanceof ReferenceError)\n" + " counter++" + "}\n" + "try {\n" + " hideAccessor({ y:5 }, 'x')\n" + "} catch (e) {\n" + " assert(e instanceof ReferenceError)\n" + " counter++" + "}\n" + "assert(counter === 2)\n"); + + /* Check redefining property descriptors. */ + + eval_and_check_error ("var counter = 0\n" + "var o = { get prop() { counter++; return 0 },\n" + " set prop(v) { counter++ } }\n" + "hideAccessor(o, 'prop')\n" + "Object.defineProperty(o, 'prop', { value:'V', writable:true })\n" + "var d = Object.getOwnPropertyDescriptor(o, 'prop')\n" + "assert(d.value === 'V')\n" + "assert(d.writable === true)\n" + "assert(!d.hasOwnProperty('get'))\n" + "assert(!d.hasOwnProperty('set'))\n" + "assert(counter === 0)\n"); + + eval_and_check_error ("var counter = 0\n" + "var o = {}\n" + "Object.defineProperty(o, 'prop', {\n" + " get() { counter++; return this.v },\n" + " set(v) { counter++; this.v = v },\n" + " configurable:false\n" + "})\n" + "hideAccessor(o, 'prop')\n" + "Object.defineProperty(o, 'prop', { value:'V', writable:true })\n" + "assert(o.v === 'V')\n" + "var d = Object.getOwnPropertyDescriptor(o, 'prop')\n" + "assert(d.value === 'V')\n" + "assert(d.writable === true)\n" + "assert(!d.hasOwnProperty('get'))\n" + "assert(!d.hasOwnProperty('set'))\n" + "assert(counter === 2)\n"); + + eval_and_check_error ("var counter = 0\n" + "var o = {}\n" + "Object.defineProperty(o, 'prop', {\n" + " get() { counter++; return -7.25 },\n" + " configurable:false\n" + "})\n" + "hideAccessor(o, 'prop')\n" + "Object.defineProperty(o, 'prop', { value:-7.25 })\n" + "var d = Object.getOwnPropertyDescriptor(o, 'prop')\n" + "assert(d.value === -7.25)\n" + "assert(d.writable === false)\n" + "assert(!d.hasOwnProperty('get'))\n" + "assert(!d.hasOwnProperty('set'))\n" + "assert(counter === 2)\n"); + + eval_and_check_error ("var counter = 0\n" + "var o = {}\n" + "Object.defineProperty(o, 'prop', {\n" + " get() { counter++; return -7.25 },\n" + " configurable:false\n" + "})\n" + "hideAccessor(o, 'prop')\n" + "try {\n" + " Object.defineProperty(o, 'prop', { value:1 })\n" + "} catch (e) {\n" + " assert(e instanceof TypeError)\n" + " counter++" + "}\n" + "assert(counter === 2)\n"); + + jerry_cleanup (); + return 0; +} /* main */