From 01e0388d77c430b5b9fb7a82997329e18e65ee31 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Thu, 18 Feb 2021 11:29:52 +0100 Subject: [PATCH] Add notification callback for Promise operations (#4595) JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/01.CONFIGURATION.md | 11 + docs/02.API-REFERENCE.md | 151 +++++++++ jerry-core/CMakeLists.txt | 5 + jerry-core/api/jerry.c | 25 +- jerry-core/config.h | 22 +- .../builtin-objects/ecma-builtin-promise.c | 4 +- .../operations/ecma-async-generator-object.c | 2 +- jerry-core/ecma/operations/ecma-jobqueue.c | 67 +++- .../ecma/operations/ecma-promise-object.c | 95 ++++-- .../ecma/operations/ecma-promise-object.h | 13 +- jerry-core/include/jerryscript-core.h | 50 +++ jerry-core/jcontext/jcontext.h | 4 + jerry-core/vm/opcodes.c | 2 +- jerry-core/vm/vm.c | 2 +- tests/unit-core/CMakeLists.txt | 1 + tests/unit-core/test-promise-callback.c | 286 ++++++++++++++++++ tools/build.py | 3 + tools/run-tests.py | 6 +- 18 files changed, 703 insertions(+), 46 deletions(-) create mode 100644 tests/unit-core/test-promise-callback.c diff --git a/docs/01.CONFIGURATION.md b/docs/01.CONFIGURATION.md index 27969fcca..aa10c92c4 100644 --- a/docs/01.CONFIGURATION.md +++ b/docs/01.CONFIGURATION.md @@ -53,6 +53,17 @@ To see how a profile file should be created, or what configuration options are a | CMake: | `-DJERRY_PROFILE="path"` | | Python: | `--profile="path"` | +### Promise callback + +Enables Promise event notification support. This feature allows setting a user callback, which is called when certain Promise related events occur such as +creating a new Promise, resolving a Promise with a value, etc. + +| Options | | +|---------|----------------------------------------------| +| C: | `-DJERRY_PROMISE_CALLBACK=0/1` | +| CMake: | `-DJERRY_PROMISE_CALLBACK=ON/OFF` | +| Python: | `--promise-callback=ON/OFF` | + ### External context Enables external context support in the engine. By default, JerryScript uses a statically allocated context to store the current state of the engine internals. diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index b5cd027cb..bc8762a98 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -157,6 +157,8 @@ Possible compile time enabled feature types: - JERRY_FEATURE_WEAKSET - WeakSet support - JERRY_FEATURE_BIGINT - BigInt support - JERRY_FEATURE_REALM - realm support + - JERRY_FEATURE_GLOBAL_THIS - GlobalThisValue support + - JERRY_FEATURE_PROMISE_CALLBACK - Promise callback support *New in version 2.0*. @@ -164,6 +166,8 @@ Possible compile time enabled feature types: *Changed in version 2.4*: Added `JERRY_FEATURE_BIGINT`, `JERRY_FEATURE_REALM` values. +*Changed in version [[NEXT_RELEASE]]*: Added `JERRY_FEATURE_GLOBAL_THIS`, `JERRY_FEATURE_PROMISE_CALLBACK` values. + ## jerry_container_type_t Container object types: @@ -883,6 +887,80 @@ Possible values: - [jerry_get_promise_result](#jerry_get_promise_result) +## jerry_promise_event_type_t + +Event types for [jerry_promise_callback_t](#jerry_promise_callback_t) callback function. +The description of the `object` and `value` arguments are provided for each type. + +Possible values: + +- JERRY_PROMISE_EVENT_CREATE - A new Promise object is created. + - object - the new Promise object + - value - parent Promise for `then` chains, undefined otherwise. +- JERRY_PROMISE_EVENT_RESOLVE - Called when a Promise is about to be resolved. + - object - the Promise object + - value - value for resolving. +- JERRY_PROMISE_EVENT_REJECT - Called when a Promise is about to be rejected. + - object - the Promise object + - value - value for rejecting. +- JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB - Called before executing a Promise reaction job. + - object - the Promise object + - value - undefined +- JERRY_PROMISE_EVENT_AFTER_REACTION_JOB - Called after a Promise reaction job is completed. + - object - the Promise object + - value - undefined +- JERRY_PROMISE_EVENT_ASYNC_AWAIT - Called when an async function awaits the result of a Promise object. + - object - internal object representing the execution status + - value - the Promise object +- JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE - Called when an async function is continued with resolve. + - object - internal object representing the execution status + - value - value for resolving +- JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT - Called when an async function is continued with reject. + - object - internal object representing the execution status + - value - value for rejecting +- JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE - Called when an async function resolve is completed. + - object - internal object representing the execution status + - value - value for resolving +- JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT - Called when an async function reject is completed. + - object - internal object representing the execution status + - value - value for rejecting + +*New in version [[NEXT_RELEASE]]*. + +**See also** + +- [jerry_promise_callback_t](#jerry_promise_callback_t) +- [jerry_promise_set_callback](#jerry_promise_set_callback) + + +## jerry_promise_callback_t + +**Summary** + +Notification callback for tracking Promise and async function operations. The arguments +passed to the callback depends on the `event_type` which is detailed in the +description of [jerry_promise_event_type_t](#jerry_promise_event_type_t). + +**Prototype** + +```c +typedef void (*jerry_promise_callback_t) (jerry_promise_event_type_t event_type, + const jerry_value_t object, const jerry_value_t value, + void *user_p); +``` + +- `event_type` - type of the event notification. +- `object` - object corresponding to the event. +- `value` - optional value argument. +- `user_data_p` - optional user data pointer supplied via the (jerry_promise_set_callback)[#jerry_promise_set_callback] method. + +*New in version [[NEXT_RELEASE]]*. + +**See also** + +- [jerry_promise_event_type_t](#jerry_promise_event_type_t) +- [jerry_promise_set_callback](#jerry_promise_set_callback) + ## jerry_typedarray_type_t Enum which describes the TypedArray types. @@ -4213,6 +4291,79 @@ example (void) - [jerry_create_promise](#jerry_create_promise) - [jerry_promise_state_t](#jerry_promise_state_t) +## jerry_promise_set_callback + +**Summary** + +Sets a callback for tracking Promise and async operations. + +*Notes*: +- This API depends on a build option (`JERRY_PROMISE_CALLBACK`) and can be checked + in runtime with the `JERRY_FEATURE_PROMISE_CALLBACK` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). + + +**Prototype** + +```c +void jerry_promise_set_callback (jerry_promise_callback_t callback, void *user_p); +``` + +- `callback` - callback function, the previously set value is overwritten, + and setting NULL disables the tracking +- `user_p` - pointer passed to the callback function, can be NULL + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +[doctest]: # () + +```c +#include +#include +#include "jerryscript.h" + +static void +promise_callback (jerry_promise_event_type_t event_type, /**< event type */ + const jerry_value_t object, /**< target object */ + const jerry_value_t value, /**< optional argument */ + void *user_p) /**< user pointer passed to the callback */ +{ + if (event_type == JERRY_PROMISE_EVENT_CREATE) + { + printf ("A new promise is created\n"); + + if (!jerry_value_is_undefined (value)) + { + printf (" The Promise is created by Promise.then() built-in.\n"); + } + } +} /* promise_callback */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_promise_set_callback (promise_callback, NULL); + + const char *source_p = "var p = Promise.resolve(0)\n" + "p.then(function (v) { return v; })"; + jerry_release_value (jerry_eval ((const jerry_char_t *) source_p, + strlen (source_p), + JERRY_PARSE_NO_OPTS)); + + jerry_cleanup (); + return 0; +} /* main */ +``` + +**See also** + +- [jerry_create_promise](#jerry_create_promise) +- [jerry_promise_state_t](#jerry_promise_state_t) + ## jerry_from_property_descriptor **Summary** diff --git a/jerry-core/CMakeLists.txt b/jerry-core/CMakeLists.txt index 46a2b89a1..442fe94ca 100644 --- a/jerry-core/CMakeLists.txt +++ b/jerry-core/CMakeLists.txt @@ -30,6 +30,7 @@ set(JERRY_MEM_STATS OFF CACHE BOOL "Enable memory sta set(JERRY_MEM_GC_BEFORE_EACH_ALLOC OFF CACHE BOOL "Enable mem-stress test?") set(JERRY_PARSER_DUMP_BYTE_CODE OFF CACHE BOOL "Enable parser byte-code dumps?") set(JERRY_PROFILE "es.next" CACHE STRING "Use default or other profile?") +set(JERRY_PROMISE_CALLBACK OFF CACHE BOOL "Enable Promise callbacks?") set(JERRY_REGEXP_STRICT_MODE OFF CACHE BOOL "Enable regexp strict mode?") set(JERRY_REGEXP_DUMP_BYTE_CODE OFF CACHE BOOL "Enable regexp byte-code dumps?") set(JERRY_SNAPSHOT_EXEC OFF CACHE BOOL "Enable executing snapshot files?") @@ -87,6 +88,7 @@ message(STATUS "JERRY_MEM_STATS " ${JERRY_MEM_STATS}) message(STATUS "JERRY_MEM_GC_BEFORE_EACH_ALLOC " ${JERRY_MEM_GC_BEFORE_EACH_ALLOC}) message(STATUS "JERRY_PARSER_DUMP_BYTE_CODE " ${JERRY_PARSER_DUMP_BYTE_CODE} ${JERRY_PARSER_DUMP_MESSAGE}) message(STATUS "JERRY_PROFILE " ${JERRY_PROFILE}) +message(STATUS "JERRY_PROMISE_CALLBACK " ${JERRY_PROMISE_CALLBACK}) message(STATUS "JERRY_REGEXP_STRICT_MODE " ${JERRY_REGEXP_STRICT_MODE}) message(STATUS "JERRY_REGEXP_DUMP_BYTE_CODE " ${JERRY_REGEXP_DUMP_BYTE_CODE}) message(STATUS "JERRY_SNAPSHOT_EXEC " ${JERRY_SNAPSHOT_EXEC} ${JERRY_SNAPSHOT_EXEC_MESSAGE}) @@ -582,6 +584,9 @@ else() message(FATAL_ERROR "Profile file: '${JERRY_PROFILE}' doesn't exist!") endif() +# Promise callback +jerry_add_define01(JERRY_PROMISE_CALLBACK) + # RegExp strict mode jerry_add_define01(JERRY_REGEXP_STRICT_MODE) diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index f58edd3ed..952af5457 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -1467,6 +1467,9 @@ jerry_is_feature_enabled (const jerry_feature_t feature) /**< feature to check * #if JERRY_BUILTIN_REALMS || feature == JERRY_FEATURE_REALM #endif /* JERRY_BUILTIN_REALMS */ +#if JERRY_PROMISE_CALLBACK + || feature == JERRY_FEATURE_PROMISE_CALLBACK +#endif /* JERRY_PROMISE_CALLBACK */ ); } /* jerry_is_feature_enabled */ @@ -2194,7 +2197,7 @@ jerry_create_promise (void) JERRY_CONTEXT (current_new_target_p) = ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE); } - ecma_value_t promise_value = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_PROMISE_EXECUTOR_EMPTY); + ecma_value_t promise_value = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_VALUE_UNDEFINED); JERRY_CONTEXT (current_new_target_p) = old_new_target_p; return promise_value; @@ -4353,6 +4356,26 @@ jerry_get_promise_state (const jerry_value_t promise) /**< promise object to get #endif /* JERRY_BUILTIN_PROMISE */ } /* jerry_get_promise_state */ +/** + * Sets a callback for tracking Promise and async operations. + * + * Note: + * the previous callback is overwritten + */ +void jerry_promise_set_callback (jerry_promise_callback_t callback, /**< notification callback */ + void *user_p) /**< user pointer passed to the callback */ +{ + jerry_assert_api_available (); + +#if JERRY_BUILTIN_PROMISE && JERRY_PROMISE_CALLBACK + JERRY_CONTEXT (promise_callback) = callback; + JERRY_CONTEXT (promise_callback_user_p) = user_p; +#else /* !JERRY_BUILTIN_PROMISE && !JERRY_PROMISE_CALLBACK */ + JERRY_UNUSED (callback); + JERRY_UNUSED (user_p); +#endif /* JERRY_BUILTIN_PROMISE && JERRY_PROMISE_CALLBACK */ +} /* jerry_promise_set_callback */ + /** * Get the well-knwon symbol represented by the given `symbol` enum value. * diff --git a/jerry-core/config.h b/jerry-core/config.h index 6a682ba53..e21c4546b 100644 --- a/jerry-core/config.h +++ b/jerry-core/config.h @@ -348,6 +348,15 @@ # define JERRY_PROPRETY_HASHMAP 1 #endif /* !defined (JERRY_PROPRETY_HASHMAP) */ +/** + * Enables/disables the Promise event callbacks + * + * Default value: 0 + */ +#ifndef JERRY_PROMISE_CALLBACK +# define JERRY_PROMISE_CALLBACK 0 +#endif /* !defined (JERRY_PROMISE_CALLBACK) */ + /** * Enable/Disable byte code dump functions for RegExp objects. * To dump the RegExp byte code the engine must be initialized with @@ -666,6 +675,10 @@ || ((JERRY_PROPRETY_HASHMAP != 0) && (JERRY_PROPRETY_HASHMAP != 1)) # error "Invalid value for 'JERRY_PROPRETY_HASHMAP' macro." #endif +#if !defined (JERRY_PROMISE_CALLBACK) \ +|| ((JERRY_PROMISE_CALLBACK != 0) && (JERRY_PROMISE_CALLBACK != 1)) +# error "Invalid value for 'JERRY_PROMISE_CALLBACK' macro." +#endif #if !defined (JERRY_REGEXP_DUMP_BYTE_CODE) \ || ((JERRY_REGEXP_DUMP_BYTE_CODE != 0) && (JERRY_REGEXP_DUMP_BYTE_CODE != 1)) # error "Invalid value for 'JERRY_REGEXP_DUMP_BYTE_CODE' macro." @@ -702,14 +715,21 @@ /** * Cross component requirements check. */ + /** * The date module can only use the float 64 number types. - * Do a check for this. */ #if JERRY_BUILTIN_DATE && !JERRY_NUMBER_TYPE_FLOAT64 # error "Date does not support float32" #endif +/** + * Promise support must be enabled if Promise callback support is enabled. + */ +#if JERRY_PROMISE_CALLBACK && !JERRY_BUILTIN_PROMISE +# error "Promise callback support depends on Promise support" +#endif /* JERRY_PROMISE_CALLBACK && !JERRY_BUILTIN_PROMISE */ + /** * Wrap container types into a single guard */ diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c b/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c index 4cd73552f..cbbfa1091 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-promise.c @@ -375,7 +375,7 @@ ecma_builtin_promise_race_or_all (ecma_value_t this_arg, /**< 'this' argument */ ecma_value_t iterable, /**< the items to be resolved */ bool is_race) /**< indicates whether it is race function */ { - ecma_object_t *capability_obj_p = ecma_promise_new_capability (this_arg); + ecma_object_t *capability_obj_p = ecma_promise_new_capability (this_arg, ECMA_VALUE_UNDEFINED); if (JERRY_UNLIKELY (capability_obj_p == NULL)) { @@ -455,7 +455,7 @@ ecma_builtin_promise_dispatch_construct (const ecma_value_t *arguments_list_p, / return ecma_raise_type_error (ECMA_ERR_MSG ("First parameter must be callable")); } - return ecma_op_create_promise_object (arguments_list_p[0], ECMA_PROMISE_EXECUTOR_FUNCTION); + return ecma_op_create_promise_object (arguments_list_p[0], ECMA_VALUE_UNDEFINED); } /* ecma_builtin_promise_dispatch_construct */ /** diff --git a/jerry-core/ecma/operations/ecma-async-generator-object.c b/jerry-core/ecma/operations/ecma-async-generator-object.c index 6ef633778..ca5a82ce4 100644 --- a/jerry-core/ecma/operations/ecma-async-generator-object.c +++ b/jerry-core/ecma/operations/ecma-async-generator-object.c @@ -57,7 +57,7 @@ ecma_async_generator_enqueue (vm_executable_object_t *async_generator_object_p, ecma_object_t *old_new_target_p = JERRY_CONTEXT (current_new_target_p); JERRY_CONTEXT (current_new_target_p) = ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE); - ecma_value_t result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_PROMISE_EXECUTOR_EMPTY); + ecma_value_t result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_VALUE_UNDEFINED); JERRY_CONTEXT (current_new_target_p) = old_new_target_p; task_p->promise = result; diff --git a/jerry-core/ecma/operations/ecma-jobqueue.c b/jerry-core/ecma/operations/ecma-jobqueue.c index 9c67f723a..85286d331 100644 --- a/jerry-core/ecma/operations/ecma-jobqueue.c +++ b/jerry-core/ecma/operations/ecma-jobqueue.c @@ -186,6 +186,17 @@ ecma_process_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< the LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY)); ecma_promise_capabality_t *capability_p; capability_p = (ecma_promise_capabality_t *) ecma_get_object_from_value (job_p->capability); + +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB, + capability_p->header.u.class_prop.u.promise, + ECMA_VALUE_UNDEFINED, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + /* 3. */ ecma_value_t handler = job_p->handler; @@ -232,6 +243,17 @@ ecma_process_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< the } ecma_free_value (handler_result); + +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_AFTER_REACTION_JOB, + capability_p->header.u.class_prop.u.promise, + ECMA_VALUE_UNDEFINED, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + ecma_free_promise_reaction_job (job_p); return status; @@ -246,6 +268,23 @@ ecma_process_promise_reaction_job (ecma_job_promise_reaction_t *job_p) /**< the static ecma_value_t ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_p) /**< the job to be operated */ { +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + jerry_promise_event_type_t type = JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE; + + if (ecma_job_queue_get_type (&job_p->header) == ECMA_JOB_PROMISE_ASYNC_REACTION_REJECTED) + { + type = JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT; + } + + JERRY_CONTEXT (promise_callback) (type, + job_p->executable_object, + job_p->argument, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + ecma_object_t *object_p = ecma_get_object_from_value (job_p->executable_object); vm_executable_object_t *executable_object_p = (vm_executable_object_t *) object_p; @@ -284,6 +323,8 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ } } + ecma_value_t result; + if (executable_object_p->extended_object.u.class_prop.extra_info & ECMA_EXECUTABLE_OBJECT_DO_AWAIT_OR_YIELD) { job_p->argument = ecma_await_continue (executable_object_p, job_p->argument); @@ -297,9 +338,8 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ { /* Continue iteration. */ JERRY_ASSERT (job_p->argument == ECMA_VALUE_UNDEFINED); - - ecma_free_promise_async_reaction_job (job_p); - return ECMA_VALUE_UNDEFINED; + result = ECMA_VALUE_UNDEFINED; + goto free_job; } if (ECMA_AWAIT_GET_STATE (executable_object_p) <= ECMA_AWAIT_YIELD_END) @@ -316,7 +356,7 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ executable_object_p->extended_object.u.class_prop.extra_info &= ECMA_AWAIT_CLEAR_MASK; } - ecma_value_t result = opfunc_resume_executable_object (executable_object_p, job_p->argument); + result = opfunc_resume_executable_object (executable_object_p, job_p->argument); /* Argument reference has been taken by opfunc_resume_executable_object. */ job_p->argument = ECMA_VALUE_UNDEFINED; @@ -327,6 +367,25 @@ ecma_process_promise_async_reaction_job (ecma_job_promise_async_reaction_t *job_ result = ECMA_VALUE_UNDEFINED; } +free_job: + +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + jerry_promise_event_type_t type = JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE; + + if (ecma_job_queue_get_type (&job_p->header) == ECMA_JOB_PROMISE_ASYNC_REACTION_REJECTED) + { + type = JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT; + } + + JERRY_CONTEXT (promise_callback) (type, + job_p->executable_object, + job_p->argument, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + ecma_free_promise_async_reaction_job (job_p); return result; } /* ecma_process_promise_async_reaction_job */ diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index 783e16e15..9cf4a076d 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -211,6 +211,16 @@ ecma_reject_promise (ecma_value_t promise, /**< promise */ JERRY_ASSERT (ecma_promise_get_flags (obj_p) & ECMA_PROMISE_IS_PENDING); +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_REJECT, + promise, + reason, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + ecma_promise_set_state (obj_p, false); ecma_promise_set_result (obj_p, ecma_copy_value_if_not_object (reason)); ecma_promise_object_t *promise_p = (ecma_promise_object_t *) obj_p; @@ -272,6 +282,16 @@ ecma_fulfill_promise (ecma_value_t promise, /**< promise */ ecma_free_value (then); } +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_RESOLVE, + promise, + value, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + ecma_promise_set_state (obj_p, true); ecma_promise_set_result (obj_p, ecma_copy_value_if_not_object (value)); ecma_promise_object_t *promise_p = (ecma_promise_object_t *) obj_p; @@ -301,9 +321,10 @@ ecma_promise_reject_handler (ecma_object_t *function_obj_p, /**< function object const uint32_t args_count) /**< argument number */ { ecma_promise_resolver_t *function_p = (ecma_promise_resolver_t *) function_obj_p; + ecma_value_t promise = function_p->promise; /* 1. */ - ecma_object_t *promise_obj_p = ecma_get_object_from_value (function_p->promise); + ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ @@ -314,7 +335,7 @@ ecma_promise_reject_handler (ecma_object_t *function_obj_p, /**< function object /* 6. */ ecma_value_t reject_value = (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]; - ecma_reject_promise (function_p->promise, reject_value); + ecma_reject_promise (promise, reject_value); } return ECMA_VALUE_UNDEFINED; @@ -333,9 +354,10 @@ ecma_promise_resolve_handler (ecma_object_t *function_obj_p, /**< function objec const uint32_t args_count) /**< argument number */ { ecma_promise_resolver_t *function_p = (ecma_promise_resolver_t *) function_obj_p; + ecma_value_t promise = function_p->promise; /* 1. */ - ecma_object_t *promise_obj_p = ecma_get_object_from_value (function_p->promise); + ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ @@ -344,14 +366,14 @@ ecma_promise_resolve_handler (ecma_object_t *function_obj_p, /**< function objec /* 5. */ ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; - ecma_fulfill_promise (function_p->promise, (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]); + ecma_fulfill_promise (promise, (args_count == 0) ? ECMA_VALUE_UNDEFINED : args_p[0]); } return ECMA_VALUE_UNDEFINED; } /* ecma_promise_resolve_handler */ /** - * Helper function for PromiseCreateResovingFucntions. + * Helper function for PromiseCreateResolvingFunctions. * * See also: ES2015 25.4.1.3 2. - 7. * @@ -370,7 +392,7 @@ ecma_promise_create_resolving_functions_helper (ecma_object_t *promise_p, /**< P } /* ecma_promise_create_resolving_functions_helper */ /** - * Create a PromiseCreateResovingFucntions. + * Perform PromiseCreateResolvingFunctions. * * See also: ES2015 25.4.1.3 * @@ -401,10 +423,12 @@ ecma_promise_create_resolving_functions (ecma_promise_object_t *promise_p) /**< * Returned value must be freed with ecma_free_value */ ecma_value_t -ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function or object */ - ecma_promise_executor_type_t type) /**< indicates the type of executor */ +ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function or ECMA_VALUE_EMPTY */ + ecma_value_t parent) /**< parent promise if available */ { + JERRY_UNUSED (parent); JERRY_ASSERT (JERRY_CONTEXT (current_new_target_p) != NULL); + /* 3. */ ecma_object_t *proto_p = ecma_op_get_prototype_from_constructor (JERRY_CONTEXT (current_new_target_p), ECMA_BUILTIN_ID_PROMISE_PROTOTYPE); @@ -437,10 +461,20 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function ecma_promise_create_resolving_functions (promise_object_p); +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_CREATE, + ecma_make_object_value (object_p), + parent, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + /* 9. */ ecma_value_t completion = ECMA_VALUE_UNDEFINED; - if (type == ECMA_PROMISE_EXECUTOR_FUNCTION) + if (executor != ECMA_VALUE_EMPTY) { JERRY_ASSERT (ecma_op_is_callable (executor)); @@ -450,11 +484,6 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function argv, 2); } - else - { - JERRY_ASSERT (type == ECMA_PROMISE_EXECUTOR_EMPTY); - JERRY_UNUSED (executor); - } ecma_value_t status = ECMA_VALUE_EMPTY; @@ -479,7 +508,6 @@ ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function /* 11. */ ecma_free_value (status); - return ecma_make_object_value (object_p); } /* ecma_op_create_promise_object */ @@ -614,7 +642,8 @@ ecma_op_get_capabilities_executor_cb (ecma_object_t *function_obj_p, /**< functi * new PromiseCapability object - otherwise */ ecma_object_t * -ecma_promise_new_capability (ecma_value_t constructor) +ecma_promise_new_capability (ecma_value_t constructor, /**< constructor function */ + ecma_value_t parent) /**< parent promise if available */ { /* 1. */ if (!ecma_is_constructor (constructor)) @@ -646,10 +675,22 @@ ecma_promise_new_capability (ecma_value_t constructor) /* 7. */ ecma_value_t executor = ecma_make_object_value (executor_p); - ecma_value_t promise = ecma_op_function_construct (constructor_obj_p, - constructor_obj_p, - &executor, - 1); + ecma_value_t promise; + + if (constructor_obj_p == ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE)) + { + ecma_object_t *old_new_target_p = JERRY_CONTEXT (current_new_target_p); + JERRY_CONTEXT (current_new_target_p) = constructor_obj_p; + + promise = ecma_op_create_promise_object (executor, parent); + + JERRY_CONTEXT (current_new_target_p) = old_new_target_p; + } + else + { + promise = ecma_op_function_construct (constructor_obj_p, constructor_obj_p, &executor, 1); + } + ecma_deref_object (executor_p); if (ECMA_IS_VALUE_ERROR (promise)) @@ -723,7 +764,7 @@ ecma_promise_reject_or_resolve (ecma_value_t this_arg, /**< "this" argument */ } } - ecma_object_t *capability_obj_p = ecma_promise_new_capability (this_arg); + ecma_object_t *capability_obj_p = ecma_promise_new_capability (this_arg, ECMA_VALUE_UNDEFINED); if (JERRY_UNLIKELY (capability_obj_p == NULL)) { @@ -868,7 +909,7 @@ ecma_promise_then (ecma_value_t promise, /**< the promise which call 'then' */ return species; } - ecma_object_t *result_capability_obj_p = ecma_promise_new_capability (species); + ecma_object_t *result_capability_obj_p = ecma_promise_new_capability (species, promise); ecma_free_value (species); if (JERRY_UNLIKELY (result_capability_obj_p == NULL)) @@ -1104,6 +1145,16 @@ void ecma_promise_async_then (ecma_value_t promise, /**< promise object */ ecma_value_t executable_object) /**< executable object of the async function */ { +#if JERRY_PROMISE_CALLBACK + if (JERRY_UNLIKELY (JERRY_CONTEXT (promise_callback) != NULL)) + { + JERRY_CONTEXT (promise_callback) (JERRY_PROMISE_EVENT_ASYNC_AWAIT, + executable_object, + promise, + JERRY_CONTEXT (promise_callback_user_p)); + } +#endif /* JERRY_PROMISE_CALLBACK */ + ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); uint16_t flags = ecma_promise_get_flags (promise_obj_p); diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index 13d9c16f3..58f0cb956 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -37,15 +37,6 @@ typedef enum ECMA_PROMISE_HANDLED = (1 << 3), /**< ES11: 25.6.6 [[PromiseIsHandled]] internal slot */ } ecma_promise_flags_t; -/** - * Indicates the type of the executor in promise construct. - */ -typedef enum -{ - ECMA_PROMISE_EXECUTOR_FUNCTION, /**< the executor is a function, it is for the usual constructor */ - ECMA_PROMISE_EXECUTOR_EMPTY /**< the executor is empty, it is for external C API */ -} ecma_promise_executor_type_t; - /** * Description of a promise resolving function. */ @@ -104,12 +95,12 @@ typedef struct */ bool ecma_is_promise (ecma_object_t *obj_p); -ecma_value_t ecma_op_create_promise_object (ecma_value_t executor, ecma_promise_executor_type_t type); +ecma_value_t ecma_op_create_promise_object (ecma_value_t executor, ecma_value_t parent); uint16_t ecma_promise_get_flags (ecma_object_t *promise_p); ecma_value_t ecma_promise_get_result (ecma_object_t *promise_p); void ecma_reject_promise (ecma_value_t promise, ecma_value_t reason); void ecma_fulfill_promise (ecma_value_t promise, ecma_value_t value); -ecma_object_t *ecma_promise_new_capability (ecma_value_t constructor); +ecma_object_t *ecma_promise_new_capability (ecma_value_t constructor, ecma_value_t parent); 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); diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index d115a9ab1..5b819a23b 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -107,6 +107,7 @@ typedef enum JERRY_FEATURE_BIGINT, /**< BigInt support */ JERRY_FEATURE_REALM, /**< realm support */ JERRY_FEATURE_GLOBAL_THIS, /**< GlobalThisValue support */ + JERRY_FEATURE_PROMISE_CALLBACK, /**< Promise callback support */ JERRY_FEATURE__COUNT /**< number of features. NOTE: must be at the end of the list */ } jerry_feature_t; @@ -726,9 +727,11 @@ bool jerry_foreach_object_property (const jerry_value_t obj_val, jerry_object_pr jerry_value_t jerry_object_get_property_names (const jerry_value_t obj_val, jerry_property_filter_t filter); jerry_value_t jerry_from_property_descriptor (const jerry_property_descriptor_t *src_prop_desc_p); jerry_value_t jerry_to_property_descriptor (jerry_value_t obj_value, jerry_property_descriptor_t *out_prop_desc_p); + /** * Promise functions. */ + jerry_value_t jerry_resolve_or_reject_promise (jerry_value_t promise, jerry_value_t argument, bool is_resolve); /** @@ -745,6 +748,53 @@ typedef enum jerry_value_t jerry_get_promise_result (const jerry_value_t promise); jerry_promise_state_t jerry_get_promise_state (const jerry_value_t promise); +/** + * Event types for jerry_promise_callback_t callback function. + * The description of the 'object' and 'value' arguments are provided for each type. + */ +typedef enum +{ + JERRY_PROMISE_EVENT_CREATE = 0u, /**< a new Promise object is created + * object: the new Promise object + * value: parent Promise for `then` chains, undefined otherwise */ + JERRY_PROMISE_EVENT_RESOLVE, /**< called when a Promise is about to be resolved + * object: the Promise object + * value: value for resolving */ + JERRY_PROMISE_EVENT_REJECT, /**< called when a Promise is about to be rejected + * object: the Promise object + * value: value for rejecting */ + JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB, /**< called before executing a Promise reaction job + * object: the Promise object + * value: undefined */ + JERRY_PROMISE_EVENT_AFTER_REACTION_JOB, /**< called after a Promise reaction job is completed + * object: the Promise object + * value: undefined */ + JERRY_PROMISE_EVENT_ASYNC_AWAIT, /**< called when an async function awaits the result of a Promise object + * object: internal object representing the execution status + * value: the Promise object */ + JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE, /**< called when an async function is continued with resolve + * object: internal object representing the execution status + * value: value for resolving */ + JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT, /**< called when an async function is continued with reject + * object: internal object representing the execution status + * value: value for rejecting */ + JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE, /**< called when an async function resolve is completed + * object: internal object representing the execution status + * value: value for resolving */ + JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT, /**< called when an async function reject is completed + * object: internal object representing the execution status + * value: value for rejecting */ +} jerry_promise_event_type_t; + +/** + * Notification callback for tracking Promise and async function operations. + */ +typedef void (*jerry_promise_callback_t) (jerry_promise_event_type_t event_type, + const jerry_value_t object, const jerry_value_t value, + void *user_p); + +void jerry_promise_set_callback (jerry_promise_callback_t callback, void *user_p); + /** * Symbol functions. */ diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index e973d6a59..9d2b03d37 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -183,6 +183,10 @@ struct jerry_context_t #if JERRY_BUILTIN_PROMISE ecma_job_queue_item_t *job_queue_head_p; /**< points to the head item of the job queue */ ecma_job_queue_item_t *job_queue_tail_p; /**< points to the tail item of the job queue */ +#if JERRY_PROMISE_CALLBACK + void *promise_callback_user_p; /**< user pointer for promise callback */ + jerry_promise_callback_t promise_callback; /**< user function for tracking Promise object operations */ +#endif /* JERRY_PROMISE_CALLBACK */ #endif /* JERRY_BUILTIN_PROMISE */ #if JERRY_VM_EXEC_STOP diff --git a/jerry-core/vm/opcodes.c b/jerry-core/vm/opcodes.c index 58827040e..66a422f7f 100644 --- a/jerry-core/vm/opcodes.c +++ b/jerry-core/vm/opcodes.c @@ -901,7 +901,7 @@ opfunc_async_create_and_await (vm_frame_ctx_t *frame_ctx_p, /**< frame context * ecma_object_t *old_new_target_p = JERRY_CONTEXT (current_new_target_p); JERRY_CONTEXT (current_new_target_p) = promise_p; - result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_PROMISE_EXECUTOR_EMPTY); + result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_VALUE_UNDEFINED); JERRY_ASSERT (ecma_is_value_object (result)); executable_object_p->frame_ctx.block_result = result; diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 7010e5d97..639cb7ee3 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -2719,7 +2719,7 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ ecma_object_t *old_new_target_p = JERRY_CONTEXT (current_new_target_p); JERRY_CONTEXT (current_new_target_p) = ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE); - result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_PROMISE_EXECUTOR_EMPTY); + result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_VALUE_UNDEFINED); JERRY_CONTEXT (current_new_target_p) = old_new_target_p; } diff --git a/tests/unit-core/CMakeLists.txt b/tests/unit-core/CMakeLists.txt index bfe3ed936..be4bba1cc 100644 --- a/tests/unit-core/CMakeLists.txt +++ b/tests/unit-core/CMakeLists.txt @@ -66,6 +66,7 @@ set(SOURCE_UNIT_TEST_MAIN_MODULES test-number-to-string.c test-objects-foreach.c test-poolman.c + test-promise-callback.c test-promise.c test-proxy.c test-realm.c diff --git a/tests/unit-core/test-promise-callback.c b/tests/unit-core/test-promise-callback.c new file mode 100644 index 000000000..64e5bdef6 --- /dev/null +++ b/tests/unit-core/test-promise-callback.c @@ -0,0 +1,286 @@ +/* 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 "test-common.h" + +/* Note: RS = ReSolve, RJ = ReJect */ +typedef enum +{ + C = JERRY_PROMISE_EVENT_CREATE, /**< same as JERRY_PROMISE_CALLBACK_CREATE with undefined value */ + RS = JERRY_PROMISE_EVENT_RESOLVE, /**< same as JERRY_PROMISE_CALLBACK_RESOLVE */ + RJ = JERRY_PROMISE_EVENT_REJECT, /**< same as JERRY_PROMISE_CALLBACK_REJECT */ + BR = JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB, /**< same as JERRY_PROMISE_CALLBACK_BEFORE_REACTION_JOB */ + AR = JERRY_PROMISE_EVENT_AFTER_REACTION_JOB, /**< same as JERRY_PROMISE_CALLBACK_AFTER_REACTION_JOB */ + A = JERRY_PROMISE_EVENT_ASYNC_AWAIT, /**< same as JERRY_PROMISE_CALLBACK_ASYNC_AWAIT */ + BRS = JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE, /**< same as JERRY_PROMISE_CALLBACK_ASYNC_BEFORE_RESOLVE */ + BRJ = JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT, /**< same as JERRY_PROMISE_CALLBACK_ASYNC_BEFORE_REJECT */ + ARS = JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE, /**< same as JERRY_PROMISE_CALLBACK_ASYNC_AFTER_RESOLVE */ + ARJ = JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT, /**< same as JERRY_PROMISE_CALLBACK_ASYNC_AFTER_REJECT */ + CP = UINT8_MAX - 1, /**< same as JERRY_PROMISE_CALLBACK_CREATE with Promise value */ + E = UINT8_MAX, /**< marks the end of the event list */ +} jerry_promise_callback_event_abbreviations_t; + +static int user; +static const uint8_t *next_event_p; + +static void +promise_callback (jerry_promise_event_type_t event_type, /**< event type */ + const jerry_value_t object, /**< target object */ + const jerry_value_t value, /**< optional argument */ + void *user_p) /**< user pointer passed to the callback */ +{ + TEST_ASSERT (user_p == (void *) &user); + + switch (event_type) + { + case JERRY_PROMISE_EVENT_CREATE: + { + TEST_ASSERT (jerry_value_is_promise (object)); + if (jerry_value_is_undefined (value)) + { + break; + } + + TEST_ASSERT (jerry_value_is_promise (value)); + TEST_ASSERT (*next_event_p++ == CP); + return; + } + case JERRY_PROMISE_EVENT_RESOLVE: + case JERRY_PROMISE_EVENT_REJECT: + { + TEST_ASSERT (jerry_value_is_promise (object)); + break; + } + case JERRY_PROMISE_EVENT_BEFORE_REACTION_JOB: + case JERRY_PROMISE_EVENT_AFTER_REACTION_JOB: + { + TEST_ASSERT (jerry_value_is_promise (object)); + TEST_ASSERT (jerry_value_is_undefined (value)); + break; + } + case JERRY_PROMISE_EVENT_ASYNC_AWAIT: + { + TEST_ASSERT (jerry_value_is_object (object)); + TEST_ASSERT (jerry_value_is_promise (value)); + break; + } + default: + { + TEST_ASSERT (event_type == JERRY_PROMISE_EVENT_ASYNC_BEFORE_RESOLVE + || event_type == JERRY_PROMISE_EVENT_ASYNC_BEFORE_REJECT + || event_type == JERRY_PROMISE_EVENT_ASYNC_AFTER_RESOLVE + || event_type == JERRY_PROMISE_EVENT_ASYNC_AFTER_REJECT); + TEST_ASSERT (jerry_value_is_object (object)); + break; + } + } + + TEST_ASSERT (*next_event_p++ == (uint8_t) event_type); +} /* promise_callback */ + +static void +run_eval (const uint8_t *event_list_p, /**< event list */ + const char *source_p) /**< source code */ +{ + next_event_p = event_list_p; + + jerry_value_t result = jerry_eval ((const jerry_char_t *) source_p, strlen (source_p), 0); + + TEST_ASSERT (!jerry_value_is_error (result)); + jerry_release_value (result); + + result = jerry_run_all_enqueued_jobs (); + TEST_ASSERT (!jerry_value_is_error (result)); + jerry_release_value (result); + + TEST_ASSERT (*next_event_p == UINT8_MAX); +} /* run_eval */ + +int +main (void) +{ + TEST_INIT (); + + if (!jerry_is_feature_enabled (JERRY_FEATURE_PROMISE)) + { + jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Promise is disabled!\n"); + return 0; + } + + /* The test system enables this feature when Promises are enabled. */ + TEST_ASSERT (jerry_is_feature_enabled (JERRY_FEATURE_PROMISE_CALLBACK)); + + jerry_init (JERRY_INIT_EMPTY); + + jerry_promise_set_callback (promise_callback, (void *) &user); + + /* Test promise creation. */ + static uint8_t events1[] = { C, C, C, E }; + + run_eval (events1, + "'use strict'\n" + "new Promise((res, rej) => {})\n" + "new Promise((res, rej) => {})\n" + "new Promise((res, rej) => {})\n"); + + /* Test then call. */ + static uint8_t events2[] = { C, CP, E }; + + run_eval (events2, + "'use strict'\n" + "var promise = new Promise((res, rej) => {})\n" + "promise.then(() => {}, () => {})\n"); + + /* Test then call with extended Promise. */ + static uint8_t events3[] = { C, C, E }; + + run_eval (events3, + "'use strict'\n" + "var P = class extends Promise {}\n" + "var promise = new P((res, rej) => {})\n" + "promise.then(() => {})\n"); + + /* Test resolve and reject calls. */ + static uint8_t events4[] = { C, C, RS, RJ, E }; + + run_eval (events4, + "'use strict'\n" + "var resolve\n" + "var reject\n" + "new Promise((res, rej) => resolve = res)\n" + "new Promise((res, rej) => reject = rej)\n" + "resolve(1)\n" + "reject(1)\n"); + + /* Test then and resolve calls. */ + static uint8_t events5[] = { C, CP, RS, BR, RS, AR, E }; + + run_eval (events5, + "'use strict'\n" + "var resolve\n" + "var promise = new Promise((res, rej) => resolve = res)\n" + "promise.then(() => {})\n" + "resolve(1)\n"); + + /* Test resolve and then calls. */ + static uint8_t events6[] = { C, RS, CP, BR, RS, AR, E }; + + run_eval (events6, + "'use strict'\n" + "var promise = new Promise((res, rej) => res(1))\n" + "promise.then(() => {})\n"); + + /* Test Promise.resolve. */ + static uint8_t events7[] = { C, RS, CP, BR, RS, AR, E }; + + run_eval (events7, + "Promise.resolve(4).then(() => {})\n"); + + /* Test Promise.reject. */ + static uint8_t events8[] = { C, RJ, CP, BR, RJ, AR, E }; + + run_eval (events8, + "Promise.reject(4).then(() => {})\n"); + + /* Test Promise.race without resolve */ + static uint8_t events9[] = { C, C, C, CP, CP, E }; + + run_eval (events9, + "'use strict'\n" + "var p1 = new Promise((res, rej) => {})\n" + "var p2 = new Promise((res, rej) => {})\n" + "Promise.race([p1,p2])\n"); + + /* Test Promise.race with resolve. */ + static uint8_t events10[] = { C, RS, C, RJ, C, CP, CP, BR, RS, RS, AR, BR, RS, AR, E }; + + run_eval (events10, + "'use strict'\n" + "var p1 = new Promise((res, rej) => res(1))\n" + "var p2 = new Promise((res, rej) => rej(1))\n" + "Promise.race([p1,p2])\n"); + + /* Test Promise.all without resolve. */ + static uint8_t events11[] = { C, C, C, CP, CP, E }; + + run_eval (events11, + "'use strict'\n" + "var p1 = new Promise((res, rej) => {})\n" + "var p2 = new Promise((res, rej) => {})\n" + "Promise.all([p1,p2])\n"); + + /* Test Promise.all with resolve. */ + static uint8_t events12[] = { C, RS, C, RJ, C, CP, CP, BR, RS, AR, BR, RJ, RS, AR, E }; + + run_eval (events12, + "'use strict'\n" + "var p1 = new Promise((res, rej) => res(1))\n" + "var p2 = new Promise((res, rej) => rej(1))\n" + "Promise.all([p1,p2])\n"); + + /* Test async function. */ + static uint8_t events13[] = { C, RS, E }; + + run_eval (events13, + "'use strict'\n" + "async function f() {}\n" + "f()\n"); + + /* Test await with resolved Promise. */ + static uint8_t events14[] = { C, RS, A, C, BRS, RS, ARS, E }; + + run_eval (events14, + "'use strict'\n" + "async function f(p) { await p }\n" + "f(Promise.resolve(1))\n"); + + /* Test await with non-Promise value. */ + static uint8_t events15[] = { C, RS, A, C, BRS, C, RS, A, ARS, BRS, RS, ARS, E }; + + run_eval (events15, + "'use strict'\n" + "async function f(p) { await p; await 'X' }\n" + "f(Promise.resolve(1))\n"); + + /* Test await with rejected Promise. */ + static uint8_t events16[] = { C, RJ, A, C, BRJ, C, RS, RS, ARJ, E }; + + run_eval (events16, + "'use strict'\n" + "async function f(p) { try { await p; } catch (e) { Promise.resolve(1) } }\n" + "f(Promise.reject(1))\n"); + + /* Test async generator function. */ + static uint8_t events17[] = { C, RS, C, A, BRS, RS, ARS, E }; + + run_eval (events17, + "'use strict'\n" + "async function *f(p) { await p; return 4 }\n" + "f(Promise.resolve(1)).next()\n"); + + /* Test yield* operation. */ + static uint8_t events18[] = { C, C, RS, A, BRS, C, RS, A, ARS, BRS, RS, ARS, E }; + + run_eval (events18, + "'use strict'\n" + "async function *f(p) { yield 1 }\n" + "async function *g() { yield* f() }\n" + "g().next()\n"); + + jerry_cleanup (); + return 0; +} /* main */ diff --git a/tools/build.py b/tools/build.py index a50dbad0d..fe772d412 100755 --- a/tools/build.py +++ b/tools/build.py @@ -138,6 +138,8 @@ def get_arguments(): help=devhelp('enable mem-stress test (%(choices)s)')) coregrp.add_argument('--profile', metavar='FILE', help='specify profile file') + coregrp.add_argument('--promise-callback', metavar='X', choices=['ON', 'OFF'], type=str.upper, + help='enable promise callback (%(choices)s)') coregrp.add_argument('--regexp-strict-mode', metavar='X', choices=['ON', 'OFF'], type=str.upper, help=devhelp('enable regexp strict mode (%(choices)s)')) coregrp.add_argument('--show-opcodes', metavar='X', choices=['ON', 'OFF'], type=str.upper, @@ -212,6 +214,7 @@ def generate_build_options(arguments): build_options_append('JERRY_MEM_STATS', arguments.mem_stats) build_options_append('JERRY_MEM_GC_BEFORE_EACH_ALLOC', arguments.mem_stress_test) build_options_append('JERRY_PROFILE', arguments.profile) + build_options_append('JERRY_PROMISE_CALLBACK', arguments.promise_callback) build_options_append('JERRY_REGEXP_STRICT_MODE', arguments.regexp_strict_mode) build_options_append('JERRY_PARSER_DUMP_BYTE_CODE', arguments.show_opcodes) build_options_append('JERRY_REGEXP_DUMP_BYTE_CODE', arguments.show_regexp_opcodes) diff --git a/tools/run-tests.py b/tools/run-tests.py index c84dcdcbe..f037fd73a 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -57,9 +57,11 @@ OPTIONS_DOCTESTS = ['--doctests=on', '--jerry-cmdline=off', '--error-messages=on # Test options for unittests JERRY_UNITTESTS_OPTIONS = [ Options('unittests-es.next', - OPTIONS_COMMON + OPTIONS_UNITTESTS + OPTIONS_PROFILE_ESNEXT), + OPTIONS_COMMON + OPTIONS_UNITTESTS + OPTIONS_PROFILE_ESNEXT + + ['--promise-callback=on']), Options('doctests-es.next', - OPTIONS_COMMON + OPTIONS_DOCTESTS + OPTIONS_PROFILE_ESNEXT), + OPTIONS_COMMON + OPTIONS_DOCTESTS + OPTIONS_PROFILE_ESNEXT + + ['--promise-callback=on']), Options('unittests-es5.1', OPTIONS_COMMON + OPTIONS_UNITTESTS + OPTIONS_PROFILE_ES51), Options('doctests-es5.1',