From: Dmitry Volyntsev Date: Tue, 29 Aug 2017 11:06:23 +0000 (+0300) Subject: Introduced function level backtrace. X-Git-Tag: 0.1.13~4 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/static/gitweb.js?a=commitdiff_plain;h=6e5428f7c47d5b5dbe886cb4a9b1be527291b036;p=njs.git Introduced function level backtrace. --- diff --git a/njs/njs.c b/njs/njs.c index 6106a52b..6da5884f 100644 --- a/njs/njs.c +++ b/njs/njs.c @@ -59,6 +59,7 @@ 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); @@ -93,6 +94,7 @@ main(int argc, char **argv) vm_options.mcp = mcp; vm_options.accumulative = 1; + vm_options.backtrace = 1; if (opts.interactive) { ret = njs_interactive_shell(&opts, &vm_options); @@ -150,9 +152,10 @@ njs_get_options(njs_opts_t *opts, int argc, char** argv) 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; + njs_vm_t *vm; + nxt_int_t ret; + nxt_str_t line, out; + nxt_array_t *backtrace; vm = njs_vm_create(vm_options); if (vm == NULL) { @@ -188,6 +191,11 @@ 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); } @@ -208,6 +216,7 @@ 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; @@ -287,6 +296,11 @@ 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); + } } return NXT_OK; @@ -329,6 +343,26 @@ njs_process_script(njs_vm_t *vm, njs_opts_t *opts, const nxt_str_t *script, } +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_builtin.c b/njs/njs_builtin.c index 1ce47075..b931856c 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -514,3 +514,136 @@ njs_builtin_completions(njs_vm_t *vm, size_t *size, const char **completions) return NXT_OK; } + + +nxt_int_t +njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, + nxt_str_t *name) +{ + char *buf; + size_t len; + nxt_str_t string; + nxt_uint_t i; + njs_object_t *objects; + njs_function_t *constructors; + njs_object_prop_t *prop; + nxt_lvlhsh_each_t lhe; + njs_object_prototype_t *prototypes; + + objects = vm->shared->objects; + + for (i = NJS_OBJECT_THIS; i < NJS_OBJECT_MAX; i++) { + if (object_init[i] == NULL) { + continue; + } + + nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&objects[i].shared_hash, &lhe); + + if (prop == NULL) { + break; + } + + if (!njs_is_function(&prop->value)) { + continue; + } + + if (function == prop->value.data.u.function) { + njs_string_get(&prop->name, &string); + len = object_init[i]->name.length + string.length + + sizeof("."); + + buf = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (buf == NULL) { + return NXT_ERROR; + } + + snprintf(buf, len, "%s.%s", object_init[i]->name.start, + string.start); + + name->length = len; + name->start = (u_char *) buf; + + return NXT_OK; + } + } + } + + prototypes = vm->shared->prototypes; + + for (i = NJS_PROTOTYPE_OBJECT; i < NJS_PROTOTYPE_MAX; i++) { + nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&prototypes[i].object.shared_hash, &lhe); + + if (prop == NULL) { + break; + } + + if (!njs_is_function(&prop->value)) { + continue; + } + + if (function == prop->value.data.u.function) { + njs_string_get(&prop->name, &string); + len = prototype_init[i]->name.length + string.length + + sizeof(".prototype."); + + buf = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (buf == NULL) { + return NXT_ERROR; + } + + snprintf(buf, len, "%s.prototype.%s", + prototype_init[i]->name.start, string.start); + + name->length = len; + name->start = (u_char *) buf; + + return NXT_OK; + } + } + } + + constructors = vm->shared->constructors; + + for (i = NJS_CONSTRUCTOR_OBJECT; i < NJS_CONSTRUCTOR_MAX; i++) { + nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&constructors[i].object.shared_hash, &lhe); + + if (prop == NULL) { + break; + } + + if (!njs_is_function(&prop->value)) { + continue; + } + + if (function == prop->value.data.u.function) { + njs_string_get(&prop->name, &string); + len = constructor_init[i]->name.length + string.length + + sizeof("."); + + buf = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (buf == NULL) { + return NXT_ERROR; + } + + snprintf(buf, len, "%s.%s", constructor_init[i]->name.start, + string.start); + + name->length = len; + name->start = (u_char *) buf; + + return NXT_OK; + } + } + } + + return NXT_DECLINED; +} diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 421f1444..db8b5c35 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -123,6 +123,9 @@ static nxt_noinline nxt_int_t njs_generator_node_index_release(njs_vm_t *vm, static nxt_noinline nxt_int_t njs_generator_index_release(njs_vm_t *vm, njs_parser_t *parser, njs_index_t index); +static nxt_int_t njs_generate_function_debug(njs_vm_t *vm, nxt_str_t *name, + njs_function_lambda_t *lambda, uint32_t line); + static const nxt_str_t no_label = { 0, NULL }; @@ -1609,6 +1612,13 @@ njs_generate_function(njs_vm_t *vm, njs_parser_t *parser, return ret; } + if (vm->debug != NULL) { + ret = njs_generate_function_debug(vm, NULL, lambda, node->token_line); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + njs_generate_code(parser, njs_vmcode_function_t, function); function->code.operation = njs_vmcode_function; function->code.operands = NJS_VMCODE_1OPERAND; @@ -1964,6 +1974,7 @@ static nxt_int_t njs_generate_function_declaration(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) { + nxt_int_t ret; njs_variable_t *var; njs_function_lambda_t *lambda; @@ -1974,7 +1985,17 @@ njs_generate_function_declaration(njs_vm_t *vm, njs_parser_t *parser, lambda = var->value.data.u.function->u.lambda; - return njs_generate_function_scope(vm, lambda, node); + ret = njs_generate_function_scope(vm, lambda, node); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + if (vm->debug != NULL) { + ret = njs_generate_function_debug(vm, &var->name, lambda, + node->token_line); + } + + return ret; } @@ -2628,3 +2649,28 @@ njs_generator_index_release(njs_vm_t *vm, njs_parser_t *parser, return NXT_ERROR; } + + +static nxt_int_t +njs_generate_function_debug(njs_vm_t *vm, nxt_str_t *name, + njs_function_lambda_t *lambda, uint32_t line) +{ + njs_function_debug_t *debug; + + debug = nxt_array_add(vm->debug, &njs_array_mem_proto, vm->mem_cache_pool); + if (nxt_slow_path(debug == NULL)) { + return NXT_ERROR; + } + + if (name != NULL) { + debug->name = *name; + + } else { + debug->name = no_label; + } + + debug->lambda = lambda; + debug->line = line; + + return NXT_OK; +} diff --git a/njs/njs_parser.c b/njs/njs_parser.c index feec0430..f2b06a07 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -465,6 +465,7 @@ njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser) } node->token = NJS_TOKEN_FUNCTION; + node->token_line = parser->lexer->token_line; token = njs_parser_token(parser); if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { @@ -532,6 +533,7 @@ njs_parser_function_expression(njs_vm_t *vm, njs_parser_t *parser) } node->token = NJS_TOKEN_FUNCTION_EXPRESSION; + node->token_line = parser->lexer->token_line; node->scope = parser->scope; parser->node = node; parser->code_size += sizeof(njs_vmcode_function_t); diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 6d4deaf3..7bc45db0 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -113,6 +113,8 @@ static njs_ret_t njs_object_value_to_string(njs_vm_t *vm, njs_value_t *value); static njs_ret_t njs_vmcode_value_to_string(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2); +static njs_ret_t njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame); + void njs_debug(njs_index_t index, njs_value_t *value); @@ -264,9 +266,20 @@ start: if (catch != NULL) { vm->current = catch; + + if (vm->debug != NULL) { + nxt_array_reset(vm->backtrace); + } + goto start; } + if (vm->debug != NULL + && njs_vm_add_backtrace_entry(vm, frame) != NXT_OK) + { + return NXT_ERROR; + } + previous = frame->native.previous; if (previous == NULL) { return NXT_ERROR; @@ -3433,6 +3446,70 @@ njs_vm_throw_exception(njs_vm_t *vm, const u_char *buf, uint32_t size) } +static njs_ret_t +njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame) +{ + nxt_int_t ret; + nxt_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; + + static const nxt_str_t entry_main = nxt_string("main"); + static const nxt_str_t entry_native = nxt_string("native"); + static const nxt_str_t entry_unknown = nxt_string("unknown"); + static const nxt_str_t entry_anonymous = nxt_string("anonymous"); + + native_frame = &frame->native; + function = native_frame->function; + + be = nxt_array_add(vm->backtrace, &njs_array_mem_proto, vm->mem_cache_pool); + if (nxt_slow_path(be == NULL)) { + return NXT_ERROR; + } + + be->line = 0; + + if (function == NULL) { + be->name = entry_main; + return NXT_OK; + } + + if (function->native) { + ret = njs_builtin_match_native_function(vm, function, &be->name); + if (ret != NXT_OK) { + be->name = entry_native; + } + + return NXT_OK; + } + + lambda = function->u.lambda; + debug_entry = vm->debug->start; + + for (i = 0; i < vm->debug->items; i++) { + if (lambda == debug_entry[i].lambda) { + if (debug_entry[i].name.length != 0) { + be->name = debug_entry[i].name; + + } else { + be->name = entry_anonymous; + } + + be->line = debug_entry[i].line; + + return NXT_OK; + } + } + + be->name = entry_unknown; + + return NXT_OK; +} + + void njs_debug(njs_index_t index, njs_value_t *value) { diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 14dfaef0..0f74dde9 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -853,6 +853,13 @@ typedef struct { } njs_vm_trap_t; +typedef struct { + uint32_t line; + nxt_str_t name; + njs_function_lambda_t *lambda; +} njs_function_debug_t; + + struct njs_vm_s { /* njs_vm_t must be aligned to njs_value_t due to scratch value. */ njs_value_t retval; @@ -903,6 +910,9 @@ struct njs_vm_s { nxt_trace_t trace; nxt_random_t random; + nxt_array_t *debug; + nxt_array_t *backtrace; + uint8_t trailer; /* 1 bit */ uint8_t accumulative; /* 1 bit */ }; @@ -1080,6 +1090,8 @@ 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, + njs_function_t *function, nxt_str_t *name); void *njs_lvlhsh_alloc(void *data, size_t size, nxt_uint_t nalloc); diff --git a/njs/njscript.c b/njs/njscript.c index 362dedea..c62864bc 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -106,6 +106,7 @@ njs_vm_create(njs_vm_opt_t *options) { njs_vm_t *vm; nxt_int_t ret; + nxt_array_t *debug; nxt_mem_cache_pool_t *mcp; njs_regexp_pattern_t *pattern; @@ -176,6 +177,17 @@ njs_vm_create(njs_vm_opt_t *options) vm->trailer = options->trailer; + if (options->backtrace) { + debug = nxt_array_create(4, sizeof(njs_function_debug_t), + &njs_array_mem_proto, + vm->mem_cache_pool); + if (nxt_slow_path(debug == NULL)) { + return NULL; + } + + vm->debug = debug; + } + vm->accumulative = options->accumulative; if (vm->accumulative) { ret = njs_vm_init(vm); @@ -248,6 +260,10 @@ 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 +350,7 @@ njs_vm_init(njs_vm_t *vm) u_char *values; nxt_int_t ret; njs_frame_t *frame; + nxt_array_t *backtrace; scope_size = vm->scope_size + NJS_INDEX_GLOBAL_OFFSET; @@ -371,6 +388,16 @@ njs_vm_init(njs_vm_t *vm) return NXT_ERROR; } + if (vm->debug != NULL) { + backtrace = nxt_array_create(4, sizeof(njs_backtrace_entry_t), + &njs_array_mem_proto, vm->mem_cache_pool); + if (nxt_slow_path(backtrace == NULL)) { + return NXT_ERROR; + } + + vm->backtrace = backtrace; + } + vm->retval = njs_value_void; vm->trace.level = NXT_LEVEL_TRACE; @@ -493,3 +520,14 @@ njs_vm_exception(njs_vm_t *vm, nxt_str_t *retval) { return njs_value_to_ext_string(vm, retval, vm->exception); } + + +nxt_array_t * +njs_vm_backtrace(njs_vm_t *vm) +{ + if (!nxt_array_is_empty(vm->backtrace)) { + return vm->backtrace; + } + + return NULL; +} diff --git a/njs/njscript.h b/njs/njscript.h index 7268b1cb..45b48527 100644 --- a/njs/njscript.h +++ b/njs/njscript.h @@ -72,9 +72,16 @@ typedef struct { uint8_t trailer; /* 1 bit */ uint8_t accumulative; /* 1 bit */ + uint8_t backtrace; /* 1 bit */ } njs_vm_opt_t; +typedef struct { + nxt_str_t name; + uint32_t line; +} njs_backtrace_entry_t; + + #define NJS_OK NXT_OK #define NJS_ERROR NXT_ERROR #define NJS_AGAIN NXT_AGAIN @@ -103,6 +110,7 @@ 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_interactive_test.c b/njs/test/njs_interactive_test.c index d0ca9f98..8dd1dbd4 100644 --- a/njs/test/njs_interactive_test.c +++ b/njs/test/njs_interactive_test.c @@ -109,18 +109,81 @@ static njs_interactive_test_t njs_test[] = "2 + 2" ENTER), nxt_string("4") }, + { nxt_string("function ff(o) {return o.a.a}" ENTER + "function f(o) {try {return ff(o)} " + "finally {return 1}}" ENTER + "f({})" ENTER), + nxt_string("1") }, + + /* Backtraces */ + { nxt_string("function ff(o) {return o.a.a}" ENTER "function f(o) {return ff(o)}" ENTER "f({})" ENTER), - nxt_string("TypeError") }, + nxt_string("TypeError\n" + "at ff (:1)\n" + "at f (:1)\n" + "at main\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") }, { nxt_string("function f(ff, o) {return ff(o)}" ENTER "f(function (o) {return o.a.a}, {})" ENTER), - nxt_string("TypeError") }, + nxt_string("TypeError\n" + "at anonymous (:1)\n" + "at f (:1)\n" + "at main\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") }, + + { 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") }, + + { nxt_string("String.fromCharCode(3.14)" ENTER), + nxt_string("RangeError\n" + "at String.fromCharCode\n" + "at main\n") }, + + { nxt_string("Math.log({}.a.a)" ENTER), + nxt_string("TypeError\n" + "at Math.log\n" + "at main\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") }, + + { 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") }, }; +static void njs_report_backtrace(nxt_array_t *backtrace, nxt_str_t *s); + + static nxt_int_t njs_interactive_test(void) { @@ -130,6 +193,7 @@ 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; @@ -153,6 +217,7 @@ njs_interactive_test(void) options.mcp = mcp; options.accumulative = 1; + options.backtrace = 1; vm = njs_vm_create(&options); if (vm == NULL) { @@ -184,6 +249,11 @@ njs_interactive_test(void) } else { njs_vm_exception(vm, &s); + + backtrace = njs_vm_backtrace(vm); + if (backtrace != NULL) { + njs_report_backtrace(backtrace, &s); + } } success = nxt_strstr_eq(&test->ret, &s); @@ -211,6 +281,34 @@ 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) {