diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index eab3ae34d..3597c376c 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -795,6 +795,23 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ ecma_gc_set_object_visited (ecma_get_object_from_value (executor_p->values)); ecma_gc_set_object_visited (ecma_get_object_from_value (executor_p->remaining_elements)); } + else if (ext_func_p->u.external_handler_cb == ecma_promise_then_finally_cb + || ext_func_p->u.external_handler_cb == ecma_promise_catch_finally_cb) + { + ecma_promise_finally_function_t *finally_obj_p = (ecma_promise_finally_function_t *) object_p; + ecma_gc_set_object_visited (ecma_get_object_from_value (finally_obj_p->constructor)); + ecma_gc_set_object_visited (ecma_get_object_from_value (finally_obj_p->on_finally)); + } + else if (ext_func_p->u.external_handler_cb == ecma_value_thunk_helper_cb + || ext_func_p->u.external_handler_cb == ecma_value_thunk_thrower_cb) + { + ecma_promise_value_thunk_t *thunk_obj_p = (ecma_promise_value_thunk_t *) object_p; + + if (ecma_is_value_object (thunk_obj_p->value)) + { + ecma_gc_set_object_visited (ecma_get_object_from_value (thunk_obj_p->value)); + } + } break; } #endif /* ENABLED (JERRY_ESNEXT) */ @@ -1162,6 +1179,20 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ { ext_object_size = sizeof (ecma_promise_all_executor_t); } + else if (ext_func_p->u.external_handler_cb == ecma_promise_then_finally_cb + || ext_func_p->u.external_handler_cb == ecma_promise_catch_finally_cb) + { + ext_object_size = sizeof (ecma_promise_finally_function_t); + } + else if (ext_func_p->u.external_handler_cb == ecma_value_thunk_helper_cb + || ext_func_p->u.external_handler_cb == ecma_value_thunk_thrower_cb) + { + ecma_promise_value_thunk_t *thunk_obj_p = (ecma_promise_value_thunk_t *) object_p; + + ecma_free_value_if_not_object (thunk_obj_p->value); + + ext_object_size = sizeof (ecma_promise_value_thunk_t); + } #endif /* ENABLED (JERRY_ESNEXT) */ break; } diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.c index a5706debc..14bf4688c 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.c @@ -69,6 +69,22 @@ ecma_builtin_promise_prototype_catch (ecma_value_t this_arg, /**< this argument return ecma_op_invoke_by_magic_id (this_arg, LIT_MAGIC_STRING_THEN, args, 2); } /* ecma_builtin_promise_prototype_catch */ +/** + * Promise routine: finally. + * + * See also: + * ECMA-262 v11, 25.6.5.3 + * + * @return ecma value of a new promise object. + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_promise_prototype_finally (ecma_value_t this_arg, /**< this argument */ + ecma_value_t on_finally) /**< on_finally function */ +{ + return ecma_promise_finally (this_arg, on_finally); +} /* ecma_builtin_promise_prototype_finally */ + /** * @} * @} diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.inc.h index de5d07ec2..6d026dcb5 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-promise-prototype.inc.h @@ -31,6 +31,7 @@ STRING_VALUE (LIT_GLOBAL_SYMBOL_TO_STRING_TAG, ROUTINE (LIT_MAGIC_STRING_THEN, ecma_builtin_promise_prototype_then, 2, 2) ROUTINE (LIT_MAGIC_STRING_CATCH, ecma_builtin_promise_prototype_catch, 1, 1) +ROUTINE (LIT_MAGIC_STRING_FINALLY, ecma_builtin_promise_prototype_finally, 1, 1) #endif /* ENABLED (JERRY_BUILTIN_PROMISE) */ diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index 56701997f..9762c1368 100644 --- a/jerry-core/ecma/operations/ecma-function-object.c +++ b/jerry-core/ecma/operations/ecma-function-object.c @@ -19,6 +19,7 @@ #include "ecma-function-object.h" #include "ecma-gc.h" #include "ecma-helpers.h" +#include "ecma-promise-object.h" #include "lit-char-helpers.h" #include "ecma-lex-env.h" #include "ecma-objects.h" @@ -1616,6 +1617,25 @@ ecma_op_external_function_try_to_lazy_instantiate_property (ecma_object_t *objec return ecma_op_lazy_instantiate_prototype_object (object_p); } +#if ENABLED (JERRY_ESNEXT) + if (ecma_compare_ecma_string_to_magic_id (property_name_p, LIT_MAGIC_STRING_LENGTH)) + { + ecma_extended_object_t *ext_obj_p = (ecma_extended_object_t *) object_p; + + if (ext_obj_p->u.external_handler_cb == ecma_promise_then_finally_cb + || ext_obj_p->u.external_handler_cb == ecma_promise_catch_finally_cb) + { + ecma_property_t *value_prop_p; + ecma_property_value_t *value_p = ecma_create_named_data_property (object_p, + property_name_p, + ECMA_PROPERTY_FLAG_CONFIGURABLE, + &value_prop_p); + value_p->value = ecma_make_uint32_value (1); + return value_prop_p; + } + } +#endif /* ENABLED (JERRY_ESNEXT) */ + return NULL; } /* ecma_op_external_function_try_to_lazy_instantiate_property */ diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index b1286ae0e..68b6ab181 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -944,6 +944,242 @@ ecma_promise_then (ecma_value_t promise, /**< the promise which call 'then' */ return ret; } /* ecma_promise_then */ +/** + * Definition of valueThunk function + * + * See also: + * ES2020 25.6.5.3.1 step 8. + * + * @return ecma value + */ +ecma_value_t +ecma_value_thunk_helper_cb (const ecma_value_t function_obj, /**< the function itself */ + const ecma_value_t this_val, /**< this_arg of the function */ + const ecma_value_t args_p[], /**< argument list */ + const uint32_t args_count) /**< argument number */ +{ + JERRY_UNUSED_3 (this_val, args_p, args_count); + + ecma_object_t *func_obj_p = ecma_get_object_from_value (function_obj); + ecma_promise_value_thunk_t *value_thunk_obj_p = (ecma_promise_value_thunk_t *) func_obj_p; + + return ecma_copy_value (value_thunk_obj_p->value); +} /* ecma_value_thunk_helper_cb */ + +/** + * Definition of thrower function + * + * See also: + * ES2020 25.6.5.3.2 step 8. + * + * @return ecma value + */ +ecma_value_t +ecma_value_thunk_thrower_cb (const ecma_value_t function_obj, /**< the function itself */ + const ecma_value_t this_val, /**< this_arg of the function */ + const ecma_value_t args_p[], /**< argument list */ + const uint32_t args_count) /**< argument number */ +{ + JERRY_UNUSED_3 (this_val, args_p, args_count); + + ecma_object_t *func_obj_p = ecma_get_object_from_value (function_obj); + ecma_promise_value_thunk_t *value_thunk_obj_p = (ecma_promise_value_thunk_t *) func_obj_p; + + jcontext_raise_exception (ecma_copy_value (value_thunk_obj_p->value)); + + return ECMA_VALUE_ERROR; +} /* ecma_value_thunk_thrower_cb */ + +/** + * Helper function for Then Finally and Catch Finally common parts + * + * See also: + * ES2020 25.6.5.3.1 + * ES2020 25.6.5.3.2 + * + * @return ecma value + */ +static ecma_value_t +ecma_promise_than_catch_finally_helper (ecma_value_t function_obj, /**< the function itself */ + ecma_external_handler_t ext_func_obj, /**< external function object */ + ecma_value_t arg) /**< callback function argument */ +{ + /* 2. */ + ecma_object_t *func_obj_p = ecma_get_object_from_value (function_obj); + ecma_promise_finally_function_t *finally_func_obj = (ecma_promise_finally_function_t *) func_obj_p; + + /* 3. */ + JERRY_ASSERT (ecma_op_is_callable (finally_func_obj->on_finally)); + + /* 4. */ + ecma_value_t result = ecma_op_function_call (ecma_get_object_from_value (finally_func_obj->on_finally), + ECMA_VALUE_UNDEFINED, + NULL, + 0); + + if (ECMA_IS_VALUE_ERROR (result)) + { + return result; + } + + /* 6. */ + JERRY_ASSERT (ecma_is_constructor (finally_func_obj->constructor)); + + /* 7. */ + ecma_value_t promise = ecma_promise_reject_or_resolve (finally_func_obj->constructor, result, true); + + ecma_free_value (result); + + if (ECMA_IS_VALUE_ERROR (promise)) + { + return promise; + } + + /* 8. */ + ecma_object_t *value_thunk_func_p; + value_thunk_func_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), + sizeof (ecma_promise_value_thunk_t), + ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); + + ecma_promise_value_thunk_t *value_thunk_func_obj = (ecma_promise_value_thunk_t *) value_thunk_func_p; + value_thunk_func_obj->header.u.external_handler_cb = ext_func_obj; + + value_thunk_func_obj->value = ecma_copy_value_if_not_object (arg); + + /* 9. */ + ecma_value_t value_thunk = ecma_make_object_value (value_thunk_func_p); + ecma_value_t ret_value = ecma_op_invoke_by_magic_id (promise, LIT_MAGIC_STRING_THEN, &value_thunk, 1); + + ecma_free_value (promise); + ecma_deref_object (value_thunk_func_p); + + return ret_value; +} /* ecma_promise_than_catch_finally_helper */ + +/** + * Definition of Then Finally Function + * + * See also: + * ES2020 25.6.5.3.1 + * + * @return ecma value + */ +ecma_value_t +ecma_promise_then_finally_cb (const ecma_value_t function_obj, /**< the function itself */ + const ecma_value_t this_val, /**< this_arg of the function */ + const ecma_value_t args_p[], /**< argument list */ + const uint32_t args_count) /**< argument number */ +{ + JERRY_UNUSED_2 (this_val, args_count); + JERRY_ASSERT (args_count > 0); + + return ecma_promise_than_catch_finally_helper (function_obj, ecma_value_thunk_helper_cb, args_p[0]); +} /* ecma_promise_then_finally_cb */ + +/** + * Definition of Catch Finally Function + * + * See also: + * ES2020 25.6.5.3.2 + * + * @return ecma value + */ +ecma_value_t +ecma_promise_catch_finally_cb (const ecma_value_t function_obj, /**< the function itself */ + const ecma_value_t this_val, /**< this_arg of the function */ + const ecma_value_t args_p[], /**< argument list */ + const uint32_t args_count) /**< argument number */ +{ + JERRY_UNUSED_2 (this_val, args_count); + JERRY_ASSERT (args_count > 0); + + return ecma_promise_than_catch_finally_helper (function_obj, ecma_value_thunk_thrower_cb, args_p[0]); +} /* ecma_promise_catch_finally_cb */ + +/** + * The common function for ecma_builtin_promise_prototype_finally + * + * @return ecma value of a new promise object. + * Returned value must be freed with ecma_free_value. + */ +ecma_value_t +ecma_promise_finally (ecma_value_t promise, /**< the promise which call 'finally' */ + ecma_value_t on_finally) /**< on_finally function */ +{ + /* 2. */ + if (!ecma_is_value_object (promise)) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not an object.")); + } + + ecma_object_t *obj = ecma_get_object_from_value (promise); + + /* 3. */ + ecma_value_t species = ecma_op_species_constructor (obj, ECMA_BUILTIN_ID_PROMISE); + + if (ECMA_IS_VALUE_ERROR (species)) + { + return species; + } + + /* 4. */ + JERRY_ASSERT (ecma_is_constructor (species)); + + /* 5. */ + if (!ecma_op_is_callable (on_finally)) + { + ecma_free_value (species); + ecma_value_t invoke_args[2] = {on_finally, on_finally}; + return ecma_op_invoke_by_magic_id (promise, LIT_MAGIC_STRING_THEN, invoke_args, 2); + } + + /* 6. a,b */ + ecma_object_t *then_finally_obj_p; + then_finally_obj_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), + sizeof (ecma_promise_finally_function_t), + ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); + + ecma_promise_finally_function_t *then_finally_func_obj = (ecma_promise_finally_function_t *) then_finally_obj_p; + then_finally_func_obj->header.u.external_handler_cb = ecma_promise_then_finally_cb; + + /* 6.c */ + then_finally_func_obj->constructor = species; + + /* 6.d*/ + then_finally_func_obj->on_finally = on_finally; + + /* 6. e,f */ + ecma_object_t *catch_finally_obj_p; + catch_finally_obj_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), + sizeof (ecma_promise_finally_function_t), + ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); + + ecma_promise_finally_function_t *catch_finally_func_obj = (ecma_promise_finally_function_t *) catch_finally_obj_p; + catch_finally_func_obj->header.u.external_handler_cb = ecma_promise_catch_finally_cb; + + /* 6.g */ + catch_finally_func_obj->constructor = species; + + /* 6.h */ + catch_finally_func_obj->on_finally = on_finally; + + ecma_free_value (species); + + /* 7. */ + ecma_value_t invoke_args[2] = + { + ecma_make_object_value (then_finally_obj_p), + ecma_make_object_value (catch_finally_obj_p) + }; + + ecma_value_t ret_value = ecma_op_invoke_by_magic_id (promise, LIT_MAGIC_STRING_THEN, invoke_args, 2); + + ecma_deref_object (then_finally_obj_p); + ecma_deref_object (catch_finally_obj_p); + + return ret_value; +} /* ecma_promise_finally */ + /** * Resume the execution of an async function after the promise is resolved */ diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index 8496b26b4..6d1a12190 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -64,6 +64,25 @@ typedef struct ecma_collection_t *reactions; /**< list of promise reactions */ } ecma_promise_object_t; +/** + * Description of the finally function object + */ +typedef struct +{ + ecma_extended_object_t header; /**< extended object part */ + ecma_value_t constructor; /**< [[Constructor]] internal slot */ + ecma_value_t on_finally; /**< [[OnFinally]] internal slot */ +} ecma_promise_finally_function_t; + +/** + * Description of the thunk function object + */ +typedef struct +{ + ecma_extended_object_t header; /**< extended object part */ + ecma_value_t value; /**< value thunk */ +} ecma_promise_value_thunk_t; + /* The Promise reaction is a compressed structure, where each item can * be a sequence of up to three ecma object values as seen below: * @@ -90,6 +109,23 @@ void ecma_fulfill_promise (ecma_value_t promise, ecma_value_t value); ecma_object_t *ecma_promise_new_capability (ecma_value_t constructor); ecma_value_t ecma_promise_reject_or_resolve (ecma_value_t this_arg, ecma_value_t value, bool is_resolve); ecma_value_t ecma_promise_then (ecma_value_t promise, ecma_value_t on_fulfilled, ecma_value_t on_rejected); +ecma_value_t ecma_value_thunk_helper_cb (const ecma_value_t function_obj, + const ecma_value_t this_val, + const ecma_value_t args_p[], + const uint32_t args_count); +ecma_value_t ecma_value_thunk_thrower_cb (const ecma_value_t function_obj, + const ecma_value_t this_val, + const ecma_value_t args_p[], + const uint32_t args_count); +ecma_value_t ecma_promise_then_finally_cb (const ecma_value_t function_obj, + const ecma_value_t this_val, + const ecma_value_t args_p[], + const uint32_t args_count); +ecma_value_t ecma_promise_catch_finally_cb (const ecma_value_t function_obj, + const ecma_value_t this_val, + const ecma_value_t args_p[], + const uint32_t args_count); +ecma_value_t ecma_promise_finally (ecma_value_t promise, ecma_value_t on_finally); void ecma_promise_async_then (ecma_value_t promise, ecma_value_t executable_object); ecma_value_t ecma_promise_async_await (ecma_extended_object_t *async_generator_object_p, ecma_value_t value); void ecma_promise_create_resolving_functions (ecma_object_t *object_p, ecma_promise_resolving_functions_t *funcs, diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index 246108a53..d0338347f 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -455,6 +455,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_DEFAULT, "default") || ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ENTRIES, "entries") #endif +#if ENABLED (JERRY_BUILTIN_PROMISE) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_FINALLY, "finally") +#endif #if ENABLED (JERRY_BUILTIN_ARRAY) \ || ENABLED (JERRY_BUILTIN_MAP) \ || ENABLED (JERRY_BUILTIN_SET) \ diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index 2578d09bb..d2a48b8f0 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -175,6 +175,7 @@ LIT_MAGIC_STRING_ENTRIES = "entries" LIT_MAGIC_STRING_TO_JSON_UL = "toJSON" LIT_MAGIC_STRING_VALUES = "values" LIT_MAGIC_STRING_BOOLEAN_UL = "Boolean" +LIT_MAGIC_STRING_FINALLY = "finally" LIT_MAGIC_STRING_PROMISE_UL = "Promise" LIT_MAGIC_STRING_REFLECT_UL = "Reflect" LIT_MAGIC_STRING_SQRT1_2_U = "SQRT1_2" diff --git a/tests/jerry/es.next/promise-on-finally.js b/tests/jerry/es.next/promise-on-finally.js new file mode 100644 index 000000000..cb7272c13 --- /dev/null +++ b/tests/jerry/es.next/promise-on-finally.js @@ -0,0 +1,145 @@ +/* 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. + */ + +// test basic functionality +var order = []; +var reason = {}; +var reject = Promise.reject(reason); +reject.then = function() { + order.push(1); + return Promise.prototype.then.apply(this, arguments); +}; + +var value = {}; +var resolve = Promise.resolve(value); +resolve.then = function() { + order.push(4); + return Promise.prototype.then.apply(this, arguments); +}; + +reject.catch(function(e) { + order.push(2); + throw e; +}).finally(function() { + order.push(3); + return resolve; +}).catch(function(e) { + order.push(5); +}); + +function __checkAsync() { + assert(order.length === 5); + for (var i = 0; i < order.length; i++) + { + assert(i + 1 === order[i]); + } +} + +// check length property +var desc = Object.getOwnPropertyDescriptor(Promise.prototype.finally, "length"); +assert(desc.value === 1); +assert(desc.enumerable === false); +assert(desc.configurable === true); +assert(desc.writable === false); + +// invokes then with function +var target = new Promise(function() {}); +var returnValue = {}; +var callCount = 0; +var thisValue = null; +var argCount = null; +var firstArg = null; +var secondArg = null; + +target.then = function(a, b) { + callCount += 1; + + thisValue = this; + argCount = arguments.length; + firstArg = a; + secondArg = b; + + return returnValue; +}; + +var originalFinallyHandler = function() {}; +var result = Promise.prototype.finally.call(target, originalFinallyHandler, 2, 3); + +(callCount === 1); +assert(thisValue === target); +assert(argCount === 2); +assert(typeof firstArg === 'function'); +assert(firstArg.length === 1); +assert(typeof secondArg === 'function'); +assert(secondArg.length === 1); +assert(result === returnValue); + +// invokes then with non-function +result = Promise.prototype.finally.call(target, 1, 2, 3); + +assert(callCount === 2); +assert(thisValue === target); +assert(argCount === 2); +assert(firstArg === 1); +assert(secondArg === 1); +assert(result == returnValue); + +// thes when 'then' is not callable +var thrower = function() { + throw 42; +}; + +var symbol = Symbol(); +var p = new Promise(function() {}); + +p.then = undefined; +try { + Promise.prototype.finally.call(p, thrower); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +p.then = null; +try { + Promise.prototype.finally.call(p, thrower); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +p.then = 1; +try { + Promise.prototype.finally.call(p, thrower); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +p.then = symbol; +try { + Promise.prototype.finally.call(p, thrower); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +p.then = {}; +try { + Promise.prototype.finally.call(p, thrower); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +}