From a561bdb25ff51809c5de63ab7083ebf25d37cda9 Mon Sep 17 00:00:00 2001 From: Kouhei Sutou Date: Tue, 29 Dec 2015 20:36:12 +0900 Subject: Support backtrace after method calls GitHub: fix #2902, #2917 The current implementation traverses stack to retrieve backtrace. But stack will be changed when some operations are occurred. It means that backtrace may be broken after some operations. This change (1) saves the minimum information to retrieve backtrace when exception is raised and (2) restores backtrace from the minimum information when backtrace is needed. It reduces overhead for creating backtrace Ruby objects. The space for the minimum information is reused by multiple exceptions. So memory allocation isn't occurred for each exception. --- src/backtrace.c | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 8 deletions(-) (limited to 'src/backtrace.c') diff --git a/src/backtrace.c b/src/backtrace.c index 80a5e2935..b581083df 100644 --- a/src/backtrace.c +++ b/src/backtrace.c @@ -14,6 +14,15 @@ #include #include +struct backtrace_location_raw { + int i; + int lineno; + const char *filename; + mrb_sym method_id; + const char *sep; + struct RClass *klass; +}; + struct backtrace_location { int i; int lineno; @@ -23,6 +32,7 @@ struct backtrace_location { const char *class_name; }; +typedef void (*each_backtrace_func)(mrb_state*, struct backtrace_location_raw*, void*); typedef void (*output_stream_func)(mrb_state*, struct backtrace_location*, void*); #ifndef MRB_DISABLE_STDIO @@ -89,7 +99,7 @@ get_backtrace_i(mrb_state *mrb, struct backtrace_location *loc, void *data) } static void -output_backtrace(mrb_state *mrb, mrb_int ciidx, mrb_code *pc0, output_stream_func func, void *data) +each_backtrace(mrb_state *mrb, mrb_int ciidx, mrb_code *pc0, each_backtrace_func func, void *data) { int i; @@ -97,7 +107,7 @@ output_backtrace(mrb_state *mrb, mrb_int ciidx, mrb_code *pc0, output_stream_fun ciidx = 10; /* ciidx is broken... */ for (i = ciidx; i >= 0; i--) { - struct backtrace_location loc; + struct backtrace_location_raw loc; mrb_callinfo *ci; mrb_irep *irep; mrb_code *pc; @@ -134,13 +144,40 @@ output_backtrace(mrb_state *mrb, mrb_int ciidx, mrb_code *pc0, output_stream_fun loc.filename = "(unknown)"; } - loc.method = mrb_sym2name(mrb, ci->mid); - loc.class_name = mrb_class_name(mrb, ci->proc->target_class); + loc.method_id = ci->mid; + loc.klass = ci->proc->target_class; loc.i = i; func(mrb, &loc, data); } } +struct output_backtrace_args { + output_stream_func func; + void *data; +}; + +static void +output_backtrace_i(mrb_state *mrb, struct backtrace_location_raw *loc_raw, void *data) +{ + struct backtrace_location loc; + struct output_backtrace_args *args = data; + + loc.i = loc_raw->i; + loc.lineno = loc_raw->lineno; + loc.filename = loc_raw->filename; + loc.method = mrb_sym2name(mrb, loc_raw->method_id); + loc.sep = loc_raw->sep; + loc.class_name = mrb_class_name(mrb, loc_raw->klass); + + args->func(mrb, &loc, args->data); +} + +static void +output_backtrace(mrb_state *mrb, mrb_int ciidx, mrb_code *pc0, output_stream_func func, void *data) +{ + each_backtrace(mrb, ciidx, pc0, output_backtrace_i, data); +} + static void exc_output_backtrace(mrb_state *mrb, struct RObject *exc, output_stream_func func, void *stream) { @@ -167,18 +204,76 @@ exc_output_backtrace(mrb_state *mrb, struct RObject *exc, output_stream_func fun #ifndef MRB_DISABLE_STDIO +static void +print_backtrace(mrb_state *mrb, mrb_value backtrace) +{ + int i, n; + FILE *stream = stderr; + + fprintf(stream, "trace:\n"); + + n = RARRAY_LEN(backtrace); + for (i = 0; i < n; i++) { + mrb_value entry = RARRAY_PTR(backtrace)[i]; + + fprintf(stream, "\t[%d] %.*s\n", i, RSTRING_LEN(entry), RSTRING_PTR(entry)); + } +} + +static void +print_backtrace_saved(mrb_state *mrb) +{ + int i; + FILE *stream = stderr; + + fprintf(stream, "trace:\n"); + for (i = 0; i < mrb->backtrace.n; i++) { + mrb_backtrace_entry *entry; + + entry = &(mrb->backtrace.entries[i]); + fprintf(stream, "\t[%d] %s:%d", i, entry->filename, entry->lineno); + + if (entry->method_id != 0) { + const char *method_name; + + method_name = mrb_sym2name(mrb, entry->method_id); + if (entry->klass) { + fprintf(stream, ":in %s%s%s", + mrb_class_name(mrb, entry->klass), + entry->sep, + method_name); + } + else { + fprintf(stream, ":in %s", method_name); + } + } + + fprintf(stream, "\n"); + } +} + MRB_API void mrb_print_backtrace(mrb_state *mrb) { - struct print_backtrace_args args; + mrb_value backtrace; if (!mrb->exc || mrb_obj_is_kind_of(mrb, mrb_obj_value(mrb->exc), E_SYSSTACK_ERROR)) { return; } - args.stream = stderr; - args.tracehead = TRUE; - exc_output_backtrace(mrb, mrb->exc, print_backtrace_i, (void*)&args); + backtrace = mrb_obj_iv_get(mrb, mrb->exc, mrb_intern_lit(mrb, "backtrace")); + if (!mrb_nil_p(backtrace)) { + print_backtrace(mrb, backtrace); + } + else if (mrb->backtrace.n > 0) { + print_backtrace_saved(mrb); + } + else { + struct print_backtrace_args args; + args.stream = stderr; + args.tracehead = TRUE; + exc_output_backtrace(mrb, mrb->exc, print_backtrace_i, (void*)&args); + } } #else @@ -215,3 +310,112 @@ mrb_get_backtrace(mrb_state *mrb) return ary; } + +void +mrb_free_backtrace(mrb_state *mrb) +{ + mrb->backtrace.exc = 0; + mrb->backtrace.n = 0; + mrb->backtrace.n_allocated = 0; + mrb_free(mrb, mrb->backtrace.entries); +} + +static void +save_backtrace_i(mrb_state *mrb, + struct backtrace_location_raw *loc_raw, + void *data) +{ + mrb_backtrace_entry *entry; + + if (loc_raw->i >= mrb->backtrace.n_allocated) { + int new_n_allocated; + if (mrb->backtrace.n_allocated == 0) { + new_n_allocated = 8; + } + else { + new_n_allocated = mrb->backtrace.n_allocated * 2; + } + mrb->backtrace.entries = + mrb_realloc(mrb, + mrb->backtrace.entries, + sizeof(mrb_backtrace_entry) * new_n_allocated); + mrb->backtrace.n_allocated = new_n_allocated; + } + + entry = &mrb->backtrace.entries[mrb->backtrace.n]; + entry->filename = loc_raw->filename; + entry->lineno = loc_raw->lineno; + entry->klass = loc_raw->klass; + entry->sep = loc_raw->sep; + entry->method_id = loc_raw->method_id; + + mrb->backtrace.n++; +} + +void +mrb_save_backtrace(mrb_state *mrb) +{ + mrb_value lastpc; + mrb_code *code; + mrb_int ciidx; + + mrb->backtrace.n = 0; + mrb->backtrace.exc = 0; + + if (!mrb->exc) + return; + + mrb->backtrace.exc = mrb->exc; + + lastpc = mrb_obj_iv_get(mrb, mrb->exc, mrb_intern_lit(mrb, "lastpc")); + if (mrb_nil_p(lastpc)) { + code = NULL; + } + else { + code = (mrb_code*)mrb_cptr(lastpc); + } + + ciidx = mrb_fixnum(mrb_obj_iv_get(mrb, mrb->exc, mrb_intern_lit(mrb, "ciidx"))); + + each_backtrace(mrb, ciidx, code, save_backtrace_i, NULL); +} + +mrb_value +mrb_restore_backtrace(mrb_state *mrb) +{ + int i; + mrb_value backtrace; + + backtrace = mrb_ary_new(mrb); + for (i = 0; i < mrb->backtrace.n; i++) { + int ai; + mrb_backtrace_entry *entry; + mrb_value mrb_entry; + + ai = mrb_gc_arena_save(mrb); + entry = &(mrb->backtrace.entries[i]); + + mrb_entry = mrb_str_new_cstr(mrb, entry->filename); + mrb_str_cat_lit(mrb, mrb_entry, ":"); + mrb_str_concat(mrb, mrb_entry, + mrb_fixnum_to_str(mrb, + mrb_fixnum_value(entry->lineno), + 10)); + if (entry->method_id != 0) { + mrb_str_cat_lit(mrb, mrb_entry, ":in "); + + if (entry->klass) { + mrb_str_cat_cstr(mrb, mrb_entry, mrb_class_name(mrb, entry->klass)); + mrb_str_cat_cstr(mrb, mrb_entry, entry->sep); + } + + mrb_str_cat_cstr(mrb, mrb_entry, mrb_sym2name(mrb, entry->method_id)); + } + + mrb_ary_push(mrb, backtrace, mrb_entry); + + mrb_gc_arena_restore(mrb, ai); + } + + return backtrace; +} -- cgit v1.2.3