diff --git a/include/mruby/internal.h b/include/mruby/internal.h index ea3014673..23374e29e 100644 --- a/include/mruby/internal.h +++ b/include/mruby/internal.h @@ -198,6 +198,7 @@ mrb_value mrb_obj_instance_eval(mrb_state*, mrb_value); mrb_value mrb_object_exec(mrb_state *mrb, mrb_value self, struct RClass *target_class); mrb_value mrb_mod_module_eval(mrb_state*, mrb_value); mrb_value mrb_f_send(mrb_state *mrb, mrb_value self); +mrb_value mrb_f_public_send(mrb_state *mrb, mrb_value self); #ifdef MRB_USE_BIGINT mrb_value mrb_bint_new_int(mrb_state *mrb, mrb_int x); diff --git a/mrbgems/mruby-metaprog/src/metaprog.c b/mrbgems/mruby-metaprog/src/metaprog.c index dc453dbfa..79fe6fd8c 100644 --- a/mrbgems/mruby-metaprog/src/metaprog.c +++ b/mrbgems/mruby-metaprog/src/metaprog.c @@ -712,6 +712,7 @@ mrb_mruby_metaprog_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, krn, MRB_SYM(singleton_methods), mrb_obj_singleton_methods_m, MRB_ARGS_OPT(1)); /* 15.3.1.3.45 */ mrb_define_method_id(mrb, krn, MRB_SYM(define_singleton_method), mod_define_singleton_method, MRB_ARGS_REQ(1)|MRB_ARGS_BLOCK()); mrb_define_method_id(mrb, krn, MRB_SYM(send), mrb_f_send, MRB_ARGS_REQ(1)|MRB_ARGS_REST()|MRB_ARGS_BLOCK()); /* 15.3.1.3.44 */ + mrb_define_method_id(mrb, krn, MRB_SYM(public_send), mrb_f_public_send, MRB_ARGS_REQ(1)|MRB_ARGS_REST()|MRB_ARGS_BLOCK()); mrb_define_method_id(mrb, mod, MRB_SYM(class_variables), mrb_mod_class_variables, MRB_ARGS_OPT(1)); /* 15.2.2.4.19 */ mrb_define_method_id(mrb, mod, MRB_SYM(remove_class_variable), mrb_mod_remove_cvar, MRB_ARGS_REQ(1)); /* 15.2.2.4.39 */ diff --git a/src/vm.c b/src/vm.c index 319ea5f16..3f3100797 100644 --- a/src/vm.c +++ b/src/vm.c @@ -858,27 +858,14 @@ mrb_object_exec(mrb_state *mrb, mrb_value self, struct RClass *target_class) return mrb_exec_irep(mrb, self, mrb_proc_ptr(blk)); } -/* 15.3.1.3.4 */ -/* 15.3.1.3.44 */ -/* - * call-seq: - * obj.send(symbol [, args...]) -> obj - * obj.__send__(symbol [, args...]) -> obj - * - * Invokes the method identified by _symbol_, passing it any - * arguments specified. You can use __send__ if the name - * +send+ clashes with an existing method in _obj_. - * - * class Klass - * def hello(*args) - * "Hello " + args.join(' ') - * end - * end - * k = Klass.new - * k.send :hello, "gentle", "readers" #=> "Hello gentle readers" - */ -mrb_value -mrb_f_send(mrb_state *mrb, mrb_value self) +static mrb_noreturn void +vis_error(mrb_state *mrb, mrb_sym mid, mrb_value args, mrb_value recv, mrb_bool priv) +{ + mrb_no_method_error(mrb, mid, args, "%s method '%n' called for %T", (priv ? "private" : "protected"), mid, recv); +} + +static mrb_value +send_method(mrb_state *mrb, mrb_value self, mrb_bool public) { mrb_callinfo *ci = mrb->c->ci; int n = ci->n; @@ -914,6 +901,22 @@ mrb_f_send(mrb_state *mrb, mrb_value self) goto funcall; } + if (public) { + mrb_bool priv = TRUE; + if (m.flags & MRB_METHOD_PRIVATE_FL) { + vis_err:; + if (n == 15) { + n = RARRAY_LEN(regs[0]) - 1; + regs = RARRAY_PTR(regs[0]); + } + vis_error(mrb, name, mrb_ary_new_from_values(mrb, n, regs+1), self, priv); + } + else if ((m.flags & MRB_METHOD_PROTECTED_FL) && mrb_obj_is_kind_of(mrb, self, ci->u.target_class)) { + priv = FALSE; + goto vis_err; + } + } + ci->mid = name; ci->u.target_class = c; /* remove first symbol from arguments */ @@ -950,6 +953,48 @@ mrb_f_send(mrb_state *mrb, mrb_value self) return exec_irep(mrb, self, p); } +/* 15.3.1.3.4 */ +/* 15.3.1.3.44 */ +/* + * call-seq: + * obj.send(symbol [, args...]) -> obj + * obj.__send__(symbol [, args...]) -> obj + * + * Invokes the method identified by _symbol_, passing it any + * arguments specified. You can use __send__ if the name + * +send+ clashes with an existing method in _obj_. + * + * class Klass + * def hello(*args) + * "Hello " + args.join(' ') + * end + * end + * k = Klass.new + * k.send :hello, "gentle", "readers" #=> "Hello gentle readers" + */ +mrb_value +mrb_f_send(mrb_state *mrb, mrb_value self) +{ + return send_method(mrb, self, FALSE); +} + +/* + * call-seq: + * obj.public_send(symbol [, args...]) -> obj + * + * Invokes the method identified by symbol, passing it any + * arguments specified. Unlike send, public_send calls public methods only. + * When the method is identified by a string, the string is converted to a + * symbol. + * + * 1.public_send(:puts, "hello") # causes NoMethodError + */ +mrb_value +mrb_f_public_send(mrb_state *mrb, mrb_value self) +{ + return send_method(mrb, self, TRUE); +} + static void check_block(mrb_state *mrb, mrb_value blk) { @@ -1911,13 +1956,15 @@ RETRY_TRY_BLOCK: ci->mid = mid; } if (insn == OP_SEND || insn == OP_SENDB) { + mrb_bool priv = TRUE; if (m.flags & MRB_METHOD_PRIVATE_FL) { + vis_err:; mrb_value args = (ci->n == 15) ? regs[1] : mrb_ary_new_from_values(mrb, ci->n, regs+1); - mrb_no_method_error(mrb, mid, args, "private method '%n' called for %T", mid, recv); + vis_error(mrb, mid, args, recv, priv); } else if ((m.flags & MRB_METHOD_PROTECTED_FL) && mrb_obj_is_kind_of(mrb, recv, ci->u.target_class)) { - mrb_value args = (ci->n == 15) ? regs[1] : mrb_ary_new_from_values(mrb, ci->n, regs+1); - mrb_no_method_error(mrb, mid, args, "protected method '%n' called for %T", mid, recv); + priv = FALSE; + goto vis_err; } } ci->cci = CINFO_NONE;