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;