From 1e90f833f7791aaf188433ec8ec025c4b92f84c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20Borb=C3=A9ly?= Date: Tue, 7 Jul 2015 10:11:43 +0200 Subject: [PATCH] Implement Function.prototype.bind function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JerryScript-DCO-1.0-Signed-off-by: Zsolt Borbély zsborbely.u-szeged@partner.samsung.com --- jerry-core/ecma/base/ecma-gc.cpp | 3 + jerry-core/ecma/base/ecma-globals.h | 5 + jerry-core/ecma/base/ecma-helpers.cpp | 13 ++ .../ecma-builtin-function-prototype.cpp | 130 +++++++++++++- .../ecma/operations/ecma-function-object.cpp | 166 ++++++++++++++++-- tests/jerry/function-prototype-bind.js | 134 ++++++++++++++ 6 files changed, 437 insertions(+), 14 deletions(-) create mode 100644 tests/jerry/function-prototype-bind.js diff --git a/jerry-core/ecma/base/ecma-gc.cpp b/jerry-core/ecma/base/ecma-gc.cpp index 27ea2ec4b0..a7b71f377e 100644 --- a/jerry-core/ecma/base/ecma-gc.cpp +++ b/jerry-core/ecma/base/ecma-gc.cpp @@ -331,6 +331,9 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ case ECMA_INTERNAL_PROPERTY_NON_INSTANTIATED_BUILT_IN_MASK_0_31: /* an integer (bit-mask) */ case ECMA_INTERNAL_PROPERTY_NON_INSTANTIATED_BUILT_IN_MASK_32_63: /* an integer (bit-mask) */ case ECMA_INTERNAL_PROPERTY_REGEXP_BYTECODE: + case ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION: + case ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_THIS: + case ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_ARGS: { break; } diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 972b969aed..8661ce08f0 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -245,6 +245,11 @@ typedef enum /** Identifier of implementation-defined extension object */ ECMA_INTERNAL_PROPERTY_EXTENSION_ID, + /** Bound function internal properties **/ + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION, + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_THIS, + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_ARGS, + /** * Bit-mask of non-instantiated built-in's properties (bits 0-31) */ diff --git a/jerry-core/ecma/base/ecma-helpers.cpp b/jerry-core/ecma/base/ecma-helpers.cpp index 466b74f998..f6fa11a49d 100644 --- a/jerry-core/ecma/base/ecma-helpers.cpp +++ b/jerry-core/ecma/base/ecma-helpers.cpp @@ -800,10 +800,23 @@ ecma_free_internal_property (ecma_property_t *property_p) /**< the property */ case ECMA_INTERNAL_PROPERTY_EXTENSION_ID: /* an integer */ case ECMA_INTERNAL_PROPERTY_NON_INSTANTIATED_BUILT_IN_MASK_0_31: /* an integer (bit-mask) */ case ECMA_INTERNAL_PROPERTY_NON_INSTANTIATED_BUILT_IN_MASK_32_63: /* an integer (bit-mask) */ + case ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION: + case ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_THIS: { break; } + case ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_ARGS: + { + if (property_value != ECMA_NULL_POINTER) + { + ecma_free_values_collection (ECMA_GET_NON_NULL_POINTER (ecma_collection_header_t, property_value), + false); + } + + break; + } + case ECMA_INTERNAL_PROPERTY__COUNT: /* not a real internal property type, * but number of the real internal property types */ { diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.cpp b/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.cpp index 064e115ac0..76a62d4eac 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.cpp +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.cpp @@ -226,7 +226,135 @@ ecma_builtin_function_prototype_object_bind (ecma_value_t this_arg, /**< this ar const ecma_value_t* arguments_list_p, /**< list of arguments */ ecma_length_t arguments_number) /**< number of arguments */ { - ECMA_BUILTIN_CP_UNIMPLEMENTED (this_arg, arguments_list_p, arguments_number); + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + + /* 2. */ + if (!ecma_op_is_callable (this_arg)) + { + ret_value = ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE)); + } + else + { + /* 4. 11. 18. */ + ecma_object_t *prototype_obj_p = ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE); + ecma_object_t *function_p = ecma_create_object (prototype_obj_p, true, ECMA_OBJECT_TYPE_BOUND_FUNCTION); + + ecma_deref_object (prototype_obj_p); + + /* 7. */ + ecma_property_t *target_function_prop_p; + target_function_prop_p = ecma_create_internal_property (function_p, + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION); + + ecma_object_t *this_arg_obj_p = ecma_get_object_from_value (this_arg); + ECMA_SET_NON_NULL_POINTER (target_function_prop_p->u.internal_property.value, this_arg_obj_p); + + /* 8. */ + ecma_property_t *bound_this_prop_p; + bound_this_prop_p = ecma_create_internal_property (function_p, ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_THIS); + const ecma_length_t arg_count = arguments_number; + + if (arg_count > 0) + { + bound_this_prop_p->u.internal_property.value = ecma_copy_value (arguments_list_p[0], false); + } + else + { + bound_this_prop_p->u.internal_property.value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED); + } + + if (arg_count > 1) + { + ecma_collection_header_t *bound_args_collection_p; + bound_args_collection_p = ecma_new_values_collection (&arguments_list_p[1], arg_count - 1, false); + + ecma_property_t *bound_args_prop_p; + bound_args_prop_p = ecma_create_internal_property (function_p, ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_ARGS); + ECMA_SET_NON_NULL_POINTER (bound_args_prop_p->u.internal_property.value, bound_args_collection_p); + } + + /* + * [[Class]] property is not stored explicitly for objects of ECMA_OBJECT_TYPE_FUNCTION type. + * + * See also: ecma_object_get_class_name + */ + + /* 17. */ + ecma_number_t *length_p = ecma_alloc_number (); + *length_p = 0; + + ecma_property_descriptor_t length_prop_desc = ecma_make_empty_property_descriptor (); + { + length_prop_desc.is_value_defined = true; + length_prop_desc.value = ecma_make_number_value (length_p); + + length_prop_desc.is_writable_defined = true; + length_prop_desc.is_writable = false; + + length_prop_desc.is_enumerable_defined = true; + length_prop_desc.is_enumerable = false; + + length_prop_desc.is_configurable_defined = true; + length_prop_desc.is_configurable = false; + } + ecma_string_t *magic_string_length_p = ecma_get_magic_string (LIT_MAGIC_STRING_LENGTH); + ecma_completion_value_t completion = ecma_op_object_define_own_property (function_p, + magic_string_length_p, + &length_prop_desc, + false); + + JERRY_ASSERT (ecma_is_completion_value_normal_true (completion) + || ecma_is_completion_value_normal_false (completion)); + + ecma_deref_ecma_string (magic_string_length_p); + ecma_dealloc_number (length_p); + + /* 19-21. */ + ecma_object_t *thrower_p = ecma_builtin_get (ECMA_BUILTIN_ID_TYPE_ERROR_THROWER); + + ecma_property_descriptor_t prop_desc = ecma_make_empty_property_descriptor (); + { + prop_desc.is_enumerable_defined = true; + prop_desc.is_enumerable = false; + + prop_desc.is_configurable_defined = true; + prop_desc.is_configurable = false; + + prop_desc.is_get_defined = true; + prop_desc.get_p = thrower_p; + + prop_desc.is_set_defined = true; + prop_desc.set_p = thrower_p; + } + + ecma_string_t *magic_string_caller_p = ecma_get_magic_string (LIT_MAGIC_STRING_CALLER); + completion = ecma_op_object_define_own_property (function_p, + magic_string_caller_p, + &prop_desc, + false); + + JERRY_ASSERT (ecma_is_completion_value_normal_true (completion) + || ecma_is_completion_value_normal_false (completion)); + + ecma_deref_ecma_string (magic_string_caller_p); + + ecma_string_t *magic_string_arguments_p = ecma_get_magic_string (LIT_MAGIC_STRING_ARGUMENTS); + completion = ecma_op_object_define_own_property (function_p, + magic_string_arguments_p, + &prop_desc, + false); + + JERRY_ASSERT (ecma_is_completion_value_normal_true (completion) + || ecma_is_completion_value_normal_false (completion)); + + ecma_deref_ecma_string (magic_string_arguments_p); + ecma_deref_object (thrower_p); + + /* 22. */ + ret_value = ecma_make_normal_completion_value (ecma_make_object_value (function_p)); + } + + return ret_value; } /* ecma_builtin_function_prototype_object_bind */ /** diff --git a/jerry-core/ecma/operations/ecma-function-object.cpp b/jerry-core/ecma/operations/ecma-function-object.cpp index 6cba77cce1..53d536b95c 100644 --- a/jerry-core/ecma/operations/ecma-function-object.cpp +++ b/jerry-core/ecma/operations/ecma-function-object.cpp @@ -143,6 +143,70 @@ ecma_is_constructor (ecma_value_t value) /**< ecma-value */ || ecma_get_object_type (obj_p) == ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); } /* ecma_is_constructor */ +/** + * Helper function to merge argument lists + * + * See also: + * ECMA-262 v5, 15.3.4.5.1 step 4 + * ECMA-262 v5, 15.3.4.5.2 step 4 + * + * Used by: + * - [[Call]] implementation for Function objects. + * - [[Construct]] implementation for Function objects. + * + * @return ecma_value_t* - pointer to the merged argument list. + */ +static ecma_value_t* +ecma_function_bind_merge_arg_lists (ecma_object_t *func_obj_p, /**< Function object */ + const ecma_value_t *arguments_list_p, /**< arguments list */ + ecma_length_t arguments_list_len, /**< length of arguments list */ + ecma_length_t *total_args_count) /**< length of the merged argument list */ +{ + ecma_value_t *arg_list_p; + ecma_length_t bound_args_count = 0; + + ecma_property_t *bound_args_prop_p; + bound_args_prop_p = ecma_find_internal_property (func_obj_p, ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_ARGS); + + if (bound_args_prop_p != NULL) + { + ecma_collection_header_t *bound_arg_list_p = ECMA_GET_POINTER (ecma_collection_header_t, + bound_args_prop_p->u.internal_property.value); + + ecma_collection_iterator_t bound_args_iterator; + ecma_collection_iterator_init (&bound_args_iterator, bound_arg_list_p); + + bound_args_count = bound_arg_list_p->unit_number; + + *total_args_count = bound_args_count + arguments_list_len; + + const size_t arg_list_size = (size_t) *total_args_count * sizeof (ecma_value_t); + arg_list_p = static_cast (mem_heap_alloc_block (arg_list_size, MEM_HEAP_ALLOC_SHORT_TERM)); + + for (ecma_length_t i = 0; i < bound_args_count; i++) + { + bool is_moved = ecma_collection_iterator_next (&bound_args_iterator); + JERRY_ASSERT (is_moved); + + arg_list_p[i] = *bound_args_iterator.current_value_p; + } + } + else + { + *total_args_count = arguments_list_len; + + const size_t arg_list_size = (size_t) *total_args_count * sizeof (ecma_value_t); + arg_list_p = static_cast (mem_heap_alloc_block (arg_list_size, MEM_HEAP_ALLOC_SHORT_TERM)); + } + + for (ecma_length_t i = 0; i < arguments_list_len; i++) + { + arg_list_p[i + bound_args_count] = arguments_list_p[i]; + } + + return arg_list_p; +} /* ecma_function_bind_merge_arg_lists */ + /** * Function object creation operation. * @@ -535,6 +599,8 @@ ecma_op_function_has_instance (ecma_object_t *func_obj_p, /**< Function object * JERRY_ASSERT (func_obj_p != NULL && !ecma_is_lexical_environment (func_obj_p)); + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + if (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_FUNCTION) { if (!ecma_is_value_object (value)) @@ -546,8 +612,6 @@ ecma_op_function_has_instance (ecma_object_t *func_obj_p, /**< Function object * ecma_string_t *prototype_magic_string_p = ecma_get_magic_string (LIT_MAGIC_STRING_PROTOTYPE); - ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); - ECMA_TRY_CATCH (prototype_obj_value, ecma_op_object_get (func_obj_p, prototype_magic_string_p), ret_value); @@ -583,19 +647,28 @@ ecma_op_function_has_instance (ecma_object_t *func_obj_p, /**< Function object * ECMA_FINALIZE (prototype_obj_value); ecma_deref_ecma_string (prototype_magic_string_p); - - return ret_value; } else if (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BUILT_IN_FUNCTION) { - return ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE)); + ret_value = ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE)); } else { JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION); - JERRY_UNIMPLEMENTED ("Bound functions are not implemented."); + /* 1. */ + ecma_property_t *target_function_prop_p; + target_function_prop_p = ecma_get_internal_property (func_obj_p, + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION); + + ecma_object_t *target_func_obj_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, + target_function_prop_p->u.internal_property.value); + + /* 3. */ + ret_value = ecma_op_object_has_instance (target_func_obj_p, value); } + + return ret_value; } /* ecma_op_function_has_instance */ /** @@ -731,11 +804,40 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ { JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION); - JERRY_UNIMPLEMENTED ("Bound functions are not implemented."); + /* 2-3. */ + ecma_property_t *bound_this_prop_p; + ecma_property_t *target_function_prop_p; + + bound_this_prop_p = ecma_get_internal_property (func_obj_p, ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_BOUND_THIS); + target_function_prop_p = ecma_get_internal_property (func_obj_p, + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION); + + ecma_object_t *target_func_obj_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, + target_function_prop_p->u.internal_property.value); + + ecma_length_t total_args_count; + + /* 4. */ + ecma_value_t *arg_list_p = ecma_function_bind_merge_arg_lists (func_obj_p, + arguments_list_p, + arguments_list_len, + &total_args_count); + + ecma_value_t bound_this_value = bound_this_prop_p->u.internal_property.value; + + /* 5. */ + ret_value = ecma_op_function_call (target_func_obj_p, + bound_this_value, + arg_list_p, + total_args_count); + + if (arg_list_p != NULL) + { + mem_heap_free_block (arg_list_p); + } } JERRY_ASSERT (!ecma_is_completion_value_empty (ret_value)); - return ret_value; } /* ecma_op_function_call */ @@ -847,27 +949,65 @@ ecma_op_function_construct (ecma_object_t *func_obj_p, /**< Function object */ JERRY_ASSERT (ecma_is_constructor (ecma_make_object_value (func_obj_p))); JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL); + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + if (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_FUNCTION) { if (unlikely (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_FUNCTION && ecma_get_object_is_builtin (func_obj_p))) { - return ecma_builtin_dispatch_construct (func_obj_p, arguments_list_p, arguments_list_len); + ret_value = ecma_builtin_dispatch_construct (func_obj_p, arguments_list_p, arguments_list_len); + } + else + { + ret_value = ecma_op_function_construct_simple_or_external (func_obj_p, arguments_list_p, arguments_list_len); } - - return ecma_op_function_construct_simple_or_external (func_obj_p, arguments_list_p, arguments_list_len); } else if (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION) { - return ecma_op_function_construct_simple_or_external (func_obj_p, arguments_list_p, arguments_list_len); + ret_value = ecma_op_function_construct_simple_or_external (func_obj_p, arguments_list_p, arguments_list_len); } else { JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION); - JERRY_UNIMPLEMENTED ("Bound functions are not implemented."); + /* 1. */ + ecma_property_t *target_function_prop_p; + target_function_prop_p = ecma_get_internal_property (func_obj_p, + ECMA_INTERNAL_PROPERTY_BOUND_FUNCTION_TARGET_FUNCTION); + + ecma_object_t *target_func_obj_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, + target_function_prop_p->u.internal_property.value); + + /* 2. */ + if (!ecma_is_constructor (ecma_make_object_value (target_func_obj_p))) + { + ret_value = ecma_make_throw_obj_completion_value (ecma_new_standard_error (ECMA_ERROR_TYPE)); + } + else + { + ecma_length_t total_args_count; + + /* 4. */ + ecma_value_t *arg_list_p = ecma_function_bind_merge_arg_lists (func_obj_p, + arguments_list_p, + arguments_list_len, + &total_args_count); + + /* 5. */ + ret_value = ecma_op_function_construct (target_func_obj_p, + arg_list_p, + total_args_count); + if (arg_list_p != NULL) + { + mem_heap_free_block (arg_list_p); + } + } } + + return ret_value; } /* ecma_op_function_construct */ + /** * Function declaration. * diff --git a/tests/jerry/function-prototype-bind.js b/tests/jerry/function-prototype-bind.js new file mode 100644 index 0000000000..60f5d1e98a --- /dev/null +++ b/tests/jerry/function-prototype-bind.js @@ -0,0 +1,134 @@ +// Copyright 2015 Samsung Electronics Co., Ltd. +// Copyright 2015 University of Szeged. +// +// 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 mul = function(a, b) { + return a * b; +}; + +var triple = mul.bind(null, 3); +delete mul; +assert (triple(20) === 60); +assert (triple.prototype === undefined); + +var dupl = triple.bind({}, 2); +assert (dupl() === 6); +assert (dupl.prototype === undefined); + +try { + var obj = {}; + var new_func = obj.bind(null, 'foo'); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +var obj1 = {num : 36}; + +var f1 = function(a) { + return this.num + a; +} + +var add36 = f1.bind(obj1); +assert (add36(24) === 60); + +var appendfoo = f1.bind(obj1, "foo"); +assert (appendfoo() === "36foo"); + +var f2 = function(a) { + return this.num + a.num; +} + +var sum = f2.bind(obj1, obj1); +assert (sum() === 72); + +function P(x, y) { + this.x = x; + this.y = y; +} + +var P1 = P.bind({}, 2); +var _p1 = new P1(); +assert (_p1.x === 2); +assert (_p1.y === undefined); +assert (_p1 instanceof P); +assert (_p1 instanceof P1); + +var P2 = P1.bind(null); +var _p2 = new P2(); +assert (_p2.x === 2); +assert (_p2.y === undefined); + +_p2 = new P2(12, 60); +assert (_p2.x === 2); +assert (_p2.y === 12); + +_p2 = new P2({}, 12); +assert (_p2.x === 2); +assert (Object.getPrototypeOf(_p2.y) === Object.prototype); +assert (_p2 instanceof P); +assert (_p2 instanceof P1); +assert (_p2 instanceof P2); + +var P3 = P2.bind({}, 5); +var _p3 = new P3(8); +assert (_p3.x === 2); +assert (_p3.y === 5); +assert (_p3 instanceof P); +assert (_p3 instanceof P1); +assert (_p3 instanceof P2); +assert (_p3 instanceof P3); + +var P4 = P.bind(); +P4(4, 5); +assert (x === 4); +assert (y === 5); + +var _x = x; +var _y = y; + +var P5 = P.bind(undefined); +P5(5, 4); +assert (x === _y); +assert (y === _x); + +var number = Number.constructor; +var bound = number.bind(null, 24); +var foo = new bound(); +assert (foo() === undefined); + +var number = Number; +var bound = number.bind(null, 3); +var foo = new bound(); +assert (foo == 3); +assert (foo instanceof Number); +assert (foo.prototype === undefined); + +try { + var this_obj = this.constructor; + var bound = this_obj.bind(null, "foo"); + var foo = new bound(); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +try { + var math = Math.sin; + var bound = math.bind(null, 0); + var foo = new bound(); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +}