From 19a138713458ad784243e18ae4e29616759000b3 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 29 Nov 2019 17:11:11 +0300 Subject: [PATCH] Making backtrace a property of a thrown exception. --- src/njs.h | 6 +- src/njs_error.c | 172 ++++++++++++++++++-- src/njs_error.h | 2 + src/njs_json.c | 4 - src/njs_object_hash.h | 9 ++ src/njs_shell.c | 22 +-- src/njs_vm.c | 274 ++++++++++++++------------------ src/njs_vm.h | 7 +- src/njs_vmcode.c | 14 +- src/test/njs_interactive_test.c | 5 +- test/njs_expect_test.exp | 21 ++- 11 files changed, 331 insertions(+), 205 deletions(-) diff --git a/src/njs.h b/src/njs.h index 52f359c0..9421af0c 100644 --- a/src/njs.h +++ b/src/njs.h @@ -258,19 +258,19 @@ NJS_EXPORT njs_int_t njs_vm_value_string_set(njs_vm_t *vm, njs_value_t *value, NJS_EXPORT u_char *njs_vm_value_string_alloc(njs_vm_t *vm, njs_value_t *value, uint32_t size); NJS_EXPORT njs_int_t njs_vm_value_string_copy(njs_vm_t *vm, njs_str_t *retval, - const njs_value_t *value, uintptr_t *next); + njs_value_t *value, uintptr_t *next); /* * Converts a value to string. */ NJS_EXPORT njs_int_t njs_vm_value_to_string(njs_vm_t *vm, njs_str_t *dst, - const njs_value_t *src); + njs_value_t *src); /* * Calls njs_vm_value_to_string(), if exception was thrown adds backtrace. */ NJS_EXPORT njs_int_t njs_vm_value_string(njs_vm_t *vm, njs_str_t *dst, - const njs_value_t *src); + njs_value_t *src); NJS_EXPORT njs_int_t njs_vm_retval_string(njs_vm_t *vm, njs_str_t *dst); NJS_EXPORT njs_int_t njs_vm_value_dump(njs_vm_t *vm, njs_str_t *dst, diff --git a/src/njs_error.c b/src/njs_error.c index 0adcfae0..8d317959 100644 --- a/src/njs_error.c +++ b/src/njs_error.c @@ -10,6 +10,7 @@ static const njs_value_t njs_error_message_string = njs_string("message"); static const njs_value_t njs_error_name_string = njs_string("name"); +static const njs_value_t njs_error_stack_string = njs_string("stack"); void @@ -59,6 +60,127 @@ njs_error_fmt_new(njs_vm_t *vm, njs_value_t *dst, njs_object_type_t type, } +static njs_int_t +njs_error_stack_new(njs_vm_t *vm, njs_object_t *error, njs_value_t *retval) +{ + njs_int_t ret; + njs_str_t string; + njs_arr_t *stack; + njs_value_t value; + njs_native_frame_t *frame; + + njs_set_object(&value, error); + + ret = njs_error_to_string(vm, retval, &value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + stack = njs_arr_create(vm->mem_pool, 4, sizeof(njs_backtrace_entry_t)); + if (njs_slow_path(stack == NULL)) { + return NJS_ERROR; + } + + frame = vm->top_frame; + + for ( ;; ) { + if (njs_vm_add_backtrace_entry(vm, stack, frame) != NJS_OK) { + break; + } + + frame = frame->previous; + + if (frame == NULL) { + break; + } + } + + njs_string_get(retval, &string); + + ret = njs_vm_backtrace_to_string(vm, stack, &string); + + njs_arr_destroy(stack); + + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return njs_string_set(vm, retval, string.start, string.length); +} + + +njs_int_t +njs_error_stack_attach(njs_vm_t *vm, njs_value_t *value) +{ + njs_int_t ret; + njs_object_t *error; + njs_object_prop_t *prop; + njs_lvlhsh_query_t lhq; + + if (njs_slow_path(!njs_is_error(value))) { + return NJS_DECLINED; + } + + if (vm->debug == NULL || vm->start == NULL) { + return NJS_OK; + } + + error = njs_object(value); + + lhq.replace = 0; + lhq.pool = vm->mem_pool; + lhq.proto = &njs_object_hash_proto; + + lhq.key = njs_str_value("stack"); + lhq.key_hash = NJS_STACK_HASH; + + prop = njs_object_prop_alloc(vm, &njs_error_stack_string, + &njs_value_undefined, 1); + if (njs_slow_path(prop == NULL)) { + return NJS_ERROR; + } + + prop->enumerable = 0; + + ret = njs_error_stack_new(vm, error, &prop->value); + if (njs_slow_path(ret == NJS_ERROR)) { + njs_internal_error(vm, "njs_error_stack_new() failed"); + return NJS_ERROR; + } + + if (ret == NJS_OK) { + lhq.value = prop; + + ret = njs_lvlhsh_insert(&error->hash, &lhq); + if (njs_slow_path(ret == NJS_ERROR)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; + } + } + + return NJS_OK; +} + + +njs_int_t +njs_error_stack(njs_vm_t *vm, njs_value_t *value, njs_value_t *stack) +{ + njs_int_t ret; + + ret = njs_value_property(vm, value, njs_value_arg(&njs_error_stack_string), + stack); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (!njs_is_string(stack)) { + return NJS_DECLINED; + } + + return NJS_OK; +} + + njs_object_t * njs_error_alloc(njs_vm_t *vm, njs_object_type_t type, const njs_value_t *name, const njs_value_t *message) @@ -83,11 +205,11 @@ njs_error_alloc(njs_vm_t *vm, njs_object_type_t type, const njs_value_t *name, lhq.replace = 0; lhq.pool = vm->mem_pool; + lhq.proto = &njs_object_hash_proto; if (name != NULL) { lhq.key = njs_str_value("name"); lhq.key_hash = NJS_NAME_HASH; - lhq.proto = &njs_object_hash_proto; prop = njs_object_prop_alloc(vm, &njs_error_name_string, name, 1); if (njs_slow_path(prop == NULL)) { @@ -106,7 +228,6 @@ njs_error_alloc(njs_vm_t *vm, njs_object_type_t type, const njs_value_t *name, if (message!= NULL) { lhq.key = njs_str_value("message"); lhq.key_hash = NJS_MESSAGE_HASH; - lhq.proto = &njs_object_hash_proto; prop = njs_object_prop_alloc(vm, &njs_error_message_string, message, 1); if (njs_slow_path(prop == NULL)) { @@ -605,20 +726,8 @@ njs_error_prototype_value_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, static njs_int_t -njs_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused) -{ - if (nargs < 1 || !njs_is_object(&args[0])) { - njs_type_error(vm, "\"this\" argument is not an object"); - return NJS_ERROR; - } - - return njs_error_to_string(vm, &vm->retval, &args[0]); -} - - -njs_int_t -njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error) +njs_error_to_string2(njs_vm_t *vm, njs_value_t *retval, + const njs_value_t *error, njs_bool_t want_stack) { size_t length; u_char *p; @@ -630,6 +739,17 @@ njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error) static const njs_value_t default_name = njs_string("Error"); + if (want_stack) { + ret = njs_error_stack(vm, njs_value_arg(error), retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (ret == NJS_OK) { + return NJS_OK; + } + } + njs_object_property_init(&lhq, &njs_string_name, NJS_NAME_HASH); ret = njs_object_property(vm, error, &lhq, &value1); @@ -708,6 +828,26 @@ njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error) } +static njs_int_t +njs_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + if (nargs < 1 || !njs_is_object(&args[0])) { + njs_type_error(vm, "\"this\" argument is not an object"); + return NJS_ERROR; + } + + return njs_error_to_string2(vm, &vm->retval, &args[0], 0); +} + + +njs_int_t +njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error) +{ + return njs_error_to_string2(vm, retval, error, 1); +} + + static const njs_object_prop_t njs_error_prototype_properties[] = { { diff --git a/src/njs_error.h b/src/njs_error.h index 0d5f0e0a..72ef229e 100644 --- a/src/njs_error.h +++ b/src/njs_error.h @@ -44,6 +44,8 @@ njs_object_t *njs_error_alloc(njs_vm_t *vm, njs_object_type_t type, const njs_value_t *name, const njs_value_t *message); njs_int_t njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error); +njs_int_t njs_error_stack(njs_vm_t *vm, njs_value_t *value, njs_value_t *stack); +njs_int_t njs_error_stack_attach(njs_vm_t *vm, njs_value_t *value); extern const njs_object_type_init_t njs_error_type_init; diff --git a/src/njs_json.c b/src/njs_json.c index dac2e4cc..be02fff7 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -2144,10 +2144,6 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, const njs_value_t *value, njs_property_query_t pq; njs_json_stringify_t *stringify, dump_stringify; - if (njs_vm_backtrace(vm) != NULL) { - goto exception; - } - stringify = &dump_stringify; stringify->vm = vm; diff --git a/src/njs_object_hash.h b/src/njs_object_hash.h index e5df7a35..4a65b202 100644 --- a/src/njs_object_hash.h +++ b/src/njs_object_hash.h @@ -411,6 +411,15 @@ 's'), 'e'), 't') +#define NJS_STACK_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 's'), 't'), 'a'), 'c'), 'k') + + #define NJS_STRING_HASH \ njs_djb_hash_add( \ njs_djb_hash_add( \ diff --git a/src/njs_shell.c b/src/njs_shell.c index f3b8067a..1f4c9452 100644 --- a/src/njs_shell.c +++ b/src/njs_shell.c @@ -722,19 +722,23 @@ njs_output(njs_opts_t *opts, njs_vm_t *vm, njs_int_t ret) { njs_str_t out; - if (!opts->silent) { + if (opts->silent) { + return; + } + + if (ret == NJS_OK) { if (njs_vm_retval_dump(vm, &out, 1) != NJS_OK) { - out = njs_str_value("failed to get retval from VM"); - ret = NJS_ERROR; + njs_stderror("Shell:failed to get retval from VM\n"); + return; } - if (ret != NJS_OK) { - njs_stderror("%V\n", &out); - - } else if (vm->options.accumulative) { - njs_print(out.start, out.length); - njs_printf("\n"); + if (vm->options.accumulative) { + njs_printf("%V\n", &out); } + + } else { + njs_vm_retval_string(vm, &out); + njs_stderror("Thrown:\n%V\n", &out); } } diff --git a/src/njs_vm.c b/src/njs_vm.c index 56661d0a..913cdea5 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -153,10 +153,6 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) parser->lexer = &lexer; - if (vm->backtrace != NULL) { - njs_arr_reset(vm->backtrace); - } - njs_set_undefined(&vm->retval); ret = njs_parser(vm, parser, prev); @@ -471,10 +467,6 @@ njs_vm_post_event(njs_vm_t *vm, njs_vm_event_t vm_event, njs_int_t njs_vm_run(njs_vm_t *vm) { - if (njs_slow_path(vm->backtrace != NULL)) { - njs_arr_reset(vm->backtrace); - } - return njs_vm_handle_events(vm); } @@ -648,120 +640,11 @@ njs_vm_memory_error(njs_vm_t *vm) } -njs_arr_t * -njs_vm_backtrace(njs_vm_t *vm) -{ - if (vm->backtrace != NULL && !njs_arr_is_empty(vm->backtrace)) { - return vm->backtrace; - } - - return NULL; -} - - -static njs_int_t -njs_vm_backtrace_dump(njs_vm_t *vm, njs_str_t *dst, const njs_value_t *src) -{ - u_char *p, *start, *end; - size_t len, count; - njs_uint_t i; - njs_arr_t *backtrace; - njs_backtrace_entry_t *be, *prev; - - backtrace = njs_vm_backtrace(vm); - - len = dst->length + 1; - - count = 0; - prev = NULL; - - be = backtrace->start; - - for (i = 0; i < backtrace->items; i++) { - if (i != 0 && prev->name.start == be->name.start - && prev->line == be->line) - { - count++; - - } else { - - if (count != 0) { - len += njs_length(" repeats times\n") - + NJS_INT_T_LEN; - count = 0; - } - - len += be->name.length + njs_length(" at ()\n"); - - if (be->line != 0) { - len += be->file.length + NJS_INT_T_LEN + 1; - - } else { - len += njs_length("native"); - } - } - - prev = be; - be++; - } - - p = njs_mp_alloc(vm->mem_pool, len); - if (p == NULL) { - njs_memory_error(vm); - return NJS_ERROR; - } - - start = p; - end = start + len; - - p = njs_cpymem(p, dst->start, dst->length); - *p++ = '\n'; - - count = 0; - prev = NULL; - - be = backtrace->start; - - for (i = 0; i < backtrace->items; i++) { - if (i != 0 && prev->name.start == be->name.start - && prev->line == be->line) - { - count++; - - } else { - if (count != 0) { - p = njs_sprintf(p, end, " repeats %uz times\n", - count); - count = 0; - } - - p = njs_sprintf(p, end, " at %V ", &be->name); - - if (be->line != 0) { - p = njs_sprintf(p, end, "(%V:%uD)\n", &be->file, - be->line); - - } else { - p = njs_sprintf(p, end, "(native)\n"); - } - } - - prev = be; - be++; - } - - dst->start = start; - dst->length = p - dst->start; - - return NJS_OK; -} - - njs_int_t -njs_vm_value_string(njs_vm_t *vm, njs_str_t *dst, const njs_value_t *src) +njs_vm_value_string(njs_vm_t *vm, njs_str_t *dst, njs_value_t *src) { - njs_int_t ret; - njs_uint_t exception; + njs_int_t ret; + njs_uint_t exception; if (njs_slow_path(src->type == NJS_NUMBER && njs_number(src) == 0 @@ -771,26 +654,17 @@ njs_vm_value_string(njs_vm_t *vm, njs_str_t *dst, const njs_value_t *src) return NJS_OK; } - exception = 1; + exception = 0; again: ret = njs_vm_value_to_string(vm, dst, src); - if (njs_fast_path(ret == NJS_OK)) { - - if (njs_vm_backtrace(vm) != NULL) { - ret = njs_vm_backtrace_dump(vm, dst, src); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } - } - return NJS_OK; } - if (exception) { - exception = 0; + if (!exception) { + exception = 1; /* value evaluation threw an exception. */ @@ -966,18 +840,18 @@ njs_vm_object_prop(njs_vm_t *vm, const njs_value_t *value, const njs_str_t *key) njs_int_t -njs_vm_value_to_string(njs_vm_t *vm, njs_str_t *dst, const njs_value_t *src) +njs_vm_value_to_string(njs_vm_t *vm, njs_str_t *dst, njs_value_t *src) { u_char *start; size_t size; njs_int_t ret; - njs_value_t value; + njs_value_t value, stack; if (njs_slow_path(src == NULL)) { return NJS_ERROR; } - if (njs_slow_path(njs_is_error(src))) { + if (njs_is_error(src)) { /* MemoryError is a nonextensible internal error. */ @@ -987,6 +861,15 @@ njs_vm_value_to_string(njs_vm_t *vm, njs_str_t *dst, const njs_value_t *src) njs_string_get(&njs_string_memory_error, dst); return NJS_OK; } + + ret = njs_error_stack(vm, src, &stack); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (ret == NJS_OK) { + src = &stack; + } } value = *src; @@ -1020,7 +903,7 @@ njs_vm_value_to_string(njs_vm_t *vm, njs_str_t *dst, const njs_value_t *src) njs_int_t njs_vm_value_string_copy(njs_vm_t *vm, njs_str_t *retval, - const njs_value_t *value, uintptr_t *next) + njs_value_t *value, uintptr_t *next) { uintptr_t n; njs_array_t *array; @@ -1060,31 +943,19 @@ njs_vm_value_string_copy(njs_vm_t *vm, njs_str_t *retval, njs_int_t -njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame) +njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack, + njs_native_frame_t *native_frame) { njs_int_t ret; - njs_arr_t *backtrace; njs_uint_t i; njs_function_t *function; - njs_native_frame_t *native_frame; njs_function_debug_t *debug_entry; njs_function_lambda_t *lambda; njs_backtrace_entry_t *be; - if (njs_slow_path(vm->backtrace == NULL)) { - backtrace = njs_arr_create(vm->mem_pool, 4, - sizeof(njs_backtrace_entry_t)); - if (njs_slow_path(backtrace == NULL)) { - return NJS_ERROR; - } - - vm->backtrace = backtrace; - } - - native_frame = &frame->native; function = native_frame->function; - be = njs_arr_add(vm->backtrace); + be = njs_arr_add(stack); if (njs_slow_path(be == NULL)) { return NJS_ERROR; } @@ -1143,6 +1014,107 @@ njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame) } +njs_int_t +njs_vm_backtrace_to_string(njs_vm_t *vm, njs_arr_t *backtrace, njs_str_t *dst) +{ + u_char *p, *start, *end; + size_t len, count; + njs_uint_t i; + njs_backtrace_entry_t *be, *prev; + + if (backtrace->items == 0) { + return NJS_OK; + } + + len = dst->length + 1; + + count = 0; + prev = NULL; + + be = backtrace->start; + + for (i = 0; i < backtrace->items; i++) { + if (i != 0 && prev->name.start == be->name.start + && prev->line == be->line) + { + count++; + + } else { + + if (count != 0) { + len += njs_length(" repeats times\n") + + NJS_INT_T_LEN; + count = 0; + } + + len += be->name.length + njs_length(" at ()\n"); + + if (be->line != 0) { + len += be->file.length + NJS_INT_T_LEN + 1; + + } else { + len += njs_length("native"); + } + } + + prev = be; + be++; + } + + p = njs_mp_alloc(vm->mem_pool, len); + if (p == NULL) { + njs_memory_error(vm); + return NJS_ERROR; + } + + start = p; + end = start + len; + + p = njs_cpymem(p, dst->start, dst->length); + *p++ = '\n'; + + count = 0; + prev = NULL; + + be = backtrace->start; + + for (i = 0; i < backtrace->items; i++) { + if (i != 0 && prev->name.start == be->name.start + && prev->line == be->line) + { + count++; + + } else { + if (count != 0) { + p = njs_sprintf(p, end, " repeats %uz times\n", + count); + count = 0; + } + + p = njs_sprintf(p, end, " at %V ", &be->name); + + if (be->line != 0) { + p = njs_sprintf(p, end, "(%V:%uD)\n", &be->file, + be->line); + + } else { + p = njs_sprintf(p, end, "(native)\n"); + } + } + + prev = be; + be++; + } + + dst->start = start; + dst->length = p - dst->start; + + return NJS_OK; +} + + + + void * njs_lvlhsh_alloc(void *data, size_t size) { diff --git a/src/njs_vm.h b/src/njs_vm.h index 3cb98b11..14ed38c1 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -224,7 +224,6 @@ struct njs_vm_s { njs_random_t random; njs_arr_t *debug; - njs_arr_t *backtrace; /* * njs_property_query() uses it to store reference to a temporary @@ -272,14 +271,16 @@ struct njs_vm_shared_s { void njs_vm_scopes_restore(njs_vm_t *vm, njs_frame_t *frame, njs_native_frame_t *previous); -njs_int_t njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame); +njs_int_t njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack, + njs_native_frame_t *native_frame); +njs_int_t njs_vm_backtrace_to_string(njs_vm_t *vm, njs_arr_t *stack, + njs_str_t *dst); njs_int_t njs_builtin_objects_create(njs_vm_t *vm); njs_int_t njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global); njs_int_t njs_builtin_match_native_function(njs_vm_t *vm, njs_function_native_t func, njs_str_t *name); -njs_arr_t *njs_vm_backtrace(njs_vm_t *vm); njs_arr_t *njs_vm_completions(njs_vm_t *vm, njs_str_t *expression); void *njs_lvlhsh_alloc(void *data, size_t size); diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index a7de5227..725727e4 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -898,6 +898,10 @@ next: error: + if (njs_is_error(&vm->retval)) { + (void) njs_error_stack_attach(vm, &vm->retval); + } + for ( ;; ) { frame = (njs_frame_t *) vm->top_frame; @@ -906,19 +910,9 @@ error: if (catch != NULL) { pc = catch; - if (vm->backtrace != NULL) { - njs_arr_reset(vm->backtrace); - } - goto next; } - if (vm->debug != NULL - && njs_vm_add_backtrace_entry(vm, frame) != NJS_OK) - { - break; - } - previous = frame->native.previous; if (previous == NULL) { break; diff --git a/src/test/njs_interactive_test.c b/src/test/njs_interactive_test.c index fb384a02..221eff4b 100644 --- a/src/test/njs_interactive_test.c +++ b/src/test/njs_interactive_test.c @@ -243,10 +243,7 @@ static njs_interactive_test_t njs_test[] = " at main (native)\n") }, { njs_str("function f(n) { if (n == 0) { throw 'a'; } return f(n-1); }; f(2)" ENTER), - njs_str("a\n" - " at f (:1)\n" - " repeats 2 times\n" - " at main (native)\n") }, + njs_str("a") }, /* Exception in njs_vm_retval_string() */ diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp index 7f6397cb..c5b9e59e 100644 --- a/test/njs_expect_test.exp +++ b/test/njs_expect_test.exp @@ -258,7 +258,7 @@ njs_test { njs_test { {"console.ll()\r\n" - "console.ll()\r\nTypeError: (intermediate value)\\\[\"ll\"] is not a function"} + "console.ll()\r\nThrown:\r\nTypeError: (intermediate value)\\\[\"ll\"] is not a function"} } njs_test { @@ -280,7 +280,7 @@ njs_test { # Backtraces for external objects njs_test { {"console.log(console.a.a)\r\n" - "console.log(console.a.a)\r\nTypeError:*at print (native)"} + "console.log(console.a.a)\r\nThrown:\r\nTypeError:*at print (native)"} } # dumper @@ -321,9 +321,9 @@ njs_test { # Backtraces are reset between invocations njs_test { {"JSON.parse(Error())\r\n" - "JSON.parse(Error())\r\nSyntaxError: Unexpected token at position 0*at JSON.parse (native)"} + "JSON.parse(Error())\r\nThrown:\r\nSyntaxError: Unexpected token at position 0*at JSON.parse (native)"} {"JSON.parse(Error()\r\n" - "JSON.parse(Error()\r\nSyntaxError: Unexpected token \"\" in shell:1"} + "JSON.parse(Error()\r\nThrown:\r\nSyntaxError: Unexpected token \"\" in shell:1"} } njs_test { @@ -335,7 +335,18 @@ njs_test { njs_test { {"(function() { throw 'test' })()\r\n" - "test\r\n at anonymous (shell:1)"} + "Thrown:\r\ntest"} +} + +njs_test { + {"function f() { return ({}.a.a); }\r\n" + "undefined"} + {"var e; try {f()} catch (ee) {e = ee}\r\n" + "undefined"} + {"Object.keys(null)\r\n" + "Thrown:\r\nTypeError: cannot convert null argument to object"} + {"e\r\n" + "TypeError: cannot get property \"a\" of undefined*at f (shell:1)"} } # Non-ASCII characters -- 2.47.3