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/error.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 8 deletions(-) (limited to 'src/error.c') diff --git a/src/error.c b/src/error.c index 15a969d93..14e4ab4d3 100644 --- a/src/error.c +++ b/src/error.c @@ -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 */ -- cgit v1.2.3