From 232e07ad618deff7bb438aa30e86da29b17c37cf Mon Sep 17 00:00:00 2001 From: dearblue Date: Sat, 6 Feb 2021 21:22:59 +0900 Subject: Reimplement mruby-catch; ref #5321 When there is a corresponding tag, the `RBreak` object is used to make a global jump. Like CRuby, it can't be caught by `rescue`. It is also the same as CRuby that it can be canceled in the middle by `ensure`. ### How to find the corresponding tag with `throw` The called `catch` method remains in the call stack, and the tag also remains in the stack at that time. So it is possible to find the called location by searching the two. Note that no method can be given to the `proc` object specified in `RBreak`. Therefore, inside the `catch` method, the argument block is called in a seemingly meaningless closure. Also, as a countermeasure against `alias` etc., the `proc` object, which is the body of the `catch` method, is saved when mrbgem is initialized. --- mrbgems/mruby-catch/src/mruby-catch.c | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 mrbgems/mruby-catch/src/mruby-catch.c (limited to 'mrbgems/mruby-catch/src/mruby-catch.c') diff --git a/mrbgems/mruby-catch/src/mruby-catch.c b/mrbgems/mruby-catch/src/mruby-catch.c new file mode 100644 index 000000000..d0a50b821 --- /dev/null +++ b/mrbgems/mruby-catch/src/mruby-catch.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include + +#define ID_PRESERVED_CATCH MRB_SYM(__preserved_catch_proc) + +static const mrb_callinfo * +find_catcher(mrb_state *mrb, mrb_value tag) +{ + mrb_value pval = mrb_obj_iv_get(mrb, (struct RObject *)mrb->kernel_module, ID_PRESERVED_CATCH); + mrb_assert(mrb_proc_p(pval)); + const struct RProc *proc = mrb_proc_ptr(pval); + + const mrb_callinfo *ci = mrb->c->ci; + size_t n = ci - mrb->c->cibase; + ci--; + for (; n > 0; n--, ci--) { + const mrb_value *arg1 = ci->stack + 1; + if (ci->proc == proc && mrb_obj_eq(mrb, *arg1, tag)) { + return ci; + } + } + + return NULL; +} + +static mrb_value +mrb_f_throw(mrb_state *mrb, mrb_value self) +{ + mrb_value tag, obj; + mrb_get_args(mrb, "oo", &tag, &obj); + + const mrb_callinfo *ci = find_catcher(mrb, tag); + if (ci) { + struct RBreak *b = (struct RBreak *)mrb_obj_alloc(mrb, MRB_TT_BREAK, NULL); + mrb_break_value_set(b, obj); + mrb_break_proc_set(b, ci[2].proc); /* Back to the closure in `catch` method */ + mrb_exc_raise(mrb, mrb_obj_value(b)); + } + + return mrb_nil_value(); +} + +static mrb_value +mrb_s_preserve_catch(mrb_state *mrb, mrb_value self) +{ + mrb_method_t m = mrb_method_search(mrb, mrb->kernel_module, MRB_SYM(catch)); + mrb_assert(!MRB_METHOD_UNDEF_P(m)); + mrb_assert(!MRB_METHOD_CFUNC_P(m)); + mrb_obj_iv_set(mrb, (struct RObject *)mrb->kernel_module, ID_PRESERVED_CATCH, mrb_obj_value(MRB_METHOD_PROC(m))); + + mrb_remove_method(mrb, mrb_class(mrb, mrb_obj_value(mrb->kernel_module)), MRB_SYM(__preserve_catch_method)); + + return mrb_nil_value(); +} + +void +mrb_mruby_catch_gem_init(mrb_state *mrb) +{ + mrb_define_method(mrb, mrb->kernel_module, "__throw", mrb_f_throw, MRB_ARGS_REQ(2)); + mrb_define_class_method(mrb, mrb->kernel_module, "__preserve_catch_method", mrb_s_preserve_catch, MRB_ARGS_NONE()); +} + +void +mrb_mruby_catch_gem_final(mrb_state *mrb) +{ +} -- cgit v1.2.3 From c7809ca025b3b81dd6fb695f27bd5d0dfa3174af Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Wed, 10 Feb 2021 20:09:00 +0900 Subject: Refactor `mruby-catch`; ref #5328 - Move `#catch` definition to `mruby-catch.c` to avoid tweaking - Remove `#__preserve_catch_method` - Implement whole `#throw` method in C --- mrbgems/mruby-catch/mrblib/catch.rb | 14 ------ mrbgems/mruby-catch/src/mruby-catch.c | 90 ++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 31 deletions(-) (limited to 'mrbgems/mruby-catch/src/mruby-catch.c') diff --git a/mrbgems/mruby-catch/mrblib/catch.rb b/mrbgems/mruby-catch/mrblib/catch.rb index c69cd125e..8e5f59023 100644 --- a/mrbgems/mruby-catch/mrblib/catch.rb +++ b/mrbgems/mruby-catch/mrblib/catch.rb @@ -6,17 +6,3 @@ class UncaughtThrowError < ArgumentError super("uncaught throw #{tag.inspect}") end end - -module Kernel - def catch(tag=Object.new, &block) - # A double closure is required to make the nested `catch` distinguishable - # and because `break` goes back to `proc->upper`. - -> { -> { block.call(tag) }.call }.call - end - def throw(tag, val=nil) - __throw(tag, val) - raise UncaughtThrowError.new(tag, val) - end - - __preserve_catch_method -end diff --git a/mrbgems/mruby-catch/src/mruby-catch.c b/mrbgems/mruby-catch/src/mruby-catch.c index d0a50b821..6db8dfc49 100644 --- a/mrbgems/mruby-catch/src/mruby-catch.c +++ b/mrbgems/mruby-catch/src/mruby-catch.c @@ -3,8 +3,64 @@ #include #include #include +#include #include + +static const mrb_sym catch_syms_3[1] = {MRB_SYM(call),}; +static const mrb_code catch_iseq_3[18] = { + OP_ENTER, 0x00, 0x00, 0x00, + OP_GETUPVAR, 0x02, 0x02, 0x01, + OP_GETUPVAR, 0x03, 0x01, 0x01, + OP_SEND, 0x02, 0x00, 0x01, + OP_RETURN, 0x02,}; +static const mrb_irep catch_irep_3 = { + 2,5,0, + MRB_IREP_STATIC,catch_iseq_3, + NULL,catch_syms_3,NULL, + NULL, + NULL, + 18,0,1,0,0 +}; +static const mrb_irep *catch_reps_2[1] = { + &catch_irep_3, +}; +static const mrb_code catch_iseq_2[13] = { + OP_ENTER, 0x00, 0x00, 0x00, + OP_LAMBDA, 0x02, 0x00, + OP_SEND, 0x02, 0x00, 0x00, + OP_RETURN, 0x02,}; +static const mrb_irep catch_irep_2 = { + 2,4,0, + MRB_IREP_STATIC,catch_iseq_2, + NULL,catch_syms_3,catch_reps_2, + NULL, + NULL, + 13,0,1,1,0 +}; +static const mrb_irep *catch_reps_1[1] = { + &catch_irep_2, +}; +static const mrb_sym catch_syms_1[3] = {MRB_SYM(Object), MRB_SYM(new), MRB_SYM(call),}; +static const mrb_code catch_iseq_1[29] = { + OP_ENTER, 0x00, 0x20, 0x01, + OP_JMP, 0x00, 0x03, + OP_JMP, 0x00, 0x0a, + OP_GETCONST, 0x03, 0x00, + OP_SEND, 0x03, 0x01, 0x00, + OP_MOVE, 0x01, 0x03, + OP_LAMBDA, 0x03, 0x00, + OP_SEND, 0x03, 0x02, 0x00, + OP_RETURN, 0x03,}; +static const mrb_irep catch_irep = { + 3,5,0, + MRB_IREP_STATIC,catch_iseq_1, + NULL,catch_syms_1,catch_reps_1, + NULL, + NULL, + 29,0,3,1,0 +}; + #define ID_PRESERVED_CATCH MRB_SYM(__preserved_catch_proc) static const mrb_callinfo * @@ -31,7 +87,9 @@ static mrb_value mrb_f_throw(mrb_state *mrb, mrb_value self) { mrb_value tag, obj; - mrb_get_args(mrb, "oo", &tag, &obj); + if (mrb_get_args(mrb, "o|o", &tag, &obj) == 1) { + obj = mrb_nil_value(); + } const mrb_callinfo *ci = find_catcher(mrb, tag); if (ci) { @@ -40,28 +98,26 @@ mrb_f_throw(mrb_state *mrb, mrb_value self) mrb_break_proc_set(b, ci[2].proc); /* Back to the closure in `catch` method */ mrb_exc_raise(mrb, mrb_obj_value(b)); } - - return mrb_nil_value(); -} - -static mrb_value -mrb_s_preserve_catch(mrb_state *mrb, mrb_value self) -{ - mrb_method_t m = mrb_method_search(mrb, mrb->kernel_module, MRB_SYM(catch)); - mrb_assert(!MRB_METHOD_UNDEF_P(m)); - mrb_assert(!MRB_METHOD_CFUNC_P(m)); - mrb_obj_iv_set(mrb, (struct RObject *)mrb->kernel_module, ID_PRESERVED_CATCH, mrb_obj_value(MRB_METHOD_PROC(m))); - - mrb_remove_method(mrb, mrb_class(mrb, mrb_obj_value(mrb->kernel_module)), MRB_SYM(__preserve_catch_method)); - + else { + mrb_value argv[2] = {tag, obj}; + mrb_exc_raise(mrb, mrb_obj_new(mrb, mrb_exc_get_id(mrb, MRB_ERROR_SYM(UncaughtThrowError)), 2, argv)); + } + /* not reached */ return mrb_nil_value(); } void mrb_mruby_catch_gem_init(mrb_state *mrb) { - mrb_define_method(mrb, mrb->kernel_module, "__throw", mrb_f_throw, MRB_ARGS_REQ(2)); - mrb_define_class_method(mrb, mrb->kernel_module, "__preserve_catch_method", mrb_s_preserve_catch, MRB_ARGS_NONE()); + struct RProc *p; + mrb_method_t m; + + p = mrb_proc_new(mrb, &catch_irep); + MRB_METHOD_FROM_PROC(m, p); + mrb_define_method_raw(mrb, mrb->kernel_module, MRB_SYM(catch), m); + mrb_obj_iv_set(mrb, (struct RObject *)mrb->kernel_module, ID_PRESERVED_CATCH, mrb_obj_value(p)); + + mrb_define_method(mrb, mrb->kernel_module, "throw", mrb_f_throw, MRB_ARGS_ARG(1,1)); } void -- cgit v1.2.3