From 91088d8b9aafb59c67fa4af33da2b52e39fb9b3c Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Thu, 21 May 2020 00:57:18 -0700 Subject: [PATCH] Fix Promise thenable. A single promise can be resolved any number of times due to thenable functions, but each resolver pair can only be called once. JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- jerry-core/ecma/operations/ecma-jobqueue.c | 2 +- .../ecma/operations/ecma-promise-object.c | 77 ++++++++++++++++--- .../ecma/operations/ecma-promise-object.h | 3 +- jerry-core/lit/lit-magic-strings.h | 1 + tests/jerry/es2015/promise-thenable.js | 32 ++++++++ 5 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 tests/jerry/es2015/promise-thenable.js diff --git a/jerry-core/ecma/operations/ecma-jobqueue.c b/jerry-core/ecma/operations/ecma-jobqueue.c index 1b3b491563..fed814b7fc 100644 --- a/jerry-core/ecma/operations/ecma-jobqueue.c +++ b/jerry-core/ecma/operations/ecma-jobqueue.c @@ -209,7 +209,7 @@ ecma_process_promise_resolve_thenable_job (ecma_job_promise_resolve_thenable_t * { ecma_object_t *promise_p = ecma_get_object_from_value (job_p->promise); ecma_promise_resolving_functions_t funcs; - ecma_promise_create_resolving_functions (promise_p, &funcs); + ecma_promise_create_resolving_functions (promise_p, &funcs, true); ecma_string_t *str_resolve_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_RESOLVE_FUNCTION); ecma_string_t *str_reject_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_REJECT_FUNCTION); diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index a50f24f8fe..dbcd0242a5 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -161,6 +161,38 @@ ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reac } } /* ecma_promise_trigger_reactions */ +/** + * Checks whether a resolver is called before. + * + * @return true if it was called before, false otherwise + */ +static bool +ecma_is_resolver_already_called (ecma_object_t *resolver_p, /**< resolver */ + ecma_object_t *promise_obj_p) /**< promise */ +{ + ecma_string_t *str_already_resolved_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_ALREADY_RESOLVED); + ecma_property_t *property_p = ecma_find_named_property (resolver_p, str_already_resolved_p); + + if (property_p == NULL) + { + return (ecma_promise_get_flags (promise_obj_p) & ECMA_PROMISE_ALREADY_RESOLVED) != 0; + } + + JERRY_ASSERT (ECMA_PROPERTY_GET_TYPE (*property_p) == ECMA_PROPERTY_TYPE_NAMEDDATA); + + ecma_value_t already_resolved = ECMA_PROPERTY_VALUE_PTR (property_p)->value; + ecma_object_t *object_p = ecma_get_object_from_value (already_resolved); + JERRY_ASSERT (ecma_get_object_type (object_p) == ECMA_OBJECT_TYPE_CLASS); + + ecma_extended_object_t *already_resolved_p = (ecma_extended_object_t *) object_p; + JERRY_ASSERT (already_resolved_p->u.class_prop.class_id == LIT_MAGIC_STRING_BOOLEAN_UL); + + ecma_value_t current_value = already_resolved_p->u.class_prop.u.value; + already_resolved_p->u.class_prop.u.value = ECMA_VALUE_TRUE; + + return current_value == ECMA_VALUE_TRUE; +} /* ecma_is_resolver_already_called */ + /** * Reject a Promise with a reason. * @@ -242,18 +274,16 @@ ecma_promise_reject_handler (const ecma_value_t function, /**< the function itse JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ - if (ecma_promise_get_flags (promise_obj_p) & ECMA_PROMISE_ALREADY_RESOLVED) + if (!ecma_is_resolver_already_called (function_p, promise_obj_p)) { - ecma_free_value (promise); - return ECMA_VALUE_UNDEFINED; - } + /* 5. */ + ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; - /* 5. */ - ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; + /* 6. */ + ecma_value_t reject_value = (argc == 0) ? ECMA_VALUE_UNDEFINED : argv[0]; + ecma_reject_promise (promise, reject_value); + } - /* 6. */ - ecma_value_t reject_value = (argc == 0) ? ECMA_VALUE_UNDEFINED : argv[0]; - ecma_reject_promise (promise, reject_value); ecma_free_value (promise); return ECMA_VALUE_UNDEFINED; } /* ecma_promise_reject_handler */ @@ -281,7 +311,7 @@ ecma_promise_resolve_handler (const ecma_value_t function, /**< the function its JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ - if (ecma_promise_get_flags (promise_obj_p) & ECMA_PROMISE_ALREADY_RESOLVED) + if (ecma_is_resolver_already_called (function_p, promise_obj_p)) { goto end_of_resolve_function; } @@ -428,7 +458,8 @@ ecma_promise_create_resolving_functions_helper (ecma_object_t *obj_p, /**< Promi */ void ecma_promise_create_resolving_functions (ecma_object_t *object_p, /**< the promise object */ - ecma_promise_resolving_functions_t *funcs) /**< [out] resolving functions */ + ecma_promise_resolving_functions_t *funcs, /**< [out] resolving functions */ + bool create_already_resolved) /**< create already resolved flag */ { /* 2. - 4. */ funcs->resolve = ecma_promise_create_resolving_functions_helper (object_p, @@ -437,6 +468,28 @@ ecma_promise_create_resolving_functions (ecma_object_t *object_p, /**< the promi /* 5. - 7. */ funcs->reject = ecma_promise_create_resolving_functions_helper (object_p, ecma_promise_reject_handler); + if (!create_already_resolved) + { + return; + } + + ecma_value_t already_resolved = ecma_op_create_boolean_object (ECMA_VALUE_FALSE); + ecma_string_t *str_already_resolved_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_ALREADY_RESOLVED); + ecma_property_value_t *value_p; + + value_p = ecma_create_named_data_property (ecma_get_object_from_value (funcs->resolve), + str_already_resolved_p, + ECMA_PROPERTY_FIXED, + NULL); + value_p->value = already_resolved; + + value_p = ecma_create_named_data_property (ecma_get_object_from_value (funcs->reject), + str_already_resolved_p, + ECMA_PROPERTY_FIXED, + NULL); + value_p->value = already_resolved; + + ecma_free_value (already_resolved); } /* ecma_promise_create_resolving_functions */ /** @@ -490,7 +543,7 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function promise_object_p->reactions = reactions; /* 8. */ ecma_promise_resolving_functions_t funcs; - ecma_promise_create_resolving_functions (object_p, &funcs); + ecma_promise_create_resolving_functions (object_p, &funcs, false); ecma_string_t *str_resolve_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_RESOLVE_FUNCTION); ecma_string_t *str_reject_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_REJECT_FUNCTION); diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index 0c4f9336d6..18bfa103bd 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -89,7 +89,8 @@ ecma_value_t ecma_promise_get_result (ecma_object_t *promise_p); ecma_value_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); -void ecma_promise_create_resolving_functions (ecma_object_t *object_p, ecma_promise_resolving_functions_t *funcs); +void ecma_promise_create_resolving_functions (ecma_object_t *object_p, ecma_promise_resolving_functions_t *funcs, + bool create_already_resolved); void ecma_promise_free_resolving_functions (ecma_promise_resolving_functions_t *funcs); /** diff --git a/jerry-core/lit/lit-magic-strings.h b/jerry-core/lit/lit-magic-strings.h index 14e8170354..1ab61aa28d 100644 --- a/jerry-core/lit/lit-magic-strings.h +++ b/jerry-core/lit/lit-magic-strings.h @@ -34,6 +34,7 @@ typedef enum LIT_NON_INTERNAL_MAGIC_STRING__COUNT, /**< number of non-internal magic strings */ LIT_INTERNAL_MAGIC_STRING_PROMISE = LIT_NON_INTERNAL_MAGIC_STRING__COUNT, /**< [[Promise]] of promise * reject or resolve functions */ + LIT_INTERNAL_MAGIC_STRING_ALREADY_RESOLVED, /**< [[AlreadyResolved]] of promise reject or resolve functions */ LIT_INTERNAL_MAGIC_STRING_RESOLVE_FUNCTION, /**< the resolve funtion of the promise object */ LIT_INTERNAL_MAGIC_STRING_REJECT_FUNCTION, /**< the reject function of the promise object */ LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_PROMISE, /**< [[Promise]] property */ diff --git a/tests/jerry/es2015/promise-thenable.js b/tests/jerry/es2015/promise-thenable.js new file mode 100644 index 0000000000..4a47e57fa2 --- /dev/null +++ b/tests/jerry/es2015/promise-thenable.js @@ -0,0 +1,32 @@ +/* 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. + */ + +var counter = 0; + +function f() { } + +f.then = function(resolve) { + if (++counter < 10) { + resolve(f) + } +} + +new Promise(function(resolve) { + resolve(f) +}) + +function __checkAsync() { + assert(counter == 10) +}