/* ** backtrace.c - ** ** See Copyright Notice in mruby.h */ #include #include #include #include #include #include #include #include #include #include struct backtrace_location { int lineno; const char *filename; mrb_sym method_id; }; typedef void (*each_backtrace_func)(mrb_state*, int i, struct backtrace_location*, void*); static const mrb_data_type bt_type = { "Backtrace", mrb_free }; static void each_backtrace(mrb_state *mrb, mrb_int ciidx, mrb_code *pc0, each_backtrace_func func, void *data) { int i, j; if (ciidx >= mrb->c->ciend - mrb->c->cibase) ciidx = 10; /* ciidx is broken... */ for (i=ciidx, j=0; i >= 0; i--,j++) { struct backtrace_location loc; mrb_callinfo *ci; mrb_irep *irep; mrb_code *pc; ci = &mrb->c->cibase[i]; if (!ci->proc) continue; if (MRB_PROC_CFUNC_P(ci->proc)) continue; irep = ci->proc->body.irep; if (!irep) continue; if (mrb->c->cibase[i].err) { pc = mrb->c->cibase[i].err; } else if (i+1 <= ciidx) { pc = mrb->c->cibase[i+1].pc - 1; } else { pc = pc0; } loc.filename = mrb_debug_get_filename(irep, (uint32_t)(pc - irep->iseq)); loc.lineno = mrb_debug_get_line(irep, (uint32_t)(pc - irep->iseq)); if (loc.lineno == -1) continue; if (!loc.filename) { loc.filename = "(unknown)"; } loc.method_id = ci->mid; func(mrb, j, &loc, data); } } /* mrb_print_backtrace function to retrieve backtrace information from the exception. note that if you call method after the exception, call stack will be overwritten. So invoke these functions just after detecting exceptions. */ #ifndef MRB_DISABLE_STDIO static void print_backtrace(mrb_state *mrb, mrb_value backtrace) { int i, n; FILE *stream = stderr; if (!mrb_array_p(backtrace)) return; fprintf(stream, "trace:\n"); n = RARRAY_LEN(backtrace); for (i = 0; i < n; i++) { mrb_value entry = RARRAY_PTR(backtrace)[i]; if (mrb_string_p(entry)) { fprintf(stream, "\t[%d] %.*s\n", i, (int)RSTRING_LEN(entry), RSTRING_PTR(entry)); } } } static void print_packed_backtrace(mrb_state *mrb, mrb_value packed) { FILE *stream = stderr; struct backtrace_location *bt; mrb_int n, i; bt = (struct backtrace_location*)mrb_data_check_get_ptr(mrb, packed, &bt_type); if (bt == NULL) { mrb_raise(mrb, E_RUNTIME_ERROR, "broken backtrace"); } n = (mrb_int)RDATA(packed)->flags; fprintf(stream, "trace:\n"); for (i = 0; i < n; i++) { int ai = mrb_gc_arena_save(mrb); struct backtrace_location *entry = &bt[i]; if (entry->filename == NULL) continue; fprintf(stream, "\t[%d] %s:%d", (int)i, entry->filename, entry->lineno); if (entry->method_id != 0) { const char *method_name; method_name = mrb_sym2name(mrb, entry->method_id); fprintf(stream, ":in %s", method_name); mrb_gc_arena_restore(mrb, ai); } fprintf(stream, "\n"); } } MRB_API void mrb_print_backtrace(mrb_state *mrb) { mrb_value backtrace; if (!mrb->exc) { return; } backtrace = mrb_obj_iv_get(mrb, mrb->exc, mrb_intern_lit(mrb, "backtrace")); if (mrb_nil_p(backtrace)) return; if (mrb_array_p(backtrace)) { print_backtrace(mrb, backtrace); } else { print_packed_backtrace(mrb, backtrace); } } #else MRB_API void mrb_print_backtrace(mrb_state *mrb) { } #endif static void pack_backtrace_i(mrb_state *mrb, int i, struct backtrace_location *loc, void *data) { struct backtrace_location *entry = (struct backtrace_location*)data; entry[i] = *loc; } static mrb_value packed_backtrace(mrb_state *mrb) { struct RData *backtrace; mrb_int ciidx = mrb->c->ci - mrb->c->cibase; mrb_int len = (ciidx+1)*sizeof(struct backtrace_location); void *ptr; ptr = mrb_malloc(mrb, len); memset(ptr, 0, len); backtrace = mrb_data_object_alloc(mrb, NULL, ptr, &bt_type); backtrace->flags = (unsigned int)ciidx+1; each_backtrace(mrb, ciidx, mrb->c->ci->pc, pack_backtrace_i, ptr); return mrb_obj_value(backtrace); } void mrb_keep_backtrace(mrb_state *mrb, mrb_value exc) { mrb_value backtrace; int ai = mrb_gc_arena_save(mrb); backtrace = packed_backtrace(mrb); mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "backtrace"), backtrace); mrb_gc_arena_restore(mrb, ai); } mrb_value mrb_unpack_backtrace(mrb_state *mrb, mrb_value backtrace) { struct backtrace_location *bt; mrb_int n, i; if (mrb_nil_p(backtrace)) return mrb_ary_new_capa(mrb, 0); if (mrb_array_p(backtrace)) return backtrace; bt = (struct backtrace_location*)mrb_data_check_get_ptr(mrb, backtrace, &bt_type); if (bt == NULL) { mrb_raise(mrb, E_RUNTIME_ERROR, "broken backtrace"); } n = (mrb_int)RDATA(backtrace)->flags; backtrace = mrb_ary_new_capa(mrb, n); for (i = 0; i < n; i++) { int ai = mrb_gc_arena_save(mrb); struct backtrace_location *entry = &bt[i]; mrb_value btline; if (entry->filename == NULL) continue; btline = mrb_format(mrb, "%S:%S", mrb_str_new_cstr(mrb, entry->filename), mrb_fixnum_value(entry->lineno)); if (entry->method_id != 0) { mrb_str_cat_lit(mrb, btline, ":in "); mrb_str_cat_cstr(mrb, btline, mrb_sym2name(mrb, entry->method_id)); } mrb_ary_push(mrb, backtrace, btline); mrb_gc_arena_restore(mrb, ai); } return backtrace; } MRB_API mrb_value mrb_exc_backtrace(mrb_state *mrb, mrb_value exc) { mrb_sym attr_name; mrb_value backtrace; attr_name = mrb_intern_lit(mrb, "backtrace"); backtrace = mrb_iv_get(mrb, exc, attr_name); if (mrb_nil_p(backtrace) || mrb_array_p(backtrace)) { return backtrace; } backtrace = mrb_unpack_backtrace(mrb, backtrace); mrb_iv_set(mrb, exc, attr_name, backtrace); return backtrace; } MRB_API mrb_value mrb_get_backtrace(mrb_state *mrb) { return mrb_unpack_backtrace(mrb, packed_backtrace(mrb)); }