Skip to content

Add promise rejection tracker support #4012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions docs/02.API-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,30 @@ typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p);

- [jerry_set_vm_exec_stop_callback](#jerry_set_vm_exec_stop_callback)

## jerry_promise_rejection_tracker_t

**Summary**

The typical implementation of the function might try to notify developers of unhandled rejections.
The callback is called in two scenarios:
- When a promise is rejected without any handlers
- When a handler is added to a rejected promise for the first time
In these cases the callback is invoked with the following arguments:
- `promise`: rejected promise
- `reason`: reason of the rejection

**Prototype**

```c
typedef void (*jerry_promise_rejection_tracker_t) (jerry_value_t promise, jerry_value_t reason);
```

*New in version [[NEXT_RELEASE]]*.

**See also**

- [jerry_set_promise_rejection_callback](#jerry_set_promise_rejection_callback)

## jerry_promise_state_t

Enum which describes the state of a Promise.
Expand Down Expand Up @@ -7692,6 +7716,67 @@ main (void)
- [jerry_run](#jerry_run)
- [jerry_vm_exec_stop_callback_t](#jerry_vm_exec_stop_callback_t)

## jerry_set_promise_rejection_callback

**Summary**

Register a promise rejection tracker callback for the engine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should provide more details here imho and also mention the HostPromiseRejectionTracker from the standard here.


**Prototype**

```c
void
jerry_set_promise_rejection_callback (jerry_promise_rejection_tracker_t tracker_cb);
```

- `tracker_cb` - callback called whenever an unhandled promsie rejection happens (passing NULL disables this feature)

*New in version [[NEXT_RELEASE]]*.

**Example**

[doctest]: # (test="link")

```c
#include <stdio.h>
#include "jerryscript.h"

static void
promise_rejection_trackter (jerry_value_t promise,
jerry_value_t reason)
{
// Notify the host environment about uncaught promise rejection
jerry_value_t reason_to_string = jerry_value_to_string (reason);
jerry_size_t req_sz = jerry_get_utf8_string_size (reason_to_string);
jerry_char_t str_buf_p[req_sz + 1];

jerry_size_t bytes_copied = jerry_string_to_utf8_char_buffer (reason_to_string, str_buf_p, req_sz);
jerry_release_value (reason_to_string);
str_buf_p[req_sz] = '\0';

printf ("Uncaught (in promise) %s\n", str_buf_p);
}

int
main (void)
{
jerry_init (JERRY_INIT_EMPTY);
jerry_set_promise_rejection_callback (promise_rejection_trackter);

jerry_char_t src[] = "Promise.reject(42)";
jerry_value_t promise = jerry_eval (src, sizeof (src) - 1, JERRY_PARSE_NO_OPTS);
jerry_release_value (promise);
jerry_cleanup ();
}
```

**See also**

- [jerry_init](#jerry_init)
- [jerry_cleanup](#jerry_cleanup)
- [jerry_eval](#jerry_eval)
- [jerry_promise_rejection_tracker_t](#jerry_promise_rejection_tracker_t)

## jerry_get_backtrace

**Summary**
Expand Down
15 changes: 15 additions & 0 deletions jerry-core/api/jerry.c
Original file line number Diff line number Diff line change
Expand Up @@ -3504,6 +3504,21 @@ jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< per
#endif /* ENABLED (JERRY_VM_EXEC_STOP) */
} /* jerry_set_vm_exec_stop_callback */

/**
* Register a callback to track unhandled promise rejections.
*
* Note: passing NULL as argument can disable a previously set tracker callback
*/
void
jerry_set_promise_rejection_callback (jerry_promise_rejection_tracker_t tracker_cb) /**< tracker callback */
{
#if ENABLED (JERRY_ESNEXT)
JERRY_CONTEXT (promise_rejection_cb) = tracker_cb;
#else /* !ENABLED (JERRY_ESNEXT) */
JERRY_UNUSED (tracker_cb);
#endif /* ENABLED (JERRY_ESNEXT) */
} /* jerry_set_promise_rejection_callback */

/**
* Get backtrace. The backtrace is an array of strings where
* each string contains the position of the corresponding frame.
Expand Down
6 changes: 6 additions & 0 deletions jerry-core/ecma/base/ecma-globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ enum
*/
typedef ecma_value_t (*ecma_vm_exec_stop_callback_t) (void *user_p);

/**
* User defined callback to track unhandled promise rejections
*/
typedef void (*ecma_promise_rejection_tracker_t) (const jerry_value_t promise,
const ecma_value_t reason);

/**
* Type of an external function handler.
*/
Expand Down
23 changes: 23 additions & 0 deletions jerry-core/ecma/operations/ecma-promise-object.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ ecma_is_resolver_already_called (ecma_object_t *resolver_p, /**< resolver */
return current_value == ECMA_VALUE_TRUE;
} /* ecma_is_resolver_already_called */

/**
* HostPromiseRejectionTracker
*
* See also: ES11 25.6.1.9
*/
static void
ecma_promise_track_rejection (ecma_object_t *obj_p, /**< promise */
ecma_value_t reason) /**< reason for reject */
{
JERRY_ASSERT (ecma_is_promise (obj_p));

if (JERRY_CONTEXT (promise_rejection_cb) != NULL
&& !(ecma_promise_get_flags (obj_p) & ECMA_PROMISE_HANDLED))
{
JERRY_CONTEXT (promise_rejection_cb) (ecma_make_object_value (obj_p), reason);
}
} /* ecma_promise_track_rejection */

/**
* Reject a Promise with a reason.
*
Expand Down Expand Up @@ -228,6 +246,7 @@ ecma_reject_promise (ecma_value_t promise, /**< promise */
promise_p->reactions = ecma_new_collection ();

ecma_collection_destroy (reactions);
ecma_promise_track_rejection (obj_p, reason);
} /* ecma_reject_promise */

/**
Expand Down Expand Up @@ -892,10 +911,14 @@ ecma_promise_do_then (ecma_value_t promise, /**< the promise which call 'then' *
{
/* 9. */
ecma_value_t reason = ecma_promise_get_result (promise_obj_p);
ecma_promise_track_rejection (promise_obj_p, reason);
ecma_enqueue_promise_reaction_job (ecma_make_object_value (result_capability_obj_p), on_rejected, reason);
ecma_free_value (reason);
}

/* ES11: 11. */
promise_p->header.u.class_prop.extra_info |= ECMA_PROMISE_HANDLED;

/* 10. */
return ecma_copy_value (capability_p->header.u.class_prop.u.promise);
} /* ecma_promise_do_then */
Expand Down
1 change: 1 addition & 0 deletions jerry-core/ecma/operations/ecma-promise-object.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ typedef enum
ECMA_PROMISE_IS_PENDING = (1 << 0), /**< pending state */
ECMA_PROMISE_IS_FULFILLED = (1 << 1), /**< fulfilled state */
ECMA_PROMISE_ALREADY_RESOLVED = (1 << 2), /**< already resolved */
ECMA_PROMISE_HANDLED = (1 << 3), /**< ES11: 25.6.6 [[PromiseIsHandled]] internal slot*/
} ecma_promise_flags_t;

/**
Expand Down
7 changes: 7 additions & 0 deletions jerry-core/include/jerryscript-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ typedef void (*jerry_object_native_free_callback_t) (void *native_p);
*/
typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p);

/**
* User defined callback to track unhandled promise rejections
*/
typedef void (*jerry_promise_rejection_tracker_t) (const jerry_value_t promise,
const jerry_value_t reason);

/**
* Function type applied for each data property of an object.
*/
Expand Down Expand Up @@ -611,6 +617,7 @@ jerry_context_t *jerry_create_context (uint32_t heap_size, jerry_context_alloc_t
* Miscellaneous functions.
*/
void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, void *user_p, uint32_t frequency);
void jerry_set_promise_rejection_callback (jerry_promise_rejection_tracker_t tracker_cb);
jerry_value_t jerry_get_backtrace (uint32_t max_depth);
jerry_value_t jerry_get_resource_name (const jerry_value_t value);
jerry_value_t jerry_get_new_target (void);
Expand Down
2 changes: 2 additions & 0 deletions jerry-core/jcontext/jcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ struct jerry_context_t
ecma_object_t *current_new_target;
ecma_object_t *current_function_obj_p; /** currently invoked function object
(Note: currently used only in generator functions) */
ecma_promise_rejection_tracker_t promise_rejection_cb; /**< user defined function to track
* unhandled promise rejections. */
#endif /* ENABLED (JERRY_ESNEXT) */
};

Expand Down
3 changes: 2 additions & 1 deletion jerry-ext/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ file(GLOB SOURCE_EXT
debugger/*.c
handle-scope/*.c
handler/*.c
module/*.c)
module/*.c
promise/*.c)

add_library(${JERRY_EXT_NAME} ${SOURCE_EXT})

Expand Down
25 changes: 18 additions & 7 deletions jerry-ext/handler/handler-print.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@
/**
* Provide a 'print' implementation for scripts.
*
* The routine converts all of its arguments to strings and outputs them
* char-by-char using jerry_port_print_char.
*
* The NUL character is output as "\u0000", other characters are output
* bytewise.
*
* Note:
* This implementation does not use standard C `printf` to print its
* output. This allows more flexibility but also extends the core
Expand All @@ -45,6 +39,23 @@ jerryx_handler_print (const jerry_value_t func_obj_val, /**< function object */
(void) func_obj_val; /* unused */
(void) this_p; /* unused */

return jerryx_handler_print_helper (args_p, args_cnt);
} /* jerryx_handler_print */

/**
* The routine converts all of its arguments to strings and outputs them
* char-by-char using jerry_port_print_char.
*
* The NULL character is output as "\u0000", other characters are output
* bytewise.
*
* @return undefined - if all arguments could be converted to strings,
* error - otherwise.
*/
jerry_value_t
jerryx_handler_print_helper (const jerry_value_t args_p[], /**< argument list */
const jerry_length_t args_cnt) /**< number of arguments */
{
const char * const null_str = "\\u0000";

jerry_value_t ret_val = jerry_create_undefined ();
Expand Down Expand Up @@ -125,4 +136,4 @@ jerryx_handler_print (const jerry_value_t func_obj_val, /**< function object */
}

return ret_val;
} /* jerryx_handler_print */
} /* jerryx_handler_print_helper */
5 changes: 5 additions & 0 deletions jerry-ext/include/jerryscript-ext/handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ extern "C"
jerry_value_t jerryx_handler_register_global (const jerry_char_t *name_p,
jerry_external_handler_t handler_p);

/*
* Print handler helper
*/
jerry_value_t jerryx_handler_print_helper (const jerry_value_t args_p[], const jerry_length_t args_cnt);

/*
* Common external function handlers
*/
Expand Down
36 changes: 36 additions & 0 deletions jerry-ext/include/jerryscript-ext/promise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* 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.
*/

#ifndef JERRYX_PROMISE_H
#define JERRYX_PROMISE_H

#include "jerryscript.h"

#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

/*
* Promise rejection tracker
*/

void jerryx_promise_rejection_tracker (jerry_value_t promise,
jerry_value_t reason);

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* !JERRYX_PROMISE_H */
44 changes: 44 additions & 0 deletions jerry-ext/promise/promise.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* 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-ext/handler.h"
#include "jerryscript-ext/promise.h"
#include "jext-common.h"
#include "jerryscript-port.h"

/**
* Provide a 'HostPromiseRejectionTracker' implementation for scripts.
*
* See also: ECMAScript 11, 25.6.1.9
*/
void
jerryx_promise_rejection_tracker (jerry_value_t promise, /**< promise object */
jerry_value_t reason) /**< reason of rejection */
{
(void) promise; /* unused */

jerry_value_t unchaught_str = jerry_create_string ((const jerry_char_t *) "Uncaught (in promise):");
jerry_value_t args[] = { unchaught_str, reason };

jerry_value_t result = jerryx_handler_print_helper (args, sizeof (args) / sizeof (args[0]));

if (jerry_value_is_error (result))
{
result = jerry_get_value_from_error (result, true);
}

jerry_release_value (result);
jerry_release_value (unchaught_str);
} /* jerryx_promise_rejection_tracker */
3 changes: 3 additions & 0 deletions jerry-main/main-unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "jerryscript.h"
#include "jerryscript-ext/debugger.h"
#include "jerryscript-ext/handler.h"
#include "jerryscript-ext/promise.h"
#include "jerryscript-port.h"
#include "jerryscript-port-default.h"

Expand Down Expand Up @@ -467,6 +468,8 @@ init_engine (jerry_init_flag_t flags, /**< initialized flags for the engine */
register_js_function ("gc", jerryx_handler_gc);
register_js_function ("print", jerryx_handler_print);
register_js_function ("resourceName", jerryx_handler_resource_name);

jerry_set_promise_rejection_callback (jerryx_promise_rejection_tracker);
} /* init_engine */

int
Expand Down
Loading