Files
mruby-mruby/mrbgems/mruby-method/src/method.c
T
dearblue 4c196dcdaa Reorganize mcall() in mruby-method.
Use `mrb_exec_irep()`. If possible, re-entry into the VM will be suppressed.

Note that due to the effect of being a tail-call, the backtrace of `Method#call` will be lost, and it will look as if the target method was called directly.

This change fixes the problem of infinite loops when redefining methods that make block calls using `mruby-method`.

```console
% bin/mruby -e 'mm = method(:proc); define_method(:proc, ->(*a, &b) { mm.call(*a, &b) }); p proc { 1 }'
trace (most recent call last):
        [257] -e:1
        [256] -e:1:in proc
        [255] -e:1:in proc
        ...SNIP...
        [1] -e:1:in proc
-e:1:in proc: stack level too deep (SystemStackError)
```
2021-04-03 21:20:59 +09:00

547 lines
16 KiB
C

#include "mruby.h"
#include "mruby/array.h"
#include "mruby/class.h"
#include "mruby/variable.h"
#include "mruby/proc.h"
#include "mruby/string.h"
#include "mruby/presym.h"
mrb_noreturn void mrb_method_missing(mrb_state *mrb, mrb_sym name, mrb_value self, mrb_value args);
mrb_value mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook);
static mrb_value
args_shift(mrb_state *mrb)
{
mrb_value *argv = mrb->c->ci->stack + 1;
if (mrb->c->ci->argc > 0) {
mrb_value obj = argv[0];
memmove(argv, argv + 1, (mrb->c->ci->argc + 1 /* block */ - 1 /* first value */) * sizeof(mrb_value));
mrb->c->ci->argc--;
return obj;
}
else if (mrb->c->ci->argc < 0 && RARRAY_LEN(*argv) > 0) {
return mrb_ary_shift(mrb, *argv);
}
else {
mrb_argnum_error(mrb, 0, 1, -1);
return mrb_undef_value(); /* not reached */
}
}
static void
args_unshift(mrb_state *mrb, mrb_value obj)
{
mrb_value *argv = mrb->c->ci->stack + 1;
if (mrb->c->ci->argc >= 0) {
mrb_value block = argv[mrb->c->ci->argc];
argv[0] = mrb_ary_new_from_values(mrb, mrb->c->ci->argc, argv);
argv[1] = block;
mrb->c->ci->argc = -1;
}
mrb_ary_unshift(mrb, *argv, obj);
}
static struct RProc*
method_missing_prepare(mrb_state *mrb, mrb_sym *mid, mrb_value recv, struct RClass **tc)
{
const mrb_sym id_method_missing = MRB_SYM(method_missing);
if (*mid == id_method_missing) {
method_missing: ;
int argc = mrb->c->ci->argc;
mrb_value *argv = mrb->c->ci->stack + 1;
mrb_value args = (argc < 0) ? argv[0] : mrb_ary_new_from_values(mrb, argc, argv);
mrb_method_missing(mrb, *mid, recv, args);
}
*tc = mrb_class(mrb, recv);
mrb_method_t m = mrb_method_search_vm(mrb, tc, id_method_missing);
if (MRB_METHOD_UNDEF_P(m)) {
goto method_missing;
}
struct RProc *proc;
if (MRB_METHOD_FUNC_P(m)) {
proc = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m));
MRB_PROC_SET_TARGET_CLASS(proc, *tc);
}
else {
proc = MRB_METHOD_PROC(m);
}
args_unshift(mrb, mrb_symbol_value(*mid));
*mid = id_method_missing;
return proc;
}
static struct RObject *
method_object_alloc(mrb_state *mrb, struct RClass *mclass)
{
return (struct RObject*)mrb_obj_alloc(mrb, MRB_TT_OBJECT, mclass);
}
static struct RProc*
method_extract_proc(mrb_state *mrb, mrb_value self)
{
mrb_value obj = mrb_iv_get(mrb, self, MRB_SYM(_proc));
if (mrb_nil_p(obj)) {
return NULL;
}
else {
mrb_check_type(mrb, obj, MRB_TT_PROC);
return mrb_proc_ptr(obj);
}
}
static mrb_value
method_extract_receiver(mrb_state *mrb, mrb_value self)
{
return mrb_iv_get(mrb, self, MRB_SYM(_recv));
}
static mrb_sym
method_extract_mid(mrb_state *mrb, mrb_value self)
{
mrb_value obj = mrb_iv_get(mrb, self, MRB_SYM(_name));
mrb_check_type(mrb, obj, MRB_TT_SYMBOL);
return mrb_symbol(obj);
}
static struct RClass*
method_extract_owner(mrb_state *mrb, mrb_value self)
{
mrb_value obj = mrb_iv_get(mrb, self, MRB_SYM(_owner));
switch (mrb_type(obj)) {
case MRB_TT_CLASS:
case MRB_TT_MODULE:
case MRB_TT_SCLASS:
break;
default:
mrb_raise(mrb, E_TYPE_ERROR, "not class/module as owner of method object");
}
return mrb_class_ptr(obj);
}
static void
bind_check(mrb_state *mrb, mrb_value recv, mrb_value owner)
{
if (!mrb_module_p(owner) &&
mrb_class_ptr(owner) != mrb_obj_class(mrb, recv) &&
!mrb_obj_is_kind_of(mrb, recv, mrb_class_ptr(owner))) {
if (mrb_sclass_p(owner)) {
mrb_raise(mrb, E_TYPE_ERROR, "singleton method called for a different object");
} else {
mrb_raisef(mrb, E_TYPE_ERROR, "bind argument must be an instance of %v", owner);
}
}
}
static mrb_value
unbound_method_bind(mrb_state *mrb, mrb_value self)
{
struct RObject *me;
mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner));
mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name));
mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc));
mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass));
mrb_value recv = mrb_get_arg1(mrb);
bind_check(mrb, recv, owner);
me = method_object_alloc(mrb, mrb_class_get_id(mrb, MRB_SYM(Method)));
mrb_obj_iv_set(mrb, me, MRB_SYM(_owner), owner);
mrb_obj_iv_set(mrb, me, MRB_SYM(_recv), recv);
mrb_obj_iv_set(mrb, me, MRB_SYM(_name), name);
mrb_obj_iv_set(mrb, me, MRB_SYM(_proc), proc);
mrb_obj_iv_set(mrb, me, MRB_SYM(_klass), klass);
return mrb_obj_value(me);
}
#define IV_GET(value, name) mrb_iv_get(mrb, value, name)
static mrb_value
method_eql(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
mrb_value receiver, orig_proc, other_proc;
struct RClass *owner, *klass;
struct RProc *orig_rproc, *other_rproc;
if (!mrb_obj_is_instance_of(mrb, other, mrb_class(mrb, self)))
return mrb_false_value();
if (mrb_class(mrb, self) != mrb_class(mrb, other))
return mrb_false_value();
klass = mrb_class_ptr(IV_GET(self, MRB_SYM(_klass)));
if (klass != mrb_class_ptr(IV_GET(other, MRB_SYM(_klass))))
return mrb_false_value();
owner = mrb_class_ptr(IV_GET(self, MRB_SYM(_owner)));
if (owner != mrb_class_ptr(IV_GET(other, MRB_SYM(_owner))))
return mrb_false_value();
receiver = IV_GET(self, MRB_SYM(_recv));
if (!mrb_obj_equal(mrb, receiver, IV_GET(other, MRB_SYM(_recv))))
return mrb_false_value();
orig_proc = IV_GET(self, MRB_SYM(_proc));
other_proc = IV_GET(other, MRB_SYM(_proc));
if (mrb_nil_p(orig_proc) && mrb_nil_p(other_proc)) {
if (mrb_symbol(IV_GET(self, MRB_SYM(_name))) == mrb_symbol(IV_GET(other, MRB_SYM(_name))))
return mrb_true_value();
else
return mrb_false_value();
}
if (mrb_nil_p(orig_proc))
return mrb_false_value();
if (mrb_nil_p(other_proc))
return mrb_false_value();
orig_rproc = mrb_proc_ptr(orig_proc);
other_rproc = mrb_proc_ptr(other_proc);
if (MRB_PROC_CFUNC_P(orig_rproc)) {
if (!MRB_PROC_CFUNC_P(other_rproc))
return mrb_false_value();
if (orig_rproc->body.func != other_rproc->body.func)
return mrb_false_value();
}
else {
if (MRB_PROC_CFUNC_P(other_rproc))
return mrb_false_value();
if (orig_rproc->body.irep != other_rproc->body.irep)
return mrb_false_value();
}
return mrb_true_value();
}
#undef IV_GET
static mrb_value
mcall(mrb_state *mrb, mrb_value self, mrb_value recv)
{
struct RProc *proc = method_extract_proc(mrb, self);
mrb_sym mid = method_extract_mid(mrb, self);
struct RClass *tc = method_extract_owner(mrb, self);
if (mrb_undef_p(recv)) {
recv = method_extract_receiver(mrb, self);
}
else {
bind_check(mrb, recv, mrb_obj_value(tc));
}
if (!proc) {
proc = method_missing_prepare(mrb, &mid, recv, &tc);
}
mrb->c->ci->mid = mid;
mrb->c->ci->u.target_class = tc;
return mrb_exec_irep(mrb, recv, proc, NULL);
}
static mrb_value
method_call(mrb_state *mrb, mrb_value self)
{
return mcall(mrb, self, mrb_undef_value());
}
static mrb_value
method_bcall(mrb_state *mrb, mrb_value self)
{
mrb_value recv = args_shift(mrb);
mrb_gc_protect(mrb, recv);
return mcall(mrb, self, recv);
}
static mrb_value
method_unbind(mrb_state *mrb, mrb_value self)
{
struct RObject *ume;
mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner));
mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name));
mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc));
mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass));
ume = method_object_alloc(mrb, mrb_class_get_id(mrb, MRB_SYM(UnboundMethod)));
mrb_obj_iv_set(mrb, ume, MRB_SYM(_owner), owner);
mrb_obj_iv_set(mrb, ume, MRB_SYM(_recv), mrb_nil_value());
mrb_obj_iv_set(mrb, ume, MRB_SYM(_name), name);
mrb_obj_iv_set(mrb, ume, MRB_SYM(_proc), proc);
mrb_obj_iv_set(mrb, ume, MRB_SYM(_klass), klass);
return mrb_obj_value(ume);
}
static struct RProc *
method_search_vm(mrb_state *mrb, struct RClass **cp, mrb_sym mid)
{
mrb_method_t m = mrb_method_search_vm(mrb, cp, mid);
if (MRB_METHOD_UNDEF_P(m))
return NULL;
if (MRB_METHOD_PROC_P(m))
return MRB_METHOD_PROC(m);
return mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m));
}
static mrb_value
method_super_method(mrb_state *mrb, mrb_value self)
{
mrb_value recv = mrb_iv_get(mrb, self, MRB_SYM(_recv));
mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass));
mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner));
mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name));
struct RClass *super, *rklass;
struct RProc *proc;
struct RObject *me;
switch (mrb_type(klass)) {
case MRB_TT_SCLASS:
super = mrb_class_ptr(klass)->super->super;
break;
case MRB_TT_ICLASS:
super = mrb_class_ptr(klass)->super;
break;
default:
super = mrb_class_ptr(owner)->super;
break;
}
proc = method_search_vm(mrb, &super, mrb_symbol(name));
if (!proc)
return mrb_nil_value();
rklass = super;
while (super->tt == MRB_TT_ICLASS)
super = super->c;
me = method_object_alloc(mrb, mrb_obj_class(mrb, self));
mrb_obj_iv_set(mrb, me, MRB_SYM(_owner), mrb_obj_value(super));
mrb_obj_iv_set(mrb, me, MRB_SYM(_recv), recv);
mrb_obj_iv_set(mrb, me, MRB_SYM(_name), name);
mrb_obj_iv_set(mrb, me, MRB_SYM(_proc), mrb_obj_value(proc));
mrb_obj_iv_set(mrb, me, MRB_SYM(_klass), mrb_obj_value(rklass));
return mrb_obj_value(me);
}
static mrb_value
method_arity(mrb_state *mrb, mrb_value self)
{
mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc));
mrb_int arity = mrb_nil_p(proc) ? -1 : mrb_proc_arity(mrb_proc_ptr(proc));
return mrb_fixnum_value(arity);
}
static mrb_value
method_source_location(mrb_state *mrb, mrb_value self)
{
mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc));
struct RProc *rproc;
struct RClass *orig;
mrb_value ret;
if (mrb_nil_p(proc))
return mrb_nil_value();
rproc = mrb_proc_ptr(proc);
orig = rproc->c;
rproc->c = mrb->proc_class;
ret = mrb_funcall_id(mrb, proc, MRB_SYM(source_location), 0);
rproc->c = orig;
return ret;
}
static mrb_value
method_parameters(mrb_state *mrb, mrb_value self)
{
mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc));
struct RProc *rproc;
struct RClass *orig;
mrb_value ret;
if (mrb_nil_p(proc)) {
mrb_value rest = mrb_symbol_value(MRB_SYM(rest));
mrb_value arest = mrb_ary_new_from_values(mrb, 1, &rest);
return mrb_ary_new_from_values(mrb, 1, &arest);
}
rproc = mrb_proc_ptr(proc);
orig = rproc->c;
rproc->c = mrb->proc_class;
ret = mrb_funcall_id(mrb, proc, MRB_SYM(parameters), 0);
rproc->c = orig;
return ret;
}
static mrb_value
method_to_s(mrb_state *mrb, mrb_value self)
{
mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner));
mrb_value klass = mrb_iv_get(mrb, self, MRB_SYM(_klass));
mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name));
mrb_value str = mrb_str_new_lit(mrb, "#<");
struct RClass *rklass;
mrb_str_cat_cstr(mrb, str, mrb_obj_classname(mrb, self));
mrb_str_cat_lit(mrb, str, ": ");
rklass = mrb_class_ptr(klass);
if (mrb_class_ptr(owner) == rklass) {
mrb_str_concat(mrb, str, owner);
mrb_str_cat_lit(mrb, str, "#");
mrb_str_concat(mrb, str, name);
}
else {
mrb_str_cat_cstr(mrb, str, mrb_class_name(mrb, rklass));
mrb_str_cat_lit(mrb, str, "(");
mrb_str_concat(mrb, str, owner);
mrb_str_cat_lit(mrb, str, ")#");
mrb_str_concat(mrb, str, name);
}
mrb_str_cat_lit(mrb, str, ">");
return str;
}
static void
mrb_search_method_owner(mrb_state *mrb, struct RClass *c, mrb_value obj, mrb_sym name, struct RClass **owner, struct RProc **proc, mrb_bool unbound)
{
mrb_value ret;
*owner = c;
*proc = method_search_vm(mrb, owner, name);
if (!*proc) {
if (unbound) {
goto name_error;
}
if (!mrb_respond_to(mrb, obj, MRB_SYM_Q(respond_to_missing))) {
goto name_error;
}
ret = mrb_funcall_id(mrb, obj, MRB_SYM_Q(respond_to_missing), 2, mrb_symbol_value(name), mrb_true_value());
if (!mrb_test(ret)) {
goto name_error;
}
*owner = c;
}
while ((*owner)->tt == MRB_TT_ICLASS)
*owner = (*owner)->c;
return;
name_error:
mrb_raisef(mrb, E_NAME_ERROR, "undefined method '%n' for class '%C'", name, c);
}
static mrb_value
mrb_kernel_method(mrb_state *mrb, mrb_value self)
{
struct RClass *owner;
struct RProc *proc;
struct RObject *me;
mrb_sym name;
mrb_get_args(mrb, "n", &name);
mrb_search_method_owner(mrb, mrb_class(mrb, self), self, name, &owner, &proc, FALSE);
me = method_object_alloc(mrb, mrb_class_get_id(mrb, MRB_SYM(Method)));
mrb_obj_iv_set(mrb, me, MRB_SYM(_owner), mrb_obj_value(owner));
mrb_obj_iv_set(mrb, me, MRB_SYM(_recv), self);
mrb_obj_iv_set(mrb, me, MRB_SYM(_name), mrb_symbol_value(name));
mrb_obj_iv_set(mrb, me, MRB_SYM(_proc), proc ? mrb_obj_value(proc) : mrb_nil_value());
mrb_obj_iv_set(mrb, me, MRB_SYM(_klass), mrb_obj_value(mrb_class(mrb, self)));
return mrb_obj_value(me);
}
static mrb_value
mrb_module_instance_method(mrb_state *mrb, mrb_value self)
{
struct RClass *owner;
struct RProc *proc;
struct RObject *ume;
mrb_sym name;
mrb_get_args(mrb, "n", &name);
mrb_search_method_owner(mrb, mrb_class_ptr(self), self, name, &owner, &proc, TRUE);
ume = method_object_alloc(mrb, mrb_class_get_id(mrb, MRB_SYM(UnboundMethod)));
mrb_obj_iv_set(mrb, ume, MRB_SYM(_owner), mrb_obj_value(owner));
mrb_obj_iv_set(mrb, ume, MRB_SYM(_recv), mrb_nil_value());
mrb_obj_iv_set(mrb, ume, MRB_SYM(_name), mrb_symbol_value(name));
mrb_obj_iv_set(mrb, ume, MRB_SYM(_proc), proc ? mrb_obj_value(proc) : mrb_nil_value());
mrb_obj_iv_set(mrb, ume, MRB_SYM(_klass), self);
return mrb_obj_value(ume);
}
static mrb_value
method_owner(mrb_state *mrb, mrb_value self)
{
return mrb_iv_get(mrb, self, MRB_SYM(_owner));
}
static mrb_value
method_receiver(mrb_state *mrb, mrb_value self)
{
return mrb_iv_get(mrb, self, MRB_SYM(_recv));
}
static mrb_value
method_name(mrb_state *mrb, mrb_value self)
{
return mrb_iv_get(mrb, self, MRB_SYM(_name));
}
void
mrb_mruby_method_gem_init(mrb_state* mrb)
{
struct RClass *unbound_method = mrb_define_class_id(mrb, MRB_SYM(UnboundMethod), mrb->object_class);
struct RClass *method = mrb_define_class_id(mrb, MRB_SYM(Method), mrb->object_class);
mrb_undef_class_method(mrb, unbound_method, "new");
mrb_define_method(mrb, unbound_method, "bind", unbound_method_bind, MRB_ARGS_REQ(1));
mrb_define_method(mrb, unbound_method, "super_method", method_super_method, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "==", method_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, unbound_method, "eql?", method_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, unbound_method, "to_s", method_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "inspect", method_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "arity", method_arity, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "source_location", method_source_location, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "parameters", method_parameters, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "bind_call", method_bcall, MRB_ARGS_REQ(1)|MRB_ARGS_ANY());
mrb_define_method(mrb, unbound_method, "owner", method_owner, MRB_ARGS_NONE());
mrb_define_method(mrb, unbound_method, "name", method_name, MRB_ARGS_NONE());
mrb_undef_class_method(mrb, method, "new");
mrb_define_method(mrb, method, "==", method_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, method, "eql?", method_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, method, "to_s", method_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "inspect", method_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "call", method_call, MRB_ARGS_ANY());
mrb_define_method(mrb, method, "[]", method_call, MRB_ARGS_ANY());
mrb_define_method(mrb, method, "unbind", method_unbind, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "super_method", method_super_method, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "arity", method_arity, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "source_location", method_source_location, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "parameters", method_parameters, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "owner", method_owner, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "receiver", method_receiver, MRB_ARGS_NONE());
mrb_define_method(mrb, method, "name", method_name, MRB_ARGS_NONE());
mrb_define_method(mrb, mrb->kernel_module, "method", mrb_kernel_method, MRB_ARGS_REQ(1));
mrb_define_method(mrb, mrb->module_class, "instance_method", mrb_module_instance_method, MRB_ARGS_REQ(1));
}
void
mrb_mruby_method_gem_final(mrb_state* mrb)
{
}