From 575be638c4beff4cae3dec74a6c4ae0692bde18b Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 17 Nov 2017 18:55:07 +0300 Subject: [PATCH] Fixed exception handling. njs_vm_exception() is removed and combined with njs_vm_retval(). vm->exception is removed either, exceptions are now stored in vm->retval. It simplifies the client logic, because previously njs_vm_exception() had to be called if njs_vm_retval() fails. Additonally, stack traces are now appended to the retval if an exception happens. --- nginx/ngx_http_js_module.c | 6 +- nginx/ngx_stream_js_module.c | 8 +-- njs/njs.c | 54 +++------------- njs/njs_error.c | 36 ++++------- njs/njs_function.c | 2 + njs/njs_generator.c | 6 +- njs/njs_parser.c | 106 +++++++++++++++++++------------- njs/njs_parser.h | 4 ++ njs/njs_parser_expression.c | 25 ++++---- njs/njs_regexp.c | 34 +++++----- njs/njs_variable.c | 16 ++--- njs/njs_vm.c | 19 ------ njs/njs_vm.h | 5 -- njs/njscript.c | 91 +++++++++++++++++++++++---- njs/njscript.h | 1 - njs/test/njs_benchmark.c | 9 +-- njs/test/njs_expect_test.exp | 16 +++++ njs/test/njs_interactive_test.c | 99 ++++++++++------------------- njs/test/njs_unit_test.c | 46 +++++++++----- 19 files changed, 291 insertions(+), 292 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index e589f11a..e03c40ae 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -445,7 +445,7 @@ ngx_http_js_handler(ngx_http_request_t *r) } if (njs_vm_call(ctx->vm, func, ctx->args, 2) != NJS_OK) { - njs_vm_exception(ctx->vm, &exception); + njs_vm_retval(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); @@ -496,7 +496,7 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, } if (njs_vm_call(ctx->vm, func, ctx->args, 2) != NJS_OK) { - njs_vm_exception(ctx->vm, &exception); + njs_vm_retval(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); @@ -1333,7 +1333,7 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) rc = njs_vm_compile(jlcf->vm, &start, end); if (rc != NJS_OK) { - njs_vm_exception(jlcf->vm, &text); + njs_vm_retval(jlcf->vm, &text); ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%*s, included", diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index f734e5a3..a2027492 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -408,7 +408,7 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) } if (njs_vm_call(ctx->vm, func, ctx->arg, 1) != NJS_OK) { - njs_vm_exception(ctx->vm, &exception); + njs_vm_retval(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %*s", exception.length, exception.start); @@ -495,7 +495,7 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ctx->buf = in->buf; if (njs_vm_call(ctx->vm, func, ctx->arg, 1) != NJS_OK) { - njs_vm_exception(ctx->vm, &exception); + njs_vm_retval(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %*s", exception.length, exception.start); @@ -593,7 +593,7 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, } if (njs_vm_call(ctx->vm, func, ctx->arg, 1) != NJS_OK) { - njs_vm_exception(ctx->vm, &exception); + njs_vm_retval(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "js exception: %*s", exception.length, exception.start); @@ -1043,7 +1043,7 @@ ngx_stream_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) rc = njs_vm_compile(jscf->vm, &start, end); if (rc != NJS_OK) { - njs_vm_exception(jscf->vm, &text); + njs_vm_retval(jscf->vm, &text); ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%*s, included", diff --git a/njs/njs.c b/njs/njs.c index f99491f3..80c7ed8d 100644 --- a/njs/njs.c +++ b/njs/njs.c @@ -64,7 +64,6 @@ static nxt_int_t njs_interactive_shell(njs_opts_t *opts, static nxt_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options); static nxt_int_t njs_process_script(njs_vm_t *vm, njs_opts_t *opts, const nxt_str_t *script, nxt_str_t *out); -static void njs_print_backtrace(nxt_array_t *backtrace); static nxt_int_t njs_editline_init(njs_vm_t *vm); static char **njs_completion_handler(const char *text, int start, int end); static char *njs_completion_generator(const char *text, int state); @@ -240,10 +239,9 @@ njs_externals_init(njs_opts_t *opts, njs_vm_opt_t *vm_options) static nxt_int_t njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options) { - njs_vm_t *vm; - nxt_int_t ret; - nxt_str_t line, out; - nxt_array_t *backtrace; + njs_vm_t *vm; + nxt_int_t ret; + nxt_str_t line, out; vm = njs_vm_create(vm_options); if (vm == NULL) { @@ -282,11 +280,6 @@ njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options) printf("%.*s\n", (int) out.length, out.start); - backtrace = njs_vm_backtrace(vm); - if (backtrace != NULL) { - njs_print_backtrace(backtrace); - } - /* editline allocs a new buffer every time. */ free(line.start); } @@ -307,7 +300,6 @@ njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options) nxt_int_t ret; nxt_str_t out, script; struct stat sb; - nxt_array_t *backtrace; file = opts->file; @@ -399,11 +391,6 @@ njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options) if (!opts->disassemble) { printf("%.*s\n", (int) out.length, out.start); - - backtrace = njs_vm_backtrace(vm); - if (backtrace != NULL) { - njs_print_backtrace(backtrace); - } } ret = NXT_OK; @@ -442,44 +429,19 @@ njs_process_script(njs_vm_t *vm, njs_opts_t *opts, const nxt_str_t *script, } ret = njs_vm_run(vm); - - if (ret == NXT_OK) { - if (njs_vm_retval(vm, out) != NXT_OK) { - return NXT_ERROR; - } - - } else { - njs_vm_exception(vm, out); + if (ret == NXT_AGAIN) { + return ret; } + } - } else { - njs_vm_exception(vm, out); + if (njs_vm_retval(vm, out) != NXT_OK) { + return NXT_ERROR; } return NXT_OK; } -static void -njs_print_backtrace(nxt_array_t *backtrace) -{ - nxt_uint_t i; - njs_backtrace_entry_t *be; - - be = backtrace->start; - - for (i = 0; i < backtrace->items; i++) { - if (be[i].line != 0) { - printf("at %.*s (:%d)\n", (int) be[i].name.length, be[i].name.start, - be[i].line); - - } else { - printf("at %.*s\n", (int) be[i].name.length, be[i].name.start); - } - } -} - - static nxt_int_t njs_editline_init(njs_vm_t *vm) { diff --git a/njs/njs_error.c b/njs/njs_error.c index dbb560c1..1061326e 100644 --- a/njs/njs_error.c +++ b/njs/njs_error.c @@ -37,7 +37,7 @@ njs_exception_error_create(njs_vm_t *vm, njs_value_type_t type, size_t size; va_list args; nxt_int_t ret; - njs_value_t string, *value; + njs_value_t string; njs_object_t *error; static char buf[256]; @@ -61,16 +61,9 @@ njs_exception_error_create(njs_vm_t *vm, njs_value_type_t type, goto memory_error; } - value = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t)); - if (nxt_slow_path(value == NULL)) { - goto memory_error; - } - - value->data.u.object = error; - value->type = type; - value->data.truth = 1; - - vm->exception = value; + vm->retval.data.u.object = error; + vm->retval.type = type; + vm->retval.data.truth = 1; return; @@ -495,9 +488,8 @@ const njs_object_init_t njs_uri_error_constructor_init = { static void -njs_init_memory_error(njs_vm_t *vm) +njs_set_memory_error(njs_vm_t *vm) { - njs_value_t *value; njs_object_t *object; njs_object_prototype_t *prototypes; @@ -516,21 +508,17 @@ njs_init_memory_error(njs_vm_t *vm) */ object->extensible = 0; - value = &vm->memory_error; - - value->data.type = NJS_OBJECT_INTERNAL_ERROR; - value->data.truth = 1; - value->data.u.number = NAN; - value->data.u.object = object; + vm->retval.data.type = NJS_OBJECT_INTERNAL_ERROR; + vm->retval.data.truth = 1; + vm->retval.data.u.number = NAN; + vm->retval.data.u.object = object; } void njs_exception_memory_error(njs_vm_t *vm) { - njs_init_memory_error(vm); - - vm->exception = &vm->memory_error; + njs_set_memory_error(vm); } @@ -538,9 +526,7 @@ njs_ret_t njs_memory_error_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) { - njs_init_memory_error(vm); - - vm->retval = vm->memory_error; + njs_set_memory_error(vm); return NXT_OK; } diff --git a/njs/njs_function.c b/njs/njs_function.c index 8a338295..63ec876b 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -701,6 +701,8 @@ njs_ret_t njs_eval_function(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) { + njs_exception_internal_error(vm, "Not implemented", NULL); + return NXT_ERROR; } diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 0d98f3e0..885fc958 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -1150,8 +1150,7 @@ njs_generate_continue_statement(njs_vm_t *vm, njs_parser_t *parser, } } - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Illegal continue statement"); + njs_parser_syntax_error(vm, parser, "Illegal continue statement", NULL); return NXT_ERROR; @@ -1194,8 +1193,7 @@ njs_generate_break_statement(njs_vm_t *vm, njs_parser_t *parser, } } - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Illegal break statement"); + njs_parser_syntax_error(vm, parser, "Illegal break statement", NULL); return NXT_ERROR; diff --git a/njs/njs_parser.c b/njs/njs_parser.c index 2134dc68..d97e0827 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -196,9 +197,9 @@ njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type) break; } - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, "SyntaxError: " - "The maximum function nesting level is \"%d\"", - NJS_MAX_NESTING); + njs_parser_syntax_error(vm, parser, + "The maximum function nesting " + "level is \"%d\"", NJS_MAX_NESTING); return NXT_ERROR; } @@ -310,7 +311,7 @@ njs_parser_statement_chain(njs_vm_t *vm, njs_parser_t *parser, } } - } else if (vm->exception == NULL) { + } else if (!njs_is_error(&vm->retval)) { (void) njs_parser_unexpected_token(vm, parser, token); } @@ -770,8 +771,8 @@ njs_parser_return_statement(njs_vm_t *vm, njs_parser_t *parser) scope = scope->parent) { if (scope->type == NJS_SCOPE_GLOBAL) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Illegal return statement"); + njs_parser_syntax_error(vm, parser, "Illegal return statement", + NULL); return NXT_ERROR; } @@ -1053,9 +1054,9 @@ njs_parser_switch_statement(njs_vm_t *vm, njs_parser_t *parser) } else { if (dflt != NULL) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: More than one default clause " - "in switch statement"); + njs_parser_syntax_error(vm, parser, + "More than one default clause " + "in switch statement", NULL); return NJS_TOKEN_ILLEGAL; } @@ -1461,9 +1462,9 @@ njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, nxt_str_t *name, node = parser->node->left; if (node->token != NJS_TOKEN_NAME) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, "ReferenceError: Invalid " - "left-hand side \"%.*s\" in for-in statement", - (int) name->length, name->start); + njs_parser_ref_error(vm, parser, "Invalid left-hand side \"%.*s\" " + "in for-in statement", (int) name->length, + name->start); return NJS_TOKEN_ILLEGAL; } @@ -1686,8 +1687,8 @@ njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser) } if (try->right == NULL) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Missing catch or finally after try"); + njs_parser_syntax_error(vm, parser, "Missing catch or " + "finally after try", NULL); return NJS_TOKEN_ILLEGAL; } @@ -1936,9 +1937,9 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) break; case NJS_TOKEN_UNTERMINATED_STRING: - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Unterminated string \"%.*s\"", - (int) parser->lexer->text.length, parser->lexer->text.start); + njs_parser_syntax_error(vm, parser, "Unterminated string \"%.*s\"", + (int) parser->lexer->text.length, + parser->lexer->text.start); return NJS_TOKEN_ILLEGAL; @@ -2540,9 +2541,9 @@ njs_parser_escape_string_create(njs_vm_t *vm, njs_parser_t *parser, invalid: - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Invalid Unicode code point \"%.*s\"", - (int) parser->lexer->text.length, parser->lexer->text.start); + njs_parser_syntax_error(vm, parser, "Invalid Unicode code point \"%.*s\"", + (int) parser->lexer->text.length, + parser->lexer->text.start); return NJS_TOKEN_ILLEGAL; } @@ -2584,13 +2585,12 @@ njs_parser_unexpected_token(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) { if (token != NJS_TOKEN_END) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Unexpected token \"%.*s\"", - (int) parser->lexer->text.length, parser->lexer->text.start); + njs_parser_syntax_error(vm, parser, "Unexpected token \"%.*s\"", + (int) parser->lexer->text.length, + parser->lexer->text.start); } else { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Unexpected end of input"); + njs_parser_syntax_error(vm, parser, "Unexpected end of input", NULL); } return NJS_TOKEN_ILLEGAL; @@ -2601,18 +2601,13 @@ u_char * njs_parser_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, u_char *start) { - int n; u_char *p; - ssize_t size; + size_t size; njs_vm_t *vm; - p = start; - - if (td->level == NXT_LEVEL_CRIT) { - size = sizeof("InternalError: ") - 1; - memcpy(p, "InternalError: ", size); - p = start + size; - } + size = sizeof("InternalError: ") - 1; + memcpy(start, "InternalError: ", size); + p = start + size; vm = trace->data; @@ -2620,16 +2615,43 @@ njs_parser_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, p = trace->handler(trace, td, p); if (vm->parser != NULL) { - size = td->end - start; + njs_exception_internal_error(vm, "%s in %u", start, + vm->parser->lexer->line); + } else { + njs_exception_internal_error(vm, "%s", start); + } - n = snprintf((char *) p, size, " in %u", vm->parser->lexer->line); + return p; +} - if (n < size) { - p += n; - } - } - njs_vm_throw_exception(vm, start, p - start); +void +njs_parser_syntax_error(njs_vm_t *vm, njs_parser_t *parser, const char* fmt, + ...) +{ + va_list args; - return p; + static char buf[256]; + + va_start(args, fmt); + (void) vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + njs_exception_syntax_error(vm, "%s in %u", buf, parser->lexer->line); +} + + +void +njs_parser_ref_error(njs_vm_t *vm, njs_parser_t *parser, const char* fmt, + ...) +{ + va_list args; + + static char buf[256]; + + va_start(args, fmt); + (void) vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + njs_exception_ref_error(vm, "%s in %u", buf, parser->lexer->line); } diff --git a/njs/njs_parser.h b/njs/njs_parser.h index c0990a52..f2fd25c4 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -381,6 +381,10 @@ njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node); nxt_bool_t njs_parser_has_side_effect(njs_parser_node_t *node); u_char *njs_parser_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, u_char *start); +void njs_parser_syntax_error(njs_vm_t *vm, njs_parser_t *parser, + const char* fmt, ...); +void njs_parser_ref_error(njs_vm_t *vm, njs_parser_t *parser, const char* fmt, + ...); nxt_int_t njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node); diff --git a/njs/njs_parser_expression.c b/njs/njs_parser_expression.c index 3c9e1c5a..379d8037 100644 --- a/njs/njs_parser_expression.c +++ b/njs/njs_parser_expression.c @@ -294,8 +294,8 @@ njs_parser_var_expression(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) } if (!njs_parser_is_lvalue(parser->node)) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "ReferenceError: Invalid left-hand side in assignment"); + njs_parser_ref_error(vm, parser, + "Invalid left-hand side in assignment", NULL); return NJS_TOKEN_ILLEGAL; } @@ -432,8 +432,8 @@ njs_parser_assignment_expression(njs_vm_t *vm, njs_parser_t *parser, } if (!njs_parser_is_lvalue(parser->node)) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "ReferenceError: Invalid left-hand side in assignment"); + njs_parser_ref_error(vm, parser, "Invalid left-hand side " + "in assignment", NULL); return NJS_TOKEN_ILLEGAL; } @@ -756,9 +756,8 @@ njs_parser_unary_expression(njs_vm_t *vm, njs_parser_t *parser, } if (next == NJS_TOKEN_EXPONENTIATION) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Either left-hand side or entire exponentiation " - "must be parenthesized"); + njs_parser_syntax_error(vm, parser, "Either left-hand side or entire " + "exponentiation must be parenthesized"); return NJS_TOKEN_ILLEGAL; } @@ -796,8 +795,8 @@ njs_parser_unary_expression(njs_vm_t *vm, njs_parser_t *parser, case NJS_TOKEN_NAME: case NJS_TOKEN_UNDEFINED: - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Delete of an unqualified identifier"); + njs_parser_syntax_error(vm, parser, + "Delete of an unqualified identifier", NULL); return NJS_TOKEN_ILLEGAL; @@ -856,8 +855,8 @@ njs_parser_inc_dec_expression(njs_vm_t *vm, njs_parser_t *parser, } if (!njs_parser_is_lvalue(parser->node)) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "ReferenceError: Invalid left-hand side in prefix operation"); + njs_parser_ref_error(vm, parser, "Invalid left-hand side " + "in prefix operation", NULL); return NJS_TOKEN_ILLEGAL; } @@ -911,8 +910,8 @@ njs_parser_post_inc_dec_expression(njs_vm_t *vm, njs_parser_t *parser, } if (!njs_parser_is_lvalue(parser->node)) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "ReferenceError: Invalid left-hand side in postfix operation"); + njs_parser_ref_error(vm, parser, "Invalid left-hand side " + "in postfix operation", NULL); return NJS_TOKEN_ILLEGAL; } diff --git a/njs/njs_regexp.c b/njs/njs_regexp.c index 770d3693..974a4225 100644 --- a/njs/njs_regexp.c +++ b/njs/njs_regexp.c @@ -178,9 +178,9 @@ njs_regexp_literal(njs_vm_t *vm, njs_parser_t *parser, njs_value_t *value) flags = njs_regexp_flags(&p, lexer->end, 0); if (nxt_slow_path(flags < 0)) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Invalid RegExp flags \"%.*s\"", - p - lexer->start, lexer->start); + njs_parser_syntax_error(vm, parser, + "Invalid RegExp flags \"%.*s\"", + p - lexer->start, lexer->start); return NJS_TOKEN_ILLEGAL; } @@ -199,9 +199,8 @@ njs_regexp_literal(njs_vm_t *vm, njs_parser_t *parser, njs_value_t *value) } } - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Unterminated RegExp \"%.*s\"", - p - lexer->start - 1, lexer->start - 1); + njs_parser_syntax_error(vm, parser, "Unterminated RegExp \"%.*s\"", + p - lexer->start - 1, lexer->start - 1); return NJS_TOKEN_ILLEGAL; } @@ -386,9 +385,8 @@ static u_char * njs_regexp_compile_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, u_char *start) { - int n; u_char *p; - ssize_t size; + size_t size; njs_vm_t *vm; size = sizeof("SyntaxError: ") - 1; @@ -398,20 +396,16 @@ njs_regexp_compile_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, vm = trace->data; trace = trace->next; - p = trace->handler(trace, td, p); + p = trace->handler(trace, td, start); if (vm->parser != NULL) { - size = td->end - start; - - n = snprintf((char *) p, size, " in %u", vm->parser->lexer->line); + njs_exception_syntax_error(vm, "%s in %u", start, + vm->parser->lexer->line); - if (n < size) { - p += n; - } + } else { + njs_exception_syntax_error(vm, "%s", start); } - njs_vm_throw_exception(vm, start, p - start); - return p; } @@ -442,8 +436,8 @@ njs_regexp_match_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, size_t size; njs_vm_t *vm; - size = sizeof("RegExpError: ") - 1; - memcpy(start, "RegExpError: ", size); + size = sizeof("InternalError: ") - 1; + memcpy(start, "InternalError: ", size); p = start + size; vm = trace->data; @@ -451,7 +445,7 @@ njs_regexp_match_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, trace = trace->next; p = trace->handler(trace, td, p); - njs_vm_throw_exception(vm, start, p - start); + njs_exception_internal_error(vm, (const char *) start, NULL); return p; } diff --git a/njs/njs_variable.c b/njs/njs_variable.c index 70bd2623..e33fc5a6 100644 --- a/njs/njs_variable.c +++ b/njs/njs_variable.c @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -156,9 +157,9 @@ njs_variable_add(njs_vm_t *vm, njs_parser_t *parser, njs_variable_type_t type) /* ret == NXT_DECLINED. */ - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "SyntaxError: Identifier \"%.*s\" has already been declared", - (int) lhq.key.length, lhq.key.start); + njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" " + "has already been declared", + (int) lhq.key.length, lhq.key.start); return NULL; } @@ -342,8 +343,8 @@ njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node) index = (index >> NJS_SCOPE_SHIFT) + 1; if (index > 255 || vs.scope->argument_closures == 0) { - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "InternalError: too many argument closures"); + njs_exception_internal_error(vm, "too many argument closures", + NULL); return NULL; } @@ -405,9 +406,8 @@ njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node) not_found: - nxt_alert(&vm->trace, NXT_LEVEL_ERROR, - "ReferenceError: \"%.*s\" is not defined", - (int) vs.lhq.key.length, vs.lhq.key.start); + njs_parser_ref_error(vm, vm->parser, "\"%.*s\" is not defined", + (int) vs.lhq.key.length, vs.lhq.key.start); return NULL; } diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 5fee478d..c4de887a 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -3454,25 +3454,6 @@ njs_value_string_copy(njs_vm_t *vm, nxt_str_t *retval, njs_value_t *value, } -void -njs_vm_throw_exception(njs_vm_t *vm, const u_char *buf, uint32_t size) -{ - int32_t length; - njs_value_t *value; - - value = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t)); - - if (nxt_fast_path(value != NULL)) { - vm->exception = value; - - length = nxt_utf8_length(buf, size); - length = (length >= 0) ? length : 0; - - (void) njs_string_new(vm, value, buf, size, length); - } -} - - static njs_ret_t njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame) { diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 1fcd18c1..00c96a1f 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -931,8 +931,6 @@ struct njs_vm_s { njs_native_frame_t *top_frame; njs_frame_t *active_frame; - const njs_value_t *exception; - nxt_lvlhsh_t externals_hash; nxt_lvlhsh_t variables_hash; nxt_lvlhsh_t values_hash; @@ -962,7 +960,6 @@ struct njs_vm_s { * with the generic type NJS_OBJECT_INTERNAL_ERROR but its own prototype * object NJS_PROTOTYPE_MEMORY_ERROR. */ - njs_value_t memory_error; njs_object_t memory_error_object; nxt_array_t *code; /* of njs_vm_code_t */ @@ -1146,8 +1143,6 @@ njs_ret_t njs_value_to_ext_string(njs_vm_t *vm, nxt_str_t *dst, const njs_value_t *src); void njs_number_set(njs_value_t *value, double num); -void njs_vm_throw_exception(njs_vm_t *vm, const u_char *buf, uint32_t size); - nxt_int_t njs_builtin_objects_create(njs_vm_t *vm); nxt_int_t njs_builtin_objects_clone(njs_vm_t *vm); nxt_int_t njs_builtin_match_native_function(njs_vm_t *vm, diff --git a/njs/njscript.c b/njs/njscript.c index e5925320..e2a56277 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -24,6 +24,7 @@ #include #include #include +#include static nxt_int_t njs_vm_init(njs_vm_t *vm); @@ -198,6 +199,8 @@ njs_vm_create(njs_vm_opt_t *options) if (nxt_slow_path(ret != NXT_OK)) { return NULL; } + + vm->retval = njs_value_void; } } @@ -246,6 +249,10 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) parser->code_size = sizeof(njs_vmcode_stop_t); parser->scope_offset = NJS_INDEX_GLOBAL_OFFSET; + if (vm->backtrace != NULL) { + nxt_array_reset(vm->backtrace); + } + node = njs_parser(vm, parser, prev); if (nxt_slow_path(node == NULL)) { goto fail; @@ -264,10 +271,6 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) */ vm->code = NULL; - if (vm->backtrace != NULL) { - nxt_array_reset(vm->backtrace); - } - ret = njs_generate_scope(vm, parser, node); if (nxt_slow_path(ret != NXT_OK)) { goto fail; @@ -334,6 +337,8 @@ njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external) goto fail; } + nvm->retval = njs_value_void; + return nvm; } @@ -402,8 +407,6 @@ njs_vm_init(njs_vm_t *vm) vm->backtrace = backtrace; } - vm->retval = njs_value_void; - vm->trace.level = NXT_LEVEL_TRACE; vm->trace.size = 2048; vm->trace.handler = njs_parser_trace_handler; @@ -464,6 +467,10 @@ njs_vm_run(njs_vm_t *vm) nxt_thread_log_debug("RUN:"); + if (vm->backtrace != NULL) { + nxt_array_reset(vm->backtrace); + } + ret = njs_vmcode_interpreter(vm); if (nxt_slow_path(ret == NXT_AGAIN)) { @@ -515,18 +522,76 @@ njs_vm_return_string(njs_vm_t *vm, u_char *start, size_t size) nxt_int_t njs_vm_retval(njs_vm_t *vm, nxt_str_t *retval) { - return njs_value_to_ext_string(vm, retval, &vm->retval); -} + u_char *p, *start; + size_t len; + nxt_int_t ret; + nxt_uint_t i; + nxt_array_t *backtrace; + njs_backtrace_entry_t *be; + if (vm->top_frame == NULL) { + /* An exception was thrown during compilation. */ + + njs_vm_init(vm); + } + + ret = njs_value_to_ext_string(vm, retval, &vm->retval); + + if (ret != NXT_OK) { + /* retval evaluation threw an exception. */ -nxt_int_t -njs_vm_exception(njs_vm_t *vm, nxt_str_t *retval) -{ - if (vm->top_frame != NULL) { vm->top_frame->trap_tries = 0; + + ret = njs_value_to_ext_string(vm, retval, &vm->retval); + if (ret != NXT_OK) { + return ret; + } + } + + backtrace = njs_vm_backtrace(vm); + + if (backtrace != NULL) { + + len = retval->length + 1; + + be = backtrace->start; + + for (i = 0; i < backtrace->items; i++) { + if (be[i].line != 0) { + len += sizeof(" at (:)\n") - 1 + 10 + be[i].name.length; + + } else { + len += sizeof(" at (native)\n") - 1 + be[i].name.length; + } + } + + p = nxt_mem_cache_alloc(vm->mem_cache_pool, len); + if (p == NULL) { + return NXT_ERROR; + } + + start = p; + + p = nxt_cpymem(p, retval->start, retval->length); + *p++ = '\n'; + + for (i = 0; i < backtrace->items; i++) { + if (be[i].line != 0) { + p += sprintf((char *) p, " at %.*s (:%u)\n", + (int) be[i].name.length, be[i].name.start, + be[i].line); + + } else { + p += sprintf((char *) p, " at %.*s (native)\n", + (int) be[i].name.length, be[i].name.start); + } + } + + retval->start = start; + retval->length = p - retval->start; } - return njs_value_to_ext_string(vm, retval, vm->exception); + return NXT_OK; } diff --git a/njs/njscript.h b/njs/njscript.h index bd3b376f..a76585a4 100644 --- a/njs/njscript.h +++ b/njs/njscript.h @@ -110,7 +110,6 @@ NXT_EXPORT njs_function_t *njs_vm_function(njs_vm_t *vm, nxt_str_t *name); NXT_EXPORT njs_ret_t njs_vm_return_string(njs_vm_t *vm, u_char *start, size_t size); NXT_EXPORT nxt_int_t njs_vm_retval(njs_vm_t *vm, nxt_str_t *retval); -NXT_EXPORT nxt_int_t njs_vm_exception(njs_vm_t *vm, nxt_str_t *retval); NXT_EXPORT nxt_array_t *njs_vm_backtrace(njs_vm_t *vm); NXT_EXPORT void njs_disassembler(njs_vm_t *vm); diff --git a/njs/test/njs_benchmark.c b/njs/test/njs_benchmark.c index 194bde37..89ef14be 100644 --- a/njs/test/njs_benchmark.c +++ b/njs/test/njs_benchmark.c @@ -111,13 +111,10 @@ njs_unit_test_benchmark(nxt_str_t *script, nxt_str_t *result, const char *msg, return NXT_ERROR; } - if (njs_vm_run(nvm) == NXT_OK) { - if (njs_vm_retval(nvm, &s) != NXT_OK) { - return NXT_ERROR; - } + (void) njs_vm_run(nvm); - } else { - njs_vm_exception(nvm, &s); + if (njs_vm_retval(nvm, &s) != NXT_OK) { + return NXT_ERROR; } success = nxt_strstr_eq(result, &s); diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp index 5081a7b9..0a4fae3f 100644 --- a/njs/test/njs_expect_test.exp +++ b/njs/test/njs_expect_test.exp @@ -166,3 +166,19 @@ njs_test { {"console.help()\r\n" "console.help()\r\nVM built-in objects:"} } + +# Exception in njs_vm_retval() +njs_test { + {"var o = { toString: function() { return [1] } }\r\n" + "undefined\r\n>> "} + {"o\r\n" + "TypeError"} +} + +# 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\n" + "JSON.parse(Error()\r\nSyntaxError: Unexpected token \"\" in 1"} +} diff --git a/njs/test/njs_interactive_test.c b/njs/test/njs_interactive_test.c index 8dd1dbd4..25030f4b 100644 --- a/njs/test/njs_interactive_test.c +++ b/njs/test/njs_interactive_test.c @@ -121,67 +121,71 @@ static njs_interactive_test_t njs_test[] = "function f(o) {return ff(o)}" ENTER "f({})" ENTER), nxt_string("TypeError\n" - "at ff (:1)\n" - "at f (:1)\n" - "at main\n") }, + " at ff (:1)\n" + " at f (:1)\n" + " at main (native)\n") }, { nxt_string("function ff(o) {return o.a.a}" ENTER "function f(o) {try {return ff(o)} " "finally {return o.a.a}}" ENTER "f({})" ENTER), nxt_string("TypeError\n" - "at f (:1)\n" - "at main\n") }, + " at f (:1)\n" + " at main (native)\n") }, { nxt_string("function f(ff, o) {return ff(o)}" ENTER "f(function (o) {return o.a.a}, {})" ENTER), nxt_string("TypeError\n" - "at anonymous (:1)\n" - "at f (:1)\n" - "at main\n") }, + " at anonymous (:1)\n" + " at f (:1)\n" + " at main (native)\n") }, { nxt_string("'str'.replace(/t/g," " function(m) {return m.a.a})" ENTER), nxt_string("TypeError\n" - "at anonymous (:1)\n" - "at String.prototype.replace\n" - "at main\n") }, + " at anonymous (:1)\n" + " at String.prototype.replace (native)\n" + " at main (native)\n") }, { nxt_string("function f(o) {return Object.keys(o)}" ENTER "f()" ENTER), nxt_string("TypeError\n" - "at Object.keys\n" - "at f (:1)\n" - "at main\n") }, + " at Object.keys (native)\n" + " at f (:1)\n" + " at main (native)\n") }, { nxt_string("String.fromCharCode(3.14)" ENTER), nxt_string("RangeError\n" - "at String.fromCharCode\n" - "at main\n") }, + " at String.fromCharCode (native)\n" + " at main (native)\n") }, { nxt_string("Math.log({}.a.a)" ENTER), nxt_string("TypeError\n" - "at Math.log\n" - "at main\n") }, + " at Math.log (native)\n" + " at main (native)\n") }, { nxt_string("function f(o) {function f_in(o) {return o.a.a};" " return f_in(o)}; f({})" ENTER), nxt_string("TypeError\n" - "at f_in (:1)\n" - "at f (:1)\n" - "at main\n") }, + " at f_in (:1)\n" + " at f (:1)\n" + " at main (native)\n") }, { nxt_string("function f(o) {var ff = function (o) {return o.a.a};" " return ff(o)}; f({})" ENTER), nxt_string("TypeError\n" - "at anonymous (:1)\n" - "at f (:1)\n" - "at main\n") }, + " at anonymous (:1)\n" + " at f (:1)\n" + " at main (native)\n") }, -}; + /* Exception in njs_vm_retval() */ + { nxt_string("var o = { toString: function() { return [1] } }" ENTER + "o" ENTER), + nxt_string("TypeError\n" + "at main\n") }, -static void njs_report_backtrace(nxt_array_t *backtrace, nxt_str_t *s); +}; static nxt_int_t @@ -193,7 +197,6 @@ njs_interactive_test(void) nxt_str_t s; nxt_uint_t i; nxt_bool_t success; - nxt_array_t *backtrace; njs_vm_opt_t options; nxt_mem_cache_pool_t *mcp; njs_interactive_test_t *test; @@ -242,18 +245,8 @@ njs_interactive_test(void) } } - if (ret == NXT_OK) { - if (njs_vm_retval(vm, &s) != NXT_OK) { - goto fail; - } - - } else { - njs_vm_exception(vm, &s); - - backtrace = njs_vm_backtrace(vm); - if (backtrace != NULL) { - njs_report_backtrace(backtrace, &s); - } + if (njs_vm_retval(vm, &s) != NXT_OK) { + return NXT_ERROR; } success = nxt_strstr_eq(&test->ret, &s); @@ -281,34 +274,6 @@ fail: } -static void -njs_report_backtrace(nxt_array_t *backtrace, nxt_str_t *s) -{ - char *p; - nxt_uint_t i; - njs_backtrace_entry_t *be; - - static char buf[4096]; - - p = buf + sprintf(buf, "%.*s\n", (int) s->length, s->start); - - be = backtrace->start; - for (i = 0; i < backtrace->items; i++) { - if (be[i].line != 0) { - p += sprintf(p, "at %.*s (:%d)\n", (int) be[i].name.length, - be[i].name.start, be[i].line); - - } else { - p += sprintf(p, "at %.*s\n", (int) be[i].name.length, - be[i].name.start); - } - } - - s->length = strlen(buf); - s->start = (u_char *) buf; -} - - int nxt_cdecl main(int argc, char **argv) { diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 6e06776e..f38d7dfa 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -4457,6 +4457,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("function f() { return 1\n 2 } f()"), nxt_string("1") }, + { nxt_string("function f() { return 1\n 2 } f()"), + nxt_string("1") }, + { nxt_string("function f(a) { if (a) return 'OK' } f(1)+f(0)"), nxt_string("OKundefined") }, @@ -4481,6 +4484,10 @@ static njs_unit_test_t njs_test[] = { nxt_string("function x(a) { while (a < 2) a++; return a + 1 } x(1) "), nxt_string("3") }, + { nxt_string("(function(){(function(){(function(){(function(){" + "(function(){(function(){(function(){})})})})})})})"), + nxt_string("SyntaxError: The maximum function nesting level is \"5\" in 1") }, + /* Recursive factorial. */ { nxt_string("function f(a) {" @@ -5420,7 +5427,7 @@ static njs_unit_test_t njs_test[] = /* Exceptions. */ { nxt_string("throw null"), - nxt_string("") }, + nxt_string("null") }, { nxt_string("var a; try { throw null } catch (e) { a = e } a"), nxt_string("null") }, @@ -5428,8 +5435,14 @@ static njs_unit_test_t njs_test[] = { nxt_string("var a; try { throw Error('e') } catch (e) { a = e.message } a"), nxt_string("e") }, + { nxt_string("var a; try { NaN.toString(NaN) } catch (e) { a = e.name } a"), + nxt_string("RangeError") }, + { nxt_string("try { throw null } catch (e) { throw e }"), - nxt_string("") }, + nxt_string("null") }, + + { nxt_string("try { throw Error('e') } catch (e) { throw Error(e.message + '2') }"), + nxt_string("Error: e2") }, { nxt_string("try { throw null } catch (null) { throw e }"), nxt_string("SyntaxError: Unexpected token \"null\" in 1") }, @@ -5447,27 +5460,27 @@ static njs_unit_test_t njs_test[] = { nxt_string("var a = 0; try { throw 3 }" "catch (e) { throw e + 1 } finally { a++ }"), - nxt_string("") }, + nxt_string("4") }, { nxt_string("var a = 0; try { throw 3 }" "catch (e) { a = e } finally { throw a }"), - nxt_string("") }, + nxt_string("3") }, { nxt_string("try { throw null } catch (e) { } finally { }"), nxt_string("undefined") }, { nxt_string("var a = 0; try { throw 3 }" "catch (e) { throw 4 } finally { throw a }"), - nxt_string("") }, + nxt_string("0") }, { nxt_string("var a = 0; try { a = 5 } finally { a++ } a"), nxt_string("6") }, { nxt_string("var a = 0; try { throw 5 } finally { a++ }"), - nxt_string("") }, + nxt_string("5") }, { nxt_string("var a = 0; try { a = 5 } finally { throw 7 }"), - nxt_string("") }, + nxt_string("7") }, { nxt_string("function f(a) {" " if (a > 1) return f(a - 1);" @@ -5494,6 +5507,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("var o = { toString: function() { return 'OK' } }; 'o:' + o"), nxt_string("o:OK") }, + { nxt_string("var o = { toString: function() { return [1] } }; o"), + nxt_string("TypeError") }, + { nxt_string("var o = { toString: function() { return [1] } }; 'o:' + o"), nxt_string("TypeError") }, @@ -7032,7 +7048,7 @@ static njs_unit_test_t njs_test[] = nxt_string("true") }, { nxt_string("eval()"), - nxt_string("") }, + nxt_string("InternalError: Not implemented") }, /* Math. */ @@ -9063,17 +9079,15 @@ njs_unit_test(nxt_bool_t disassemble) ret = njs_vm_run(nvm); - if (ret == NXT_OK) { - if (njs_vm_retval(nvm, &s) != NXT_OK) { - return NXT_ERROR; - } - - } else { - njs_vm_exception(nvm, &s); + if (njs_vm_retval(nvm, &s) != NXT_OK) { + return NXT_ERROR; } } else { - njs_vm_exception(vm, &s); + if (njs_vm_retval(vm, &s) != NXT_OK) { + return NXT_ERROR; + } + nvm = NULL; } -- 2.47.3