Skip to content

Commit

Permalink
Support backtrace after method calls
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kou committed Dec 29, 2015
1 parent e132de9 commit a561bdb
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 31 deletions.
14 changes: 14 additions & 0 deletions include/mruby.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ struct mrb_context {

struct mrb_jmpbuf;

typedef struct {
const char *filename;
int lineno;
struct RClass *klass;
const char *sep;
mrb_sym method_id;
} mrb_backtrace_entry;

typedef void (*mrb_atexit_func)(struct mrb_state*);

typedef struct mrb_state {
Expand All @@ -125,6 +133,12 @@ typedef struct mrb_state {
struct mrb_context *root_c;

struct RObject *exc; /* exception */
struct {
struct RObject *exc;
int n;
int n_allocated;
mrb_backtrace_entry *entries;
} backtrace;
struct iv_tbl *globals; /* global variable table */

struct RObject *top_self;
Expand Down
220 changes: 212 additions & 8 deletions src/backtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
#include <mruby/error.h>
#include <mruby/numeric.h>

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;
Expand All @@ -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
Expand Down Expand Up @@ -89,15 +99,15 @@ 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;

if (ciidx >= mrb->c->ciend - mrb->c->cibase)
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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Loading

0 comments on commit a561bdb

Please sign in to comment.