Commit ee3fa1be authored by Yukihiro "Matz" Matsumoto's avatar Yukihiro "Matz" Matsumoto

Merge pull request #3065 from kou/support-backtrace-after-method-calls

Support backtrace after method calls
parents e132de9e 0ebac028
......@@ -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 {
......@@ -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;
......
......@@ -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;
......@@ -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;
}
......@@ -174,6 +174,42 @@ exc_inspect(mrb_state *mrb, mrb_value exc)
return str;
}
void mrb_save_backtrace(mrb_state *mrb);
mrb_value mrb_restore_backtrace(mrb_state *mrb);
static mrb_value
exc_get_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)) {
if (mrb_obj_ptr(exc) == mrb->backtrace.exc && mrb->backtrace.n > 0) {
backtrace = mrb_restore_backtrace(mrb);
mrb->backtrace.n = 0;
mrb->backtrace.exc = 0;
}
else {
backtrace = mrb_exc_backtrace(mrb, exc);
}
mrb_iv_set(mrb, exc, attr_name, backtrace);
}
return backtrace;
}
static mrb_value
exc_set_backtrace(mrb_state *mrb, mrb_value exc)
{
mrb_value backtrace;
mrb_get_args(mrb, "o", &backtrace);
mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "backtrace"), backtrace);
return backtrace;
}
static void
exc_debug_info(mrb_state *mrb, struct RObject *exc)
......@@ -202,12 +238,52 @@ exc_debug_info(mrb_state *mrb, struct RObject *exc)
}
}
static void
set_backtrace(mrb_state *mrb, mrb_value info, mrb_value bt)
{
mrb_funcall(mrb, info, "set_backtrace", 1, bt);
}
static mrb_bool
have_backtrace(mrb_state *mrb, struct RObject *exc)
{
return !mrb_nil_p(mrb_obj_iv_get(mrb, exc, mrb_intern_lit(mrb, "backtrace")));
}
void
mrb_exc_set(mrb_state *mrb, mrb_value exc)
{
if (!mrb->gc.out_of_memory && mrb->backtrace.n > 0) {
mrb_value target_exc = mrb_nil_value();
if ((mrb->exc && !have_backtrace(mrb, mrb->exc))) {
target_exc = mrb_obj_value(mrb->exc);
}
else if (!mrb_nil_p(exc) && mrb_obj_ptr(exc) == mrb->backtrace.exc) {
target_exc = exc;
}
if (!mrb_nil_p(target_exc)) {
mrb_value backtrace;
backtrace = mrb_restore_backtrace(mrb);
set_backtrace(mrb, target_exc, backtrace);
}
}
mrb->backtrace.n = 0;
if (mrb_nil_p(exc)) {
mrb->exc = 0;
}
else {
mrb->exc = mrb_obj_ptr(exc);
}
}
MRB_API mrb_noreturn void
mrb_exc_raise(mrb_state *mrb, mrb_value exc)
{
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
if (!mrb->gc.out_of_memory) {
exc_debug_info(mrb, mrb->exc);
mrb_save_backtrace(mrb);
}
if (!mrb->jmp) {
mrb_p(mrb, exc);
......@@ -337,12 +413,6 @@ mrb_bug(mrb_state *mrb, const char *fmt, ...)
exit(EXIT_FAILURE);
}
static void
set_backtrace(mrb_state *mrb, mrb_value info, mrb_value bt)
{
mrb_funcall(mrb, info, "set_backtrace", 1, bt);
}
static mrb_value
make_exception(mrb_state *mrb, int argc, const mrb_value *argv, mrb_bool isstr)
{
......@@ -449,7 +519,8 @@ mrb_init_exception(mrb_state *mrb)
mrb_define_method(mrb, exception, "to_s", exc_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, exception, "message", exc_message, MRB_ARGS_NONE());
mrb_define_method(mrb, exception, "inspect", exc_inspect, MRB_ARGS_NONE());
mrb_define_method(mrb, exception, "backtrace", mrb_exc_backtrace, MRB_ARGS_NONE());
mrb_define_method(mrb, exception, "backtrace", exc_get_backtrace, MRB_ARGS_NONE());
mrb_define_method(mrb, exception, "set_backtrace", exc_set_backtrace, MRB_ARGS_REQ(1));
mrb->eStandardError_class = mrb_define_class(mrb, "StandardError", mrb->eException_class); /* 15.2.23 */
runtime_error = mrb_define_class(mrb, "RuntimeError", mrb->eStandardError_class); /* 15.2.28 */
......
......@@ -614,10 +614,12 @@ mrb_read_irep(mrb_state *mrb, const uint8_t *bin)
return read_irep(mrb, bin, flags);
}
void mrb_exc_set(mrb_state *mrb, mrb_value exc);
static void
irep_error(mrb_state *mrb)
{
mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_SCRIPT_ERROR, "irep load error"));
mrb_exc_set(mrb, mrb_exc_new_str_lit(mrb, E_SCRIPT_ERROR, "irep load error"));
}
MRB_API mrb_value
......
......@@ -215,6 +215,8 @@ mrb_str_pool(mrb_state *mrb, mrb_value str)
return mrb_obj_value(ns);
}
void mrb_free_backtrace(mrb_state *mrb);
MRB_API void
mrb_free_context(mrb_state *mrb, struct mrb_context *c)
{
......@@ -242,6 +244,7 @@ mrb_close(mrb_state *mrb)
/* free */
mrb_gc_free_gv(mrb);
mrb_free_backtrace(mrb);
mrb_free_context(mrb, mrb->root_c);
mrb_free_symtbl(mrb);
mrb_alloca_free(mrb);
......
......@@ -258,6 +258,8 @@ cipop(mrb_state *mrb)
c->ci--;
}
void mrb_exc_set(mrb_state *mrb, mrb_value exc);
static void
ecall(mrb_state *mrb, int i)
{
......@@ -669,7 +671,7 @@ localjump_error(mrb_state *mrb, localjump_error_kind kind)
mrb_str_cat(mrb, msg, lead, sizeof(lead) - 1);
mrb_str_cat(mrb, msg, kind_str[kind], kind_str_len[kind]);
exc = mrb_exc_new_str(mrb, E_LOCALJUMP_ERROR, msg);
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
}
static void
......@@ -688,7 +690,7 @@ argnum_error(mrb_state *mrb, mrb_int num)
mrb_fixnum_value(mrb->c->ci->argc), mrb_fixnum_value(num));
}
exc = mrb_exc_new_str(mrb, E_ARGUMENT_ERROR, str);
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
}
#define ERR_PC_SET(mrb, pc) mrb->c->ci->err = pc;
......@@ -1007,7 +1009,7 @@ RETRY_TRY_BLOCK:
CASE(OP_RAISE) {
/* A raise(R(A)) */
mrb->exc = mrb_obj_ptr(regs[GETARG_A(i)]);
mrb_exc_set(mrb, regs[GETARG_A(i)]);
goto L_RAISE;
}
......@@ -1241,7 +1243,7 @@ RETRY_TRY_BLOCK:
mrb_value exc;
exc = mrb_exc_new_str_lit(mrb, E_NOMETHOD_ERROR, "super called outside of method");
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
goto L_RAISE;
}
recv = regs[0];
......@@ -1331,7 +1333,7 @@ RETRY_TRY_BLOCK:
mrb_value exc;
exc = mrb_exc_new_str_lit(mrb, E_NOMETHOD_ERROR, "super called outside of method");
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
goto L_RAISE;
}
stack = e->stack + 1;
......@@ -1561,7 +1563,7 @@ RETRY_TRY_BLOCK:
}
if (mrb->c->prev->ci == mrb->c->prev->cibase) {
mrb_value exc = mrb_exc_new_str_lit(mrb, E_FIBER_ERROR, "double resume");
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
goto L_RAISE;
}
/* automatic yield at the end */
......@@ -2323,7 +2325,7 @@ RETRY_TRY_BLOCK:
/* A R(A) := target_class */
if (!mrb->c->ci->target_class) {
mrb_value exc = mrb_exc_new_str_lit(mrb, E_TYPE_ERROR, "no target class or module");
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
goto L_RAISE;
}
regs[GETARG_A(i)] = mrb_obj_value(mrb->c->ci->target_class);
......@@ -2381,7 +2383,7 @@ RETRY_TRY_BLOCK:
else {
exc = mrb_exc_new_str(mrb, E_LOCALJUMP_ERROR, msg);
}
mrb->exc = mrb_obj_ptr(exc);
mrb_exc_set(mrb, exc);
goto L_RAISE;
}
}
......
......@@ -373,12 +373,47 @@ assert('Raise in ensure') do
end
end
assert('Raise in rescue') do
assert_raise(ArgumentError) do
begin
raise "" # RuntimeError
rescue
raise ArgumentError
def backtrace_avaialble?
begin
raise "XXX"
rescue => exception
not exception.backtrace.empty?
end
end
assert('GC in rescue') do
skip "backtrace isn't avaialble" unless backtrace_avaialble?
line = nil
begin
[1].each do
[2].each do
[3].each do
line = __LINE__; raise "XXX"
end
end
end
rescue => exception
GC.start
assert_equal("#{__FILE__}:#{line}:in Object.call",
exception.backtrace.first)
end
end
assert('Method call in rescue') do
skip "backtrace isn't avaialble" unless backtrace_avaialble?
line = nil
begin
[1].each do
[2].each do
line = __LINE__; raise "XXX"
end
end
rescue => exception
[3].each do
end
assert_equal("#{__FILE__}:#{line}:in Object.call",
exception.backtrace.first)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment