njs_vm_opt_init(&options);
options.backtrace = 1;
+ options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
options.ops = &ngx_http_js_ops;
options.argv = ngx_argv;
options.argc = ngx_argc;
njs_vm_opt_init(&options);
options.backtrace = 1;
+ options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
options.ops = &ngx_stream_js_ops;
options.argv = ngx_argv;
options.argc = ngx_argc;
char **argv;
njs_uint_t argc;
+#define NJS_VM_OPT_UNHANDLED_REJECTION_IGNORE 0
+#define NJS_VM_OPT_UNHANDLED_REJECTION_THROW 1
+
/*
- * accumulative - enables "accumulative" mode to support incremental compiling.
+ * accumulative - enables "accumulative" mode to support incremental compiling.
* (REPL). Allows starting parent VM without cloning.
- * disassemble - enables disassemble.
- * backtrace - enables backtraces.
- * quiet - removes filenames from backtraces. To produce comparable
+ * disassemble - enables disassemble.
+ * backtrace - enables backtraces.
+ * quiet - removes filenames from backtraces. To produce comparable
test262 diffs.
- * sandbox - "sandbox" mode. Disables file access.
- * unsafe - enables unsafe language features:
+ * sandbox - "sandbox" mode. Disables file access.
+ * unsafe - enables unsafe language features:
* - Function constructors.
- * module - ES6 "module" mode. Script mode is default.
- * ast - print AST.
+ * module - ES6 "module" mode. Script mode is default.
+ * ast - print AST.
+ * unhandled_rejection IGNORE | THROW - tracks unhandled promise rejections:
+ * - throwing inside a Promise without a catch block.
+ * - throwing inside in a finally or catch block.
*/
uint8_t trailer; /* 1 bit */
uint8_t unsafe; /* 1 bit */
uint8_t module; /* 1 bit */
uint8_t ast; /* 1 bit */
+ uint8_t unhandled_rejection;
} njs_vm_opt_t;
NJS_PROMISE_REJECTED
} njs_promise_type_t;
+typedef enum {
+ NJS_PROMISE_HANDLE = 0,
+ NJS_PROMISE_REJECT
+} njs_promise_rejection_type_t;
+
typedef struct {
njs_promise_type_t state;
njs_value_t result;
njs_value_t *dst);
static njs_int_t njs_promise_capability_executor(njs_vm_t *vm,
njs_value_t *args, njs_uint_t nargs, njs_index_t retval);
+static njs_int_t njs_promise_host_rejection_tracker(njs_vm_t *vm,
+ njs_promise_t *promise, njs_promise_rejection_type_t operation);
static njs_int_t njs_promise_resolve_function(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t retval);
static njs_promise_t *njs_promise_resolve(njs_vm_t *vm,
njs_inline njs_value_t *
njs_promise_reject(njs_vm_t *vm, njs_promise_t *promise, njs_value_t *reason)
{
+ njs_int_t ret;
njs_queue_t queue;
njs_promise_data_t *data;
data->result = *reason;
data->state = NJS_PROMISE_REJECTED;
+ if (!data->is_handled) {
+ ret = njs_promise_host_rejection_tracker(vm, promise,
+ NJS_PROMISE_REJECT);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return njs_value_arg(&njs_value_null);
+ }
+ }
+
if (njs_queue_is_empty(&data->reject_queue)) {
return njs_value_arg(&njs_value_undefined);
}
+static njs_int_t
+njs_promise_host_rejection_tracker(njs_vm_t *vm, njs_promise_t *promise,
+ njs_promise_rejection_type_t operation)
+{
+ uint32_t i, length;
+ njs_value_t *value;
+ njs_promise_data_t *data;
+
+ if (vm->options.unhandled_rejection
+ == NJS_VM_OPT_UNHANDLED_REJECTION_IGNORE)
+ {
+ return NJS_OK;
+ }
+
+ if (vm->promise_reason == NULL) {
+ vm->promise_reason = njs_array_alloc(vm, 1, 0, NJS_ARRAY_SPARE);
+ if (njs_slow_path(vm->promise_reason == NULL)) {
+ return NJS_ERROR;
+ }
+ }
+
+ data = njs_data(&promise->value);
+
+ if (operation == NJS_PROMISE_REJECT) {
+ if (vm->promise_reason != NULL) {
+ return njs_array_add(vm, vm->promise_reason, &data->result);
+ }
+
+ } else {
+ value = vm->promise_reason->start;
+ length = vm->promise_reason->length;
+
+ for (i = 0; i < length; i++) {
+ if (memcmp(&value[i], &data->result, sizeof(njs_value_t)) == 0) {
+ length--;
+
+ if (i < length) {
+ memmove(&value[i], &value[i + 1],
+ sizeof(njs_value_t) * (length - i));
+ }
+
+ break;
+ }
+ }
+
+ vm->promise_reason->length = length;
+ }
+
+ return NJS_OK;
+}
+
+
static njs_int_t
njs_promise_invoke_then(njs_vm_t *vm, njs_value_t *promise, njs_value_t *args,
njs_int_t nargs)
if (data->state == NJS_PROMISE_REJECTED) {
njs_set_data(&arguments[0], rejected_reaction, 0);
- /* TODO: HostPromiseRejectionTracker */
+ ret = njs_promise_host_rejection_tracker(vm, promise,
+ NJS_PROMISE_HANDLE);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
} else {
njs_set_data(&arguments[0], fulfilled_reaction, 0);
uint8_t safe;
uint8_t version;
uint8_t ast;
+ uint8_t unhandled_rejection;
char *file;
char *command;
vm_options.argv = opts.argv;
vm_options.argc = opts.argc;
vm_options.ast = opts.ast;
+ vm_options.unhandled_rejection = opts.unhandled_rejection;
if (opts.interactive) {
ret = njs_interactive_shell(&opts, &vm_options);
" -f disabled denormals mode.\n"
" -p set path prefix for modules.\n"
" -q disable interactive introduction prompt.\n"
+ " -r ignore unhandled promise rejection.\n"
" -s sandbox mode.\n"
" -t script|module source code type (script is default).\n"
" -v print njs version and exit.\n"
ret = NJS_DONE;
opts->denormals = 1;
+ opts->unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
for (i = 1; i < argc; i++) {
opts->quiet = 1;
break;
+ case 'r':
+ opts->unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_IGNORE;
+ break;
+
case 's':
opts->sandbox = 1;
break;
njs_vm_handle_events(njs_vm_t *vm)
{
njs_int_t ret;
+ njs_str_t str;
+ njs_value_t string;
njs_event_t *ev;
njs_queue_t *promise_events, *posted_events;
njs_queue_link_t *link;
}
}
+ if (vm->options.unhandled_rejection
+ == NJS_VM_OPT_UNHANDLED_REJECTION_THROW)
+ {
+ if (vm->promise_reason != NULL && vm->promise_reason->length != 0) {
+ ret = njs_value_to_string(vm, &string,
+ &vm->promise_reason->start[0]);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ njs_string_get(&string, &str);
+ njs_vm_error(vm, "unhandled promise rejection: %V", &str);
+
+ njs_mp_free(vm->mem_pool, vm->promise_reason);
+ vm->promise_reason = NULL;
+
+ return NJS_ERROR;
+ }
+ }
+
for ( ;; ) {
link = njs_queue_first(posted_events);
njs_regex_context_t *regex_context;
njs_regex_match_data_t *single_match_data;
+ njs_array_t *promise_reason;
+
/*
* MemoryError is statically allocated immutable Error object
* with the InternalError prototype.
--- /dev/null
+Promise.resolve()
+.then(() => {})
+.catch(() => {})
+.then(() => {nonExsisting()})
+.catch(() => {console.log("Done")});
\ No newline at end of file
--- /dev/null
+Promise.resolve()
+.then(() => {nonExsisting()})
+.catch(() => {nonExsistingInCatch()});
\ No newline at end of file
--- /dev/null
+Promise.resolve()
+.finally(() => {nonExsistingInFinally()});
\ No newline at end of file
--- /dev/null
+Promise.resolve()
+.finally(() => {nonExsistingInFinally()})
+.catch(() => {console.log("Done")});
\ No newline at end of file
--- /dev/null
+Promise.reject("test").catch((x) => console.log('rejected', x));
\ No newline at end of file
--- /dev/null
+var p = Promise.reject();
+setImmediate(() => {p.catch(() => {})});
\ No newline at end of file
--- /dev/null
+Promise.resolve()
+.then(() => {nonExsisting()});
\ No newline at end of file
--- /dev/null
+Promise.resolve()
+.then(() => {nonExsisting()})
+.catch(() => {console.log("Done")});
\ No newline at end of file
--- /dev/null
+Promise.resolve()
+.then(() => {nonExsistingOne()});
+
+Promise.resolve()
+.then(() => {nonExsistingTwo()})
+.catch(() => {});
--- /dev/null
+Promise.resolve()
+.then(() => {nonExsistingOne()});
+
+Promise.resolve()
+.then(() => {nonExsistingTwo()});
\ No newline at end of file
njs_run {"./test/js/promise_then_throw_finally_catch.js"} \
"Done"
+
+njs_run {"./test/js/promise_catch_throw.js"} \
+"Error: unhandled promise rejection: ReferenceError: \"nonExsistingInCatch\" is not defined"
+
+njs_run {"./test/js/promise_then_throw.js"} \
+"Error: unhandled promise rejection: ReferenceError: \"nonExsisting\" is not defined"
+
+njs_run {"./test/js/promise_then_throw_catch.js"} \
+"Done"
+
+njs_run {"./test/js/promise_catch_then_throw_catch.js"} \
+"Done"
+
+njs_run {"./test/js/promise_finally_throw.js"} \
+"Error: unhandled promise rejection: ReferenceError: \"nonExsistingInFinally\" is not defined"
+
+njs_run {"./test/js/promise_finally_throw_catch.js"} \
+"Done"
+
+njs_run {"./test/js/promise_two_then_throw.js"} \
+"Error: unhandled promise rejection: ReferenceError: \"nonExsistingOne\" is not defined"
+
+njs_run {"./test/js/promise_two_first_then_throw.js"} \
+"Error: unhandled promise rejection: ReferenceError: \"nonExsistingOne\" is not defined"
+
+njs_run {"./test/js/promise_reject_catch.js"} \
+"rejected test"
+
+njs_run {"./test/js/promise_reject_post_catch.js"} \
+"Error: unhandled promise rejection: undefined"