From 73a78bd2235e79b3ad620cdb6c63520429b69a50 Mon Sep 17 00:00:00 2001 From: Hyukwoo Park Date: Sat, 4 Apr 2020 09:04:02 +0900 Subject: [PATCH] Fix attributes of length property for bound function (#3659) fix length property of bound function objects to be configurable (ECMA-262 v6, 19.2.4.1) JerryScript-DCO-1.0-Signed-off-by: HyukWoo Park hyukwoo.park@samsung.com --- jerry-core/ecma/base/ecma-gc.c | 21 ++--- jerry-core/ecma/base/ecma-globals.h | 13 ++- .../ecma-builtin-function-prototype.c | 65 +++++++++++--- .../ecma/operations/ecma-function-object.c | 86 ++++++++++++------- .../ecma/operations/ecma-function-object.h | 3 +- jerry-core/ecma/operations/ecma-objects.c | 3 +- tests/jerry/es2015/length-property.js | 20 ++++- 7 files changed, 153 insertions(+), 58 deletions(-) diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 435669303..148735aa1 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -217,15 +217,15 @@ ecma_gc_mark_bound_function_object (ecma_object_t *object_p) /**< bound function { JERRY_ASSERT (ecma_get_object_type (object_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION); - ecma_extended_object_t *ext_function_p = (ecma_extended_object_t *) object_p; + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) object_p; - ecma_object_t *target_func_obj_p; - target_func_obj_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, - ext_function_p->u.bound_function.target_function); + ecma_object_t *target_func_p; + target_func_p = ECMA_GET_NON_NULL_POINTER_FROM_POINTER_TAG (ecma_object_t, + bound_func_p->header.u.bound_function.target_function); - ecma_gc_set_object_visited (target_func_obj_p); + ecma_gc_set_object_visited (target_func_p); - ecma_value_t args_len_or_this = ext_function_p->u.bound_function.args_len_or_this; + ecma_value_t args_len_or_this = bound_func_p->header.u.bound_function.args_len_or_this; if (!ecma_is_value_integer_number (args_len_or_this)) { @@ -238,7 +238,7 @@ ecma_gc_mark_bound_function_object (ecma_object_t *object_p) /**< bound function } ecma_integer_value_t args_length = ecma_get_integer_from_value (args_len_or_this); - ecma_value_t *args_p = (ecma_value_t *) (ext_function_p + 1); + ecma_value_t *args_p = (ecma_value_t *) (bound_func_p + 1); JERRY_ASSERT (args_length > 0); @@ -1311,9 +1311,10 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ } case ECMA_OBJECT_TYPE_BOUND_FUNCTION: { - ecma_extended_object_t *ext_function_p = (ecma_extended_object_t *) object_p; + ext_object_size = sizeof (ecma_bound_function_t); + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) object_p; - ecma_value_t args_len_or_this = ext_function_p->u.bound_function.args_len_or_this; + ecma_value_t args_len_or_this = bound_func_p->header.u.bound_function.args_len_or_this; if (!ecma_is_value_integer_number (args_len_or_this)) { @@ -1322,7 +1323,7 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ } ecma_integer_value_t args_length = ecma_get_integer_from_value (args_len_or_this); - ecma_value_t *args_p = (ecma_value_t *) (ext_function_p + 1); + ecma_value_t *args_p = (ecma_value_t *) (bound_func_p + 1); for (ecma_integer_value_t i = 0; i < args_length; i++) { diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index ae207cae2..c2d62a732 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -897,7 +897,7 @@ typedef struct */ struct { - ecma_value_t target_function; /**< target function */ + jmem_cpointer_tag_t target_function; /**< target function */ ecma_value_t args_len_or_this; /**< length of arguments or this value */ } bound_function; @@ -947,6 +947,17 @@ typedef struct #define ECMA_REGEXP_PROTO_COMPILED_CODE_SIZE \ (JERRY_ALIGNUP (sizeof (ecma_compiled_code_t), JMEM_ALIGNMENT) + sizeof (ecma_value_t)) +/** + * Description of bound function objects. + */ +typedef struct +{ + ecma_extended_object_t header; /**< extended object header */ +#if ENABLED (JERRY_ES2015) + ecma_integer_value_t target_length; /**< length of target function */ +#endif /* ENABLED (JERRY_ES2015) */ +} ecma_bound_function_t; + #if ENABLED (JERRY_SNAPSHOT_EXEC) /** diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.c index f97efefb6..ad510e71f 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.c @@ -244,46 +244,48 @@ ecma_builtin_function_prototype_object_bind (ecma_object_t *this_arg_obj_p , /** #endif /* !ENABLED (JERRY_ES2015) */ ecma_object_t *function_p; - ecma_extended_object_t *ext_function_p; + ecma_bound_function_t *bound_func_p; if (arguments_number == 0 || (arguments_number == 1 && !ecma_is_value_integer_number (arguments_list_p[0]))) { function_p = ecma_create_object (prototype_obj_p, - sizeof (ecma_extended_object_t), + sizeof (ecma_bound_function_t), ECMA_OBJECT_TYPE_BOUND_FUNCTION); /* 8. */ - ext_function_p = (ecma_extended_object_t *) function_p; - ECMA_SET_INTERNAL_VALUE_POINTER (ext_function_p->u.bound_function.target_function, - this_arg_obj_p); + bound_func_p = (ecma_bound_function_t *) function_p; + ECMA_SET_NON_NULL_POINTER_TAG (bound_func_p->header.u.bound_function.target_function, + this_arg_obj_p, + 0); - ext_function_p->u.bound_function.args_len_or_this = ECMA_VALUE_UNDEFINED; + bound_func_p->header.u.bound_function.args_len_or_this = ECMA_VALUE_UNDEFINED; if (arguments_number != 0) { - ext_function_p->u.bound_function.args_len_or_this = ecma_copy_value_if_not_object (arguments_list_p[0]); + bound_func_p->header.u.bound_function.args_len_or_this = ecma_copy_value_if_not_object (arguments_list_p[0]); } } else { JERRY_ASSERT (arguments_number > 0); - size_t obj_size = sizeof (ecma_extended_object_t) + (arguments_number * sizeof (ecma_value_t)); + size_t obj_size = sizeof (ecma_bound_function_t) + (arguments_number * sizeof (ecma_value_t)); function_p = ecma_create_object (prototype_obj_p, obj_size, ECMA_OBJECT_TYPE_BOUND_FUNCTION); /* 8. */ - ext_function_p = (ecma_extended_object_t *) function_p; - ECMA_SET_INTERNAL_VALUE_POINTER (ext_function_p->u.bound_function.target_function, - this_arg_obj_p); + bound_func_p = (ecma_bound_function_t *) function_p; + ECMA_SET_NON_NULL_POINTER_TAG (bound_func_p->header.u.bound_function.target_function, + this_arg_obj_p, + 0); /* NOTE: This solution provides temporary false data about the object's size but prevents GC from freeing it until it's not fully initialized. */ - ext_function_p->u.bound_function.args_len_or_this = ECMA_VALUE_UNDEFINED; - ecma_value_t *args_p = (ecma_value_t *) (ext_function_p + 1); + bound_func_p->header.u.bound_function.args_len_or_this = ECMA_VALUE_UNDEFINED; + ecma_value_t *args_p = (ecma_value_t *) (bound_func_p + 1); for (ecma_length_t i = 0; i < arguments_number; i++) { @@ -291,10 +293,45 @@ ecma_builtin_function_prototype_object_bind (ecma_object_t *this_arg_obj_p , /** } ecma_value_t args_len_or_this = ecma_make_integer_value ((ecma_integer_value_t) arguments_number); - ext_function_p->u.bound_function.args_len_or_this = args_len_or_this; + bound_func_p->header.u.bound_function.args_len_or_this = args_len_or_this; } #if ENABLED (JERRY_ES2015) + ecma_integer_value_t len = 0; + ecma_string_t *len_string = ecma_get_magic_string (LIT_MAGIC_STRING_LENGTH); + ecma_property_descriptor_t prop_desc; + ecma_value_t status = ecma_op_object_get_own_property_descriptor (this_arg_obj_p, + len_string, + &prop_desc); + +#if ENABLED (JERRY_ES2015_BUILTIN_PROXY) + if (ECMA_IS_VALUE_ERROR (status)) + { + return status; + } +#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROXY) */ + + if (ecma_is_value_true (status)) + { + ecma_free_property_descriptor (&prop_desc); + ecma_value_t len_value = ecma_op_object_get (this_arg_obj_p, + len_string); + + if (ECMA_IS_VALUE_ERROR (len_value)) + { + return len_value; + } + + if (ecma_is_value_number (len_value)) + { + ecma_number_t len_num; + ecma_op_to_integer (len_value, &len_num); + len = (ecma_integer_value_t) len_num; + } + } + + bound_func_p->target_length = len; + if (prototype_obj_p != NULL) { ecma_deref_object (prototype_obj_p); diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index 456948ea0..0f2a0b3af 100644 --- a/jerry-core/ecma/operations/ecma-function-object.c +++ b/jerry-core/ecma/operations/ecma-function-object.c @@ -557,10 +557,10 @@ ecma_op_function_has_instance (ecma_object_t *func_obj_p, /**< Function object * JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION); /* 1. 3. */ - ecma_extended_object_t *ext_function_p = (ecma_extended_object_t *) func_obj_p; + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) func_obj_p; - func_obj_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, - ext_function_p->u.bound_function.target_function); + func_obj_p = ECMA_GET_NON_NULL_POINTER_FROM_POINTER_TAG (ecma_object_t, + bound_func_p->header.u.bound_function.target_function); } JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_FUNCTION @@ -939,12 +939,12 @@ ecma_op_bound_function_get_argument_list (ecma_object_t *func_obj_p, /**< bound { JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION); - ecma_extended_object_t *ext_function_p = (ecma_extended_object_t *) func_obj_p; + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) func_obj_p; - func_obj_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, - ext_function_p->u.bound_function.target_function); + func_obj_p = ECMA_GET_NON_NULL_POINTER_FROM_POINTER_TAG (ecma_object_t, + bound_func_p->header.u.bound_function.target_function); - ecma_value_t args_len_or_this = ext_function_p->u.bound_function.args_len_or_this; + ecma_value_t args_len_or_this = bound_func_p->header.u.bound_function.args_len_or_this; ecma_length_t args_length = 1; @@ -956,7 +956,7 @@ ecma_op_bound_function_get_argument_list (ecma_object_t *func_obj_p, /**< bound /* 5. */ if (args_length != 1) { - const ecma_value_t *args_p = (const ecma_value_t *) (ext_function_p + 1); + const ecma_value_t *args_p = (const ecma_value_t *) (bound_func_p + 1); list_p->buffer_p[0] = *args_p; if (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION) @@ -1468,41 +1468,56 @@ ecma_op_bound_function_try_to_lazy_instantiate_property (ecma_object_t *object_p if (ecma_string_is_length (property_name_p)) { - ecma_extended_object_t *ext_function_p = (ecma_extended_object_t *) object_p; - ecma_object_t *target_func_obj_p; - target_func_obj_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, - ext_function_p->u.bound_function.target_function); - + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) object_p; + ecma_value_t args_len_or_this = bound_func_p->header.u.bound_function.args_len_or_this; ecma_integer_value_t length = 0; + ecma_integer_value_t args_length = 1; + uint8_t length_attributes; - if (ecma_object_get_class_name (target_func_obj_p) == LIT_MAGIC_STRING_FUNCTION_UL) + if (ecma_is_value_integer_number (args_len_or_this)) { - /* The property_name_p argument contans the 'length' string. */ - ecma_value_t get_len_value = ecma_op_object_get (target_func_obj_p, property_name_p); + args_length = ecma_get_integer_from_value (args_len_or_this); + } + +#if ENABLED (JERRY_ES2015) + if (ECMA_GET_FIRST_BIT_FROM_POINTER_TAG (bound_func_p->header.u.bound_function.target_function)) + { + return NULL; + } + + length_attributes = ECMA_PROPERTY_FLAG_CONFIGURABLE; + length = bound_func_p->target_length - (args_length - 1); + + /* Set tag bit to represent initialized 'length' property */ + ECMA_SET_FIRST_BIT_TO_POINTER_TAG (bound_func_p->header.u.bound_function.target_function); +#else /* !ENABLED (JERRY_ES2015) */ + length_attributes = ECMA_PROPERTY_FIXED; + + ecma_object_t *target_func_p; + target_func_p = ECMA_GET_NON_NULL_POINTER_FROM_POINTER_TAG (ecma_object_t, + bound_func_p->header.u.bound_function.target_function); + + if (ecma_object_get_class_name (target_func_p) == LIT_MAGIC_STRING_FUNCTION_UL) + { + /* The property_name_p argument contains the 'length' string. */ + ecma_value_t get_len_value = ecma_op_object_get (target_func_p, property_name_p); JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (get_len_value)); JERRY_ASSERT (ecma_is_value_integer_number (get_len_value)); - ecma_value_t args_len_or_this = ext_function_p->u.bound_function.args_len_or_this; - ecma_integer_value_t args_length = 1; - - if (ecma_is_value_integer_number (args_len_or_this)) - { - args_length = ecma_get_integer_from_value (args_len_or_this); - } - length = ecma_get_integer_from_value (get_len_value) - (args_length - 1); + } +#endif /* ENABLED (JERRY_ES2015) */ - if (length < 0) - { - length = 0; - } + if (length < 0) + { + length = 0; } ecma_property_t *len_prop_p; ecma_property_value_t *len_prop_value_p = ecma_create_named_data_property (object_p, property_name_p, - ECMA_PROPERTY_FIXED, + length_attributes, &len_prop_p); len_prop_value_p->value = ecma_make_integer_value (length); @@ -1619,7 +1634,8 @@ ecma_op_external_function_list_lazy_property_names (uint32_t opts, /**< listing * ecma_op_bound_function_try_to_lazy_instantiate_property */ void -ecma_op_bound_function_list_lazy_property_names (uint32_t opts, /**< listing options using flags +ecma_op_bound_function_list_lazy_property_names (ecma_object_t *object_p, /**< bound function object*/ + uint32_t opts, /**< listing options using flags * from ecma_list_properties_options_t */ ecma_collection_t *main_collection_p, /**< 'main' collection */ ecma_collection_t *non_enum_collection_p) /**< skipped @@ -1630,8 +1646,18 @@ ecma_op_bound_function_list_lazy_property_names (uint32_t opts, /**< listing opt ecma_collection_t *for_non_enumerable_p = (opts & ECMA_LIST_ENUMERABLE) ? non_enum_collection_p : main_collection_p; +#if ENABLED (JERRY_ES2015) + /* Unintialized 'length' property is non-enumerable (ECMA-262 v6, 19.2.4.1) */ + ecma_bound_function_t *bound_func_p = (ecma_bound_function_t *) object_p; + if (!ECMA_GET_FIRST_BIT_FROM_POINTER_TAG (bound_func_p->header.u.bound_function.target_function)) + { + ecma_collection_push_back (for_non_enumerable_p, ecma_make_magic_string_value (LIT_MAGIC_STRING_LENGTH)); + } +#else /* !ENABLED (JERRY_ES2015) */ + JERRY_UNUSED (object_p); /* 'length' property is non-enumerable (ECMA-262 v5, 13.2.5) */ ecma_collection_push_back (for_non_enumerable_p, ecma_make_magic_string_value (LIT_MAGIC_STRING_LENGTH)); +#endif /* ENABLED (JERRY_ES2015) */ /* 'caller' property is non-enumerable (ECMA-262 v5, 13.2.5) */ ecma_collection_push_back (for_non_enumerable_p, ecma_make_magic_string_value (LIT_MAGIC_STRING_CALLER)); diff --git a/jerry-core/ecma/operations/ecma-function-object.h b/jerry-core/ecma/operations/ecma-function-object.h index a80b6b7cc..b5aaede3d 100644 --- a/jerry-core/ecma/operations/ecma-function-object.h +++ b/jerry-core/ecma/operations/ecma-function-object.h @@ -99,7 +99,8 @@ ecma_op_external_function_list_lazy_property_names (uint32_t opts, ecma_collection_t *non_enum_collection_p); void -ecma_op_bound_function_list_lazy_property_names (uint32_t opts, +ecma_op_bound_function_list_lazy_property_names (ecma_object_t *object_p, + uint32_t opts, ecma_collection_t *main_collection_p, ecma_collection_t *non_enum_collection_p); diff --git a/jerry-core/ecma/operations/ecma-objects.c b/jerry-core/ecma/operations/ecma-objects.c index 3042cfafd..902456b09 100644 --- a/jerry-core/ecma/operations/ecma-objects.c +++ b/jerry-core/ecma/operations/ecma-objects.c @@ -1997,7 +1997,8 @@ ecma_op_object_get_property_names (ecma_object_t *obj_p, /**< object */ } case ECMA_OBJECT_TYPE_BOUND_FUNCTION: { - ecma_op_bound_function_list_lazy_property_names (opts, + ecma_op_bound_function_list_lazy_property_names (obj_p, + opts, prop_names_p, skipped_non_enumerable_p); break; diff --git a/tests/jerry/es2015/length-property.js b/tests/jerry/es2015/length-property.js index 448cad230..95937d83f 100644 --- a/tests/jerry/es2015/length-property.js +++ b/tests/jerry/es2015/length-property.js @@ -105,8 +105,10 @@ var builtin_typedArrays = [ /* test length property of function objects */ var normal_func = function () {}; var arrow_func = () => {}; + var bound_func = normal_func.bind({}); + var nested_bound_func = arrow_func.bind().bind(1); - var functions = [normal_func, arrow_func]; + var functions = [normal_func, arrow_func, bound_func, nested_bound_func]; for (func of functions) { var desc = Object.getOwnPropertyDescriptor(func, 'length'); @@ -121,3 +123,19 @@ var builtin_typedArrays = [ assert(func.hasOwnProperty('length') === false); } })(); + +(function() { + /* changing the length of f affects the bound function */ + function f(a,b,c) {} + Object.defineProperty(f, "length", { value: 30 }); + var g = f.bind(1,2) + assert(g.length === 29); +})(); + +(function() { + /* changing the length of f does not affect the bound function */ + function f(a,b,c) {} + var g = f.bind(1,2) + Object.defineProperty(f, "length", { value: 30 }); + assert(g.length === 2); +})();