From 668b12e7566282f6c0165252e2b2f5432964020a Mon Sep 17 00:00:00 2001 From: dearblue Date: Wed, 24 Nov 2021 23:34:31 +0900 Subject: Check more `MRB_ARGS_NONE()` The `__id__` method implemented in the C function has `MRB_ARGS_NONE()` specified, but it is also effective in the following cases. ```ruby p nil.__id__ opts: 1 rescue p :a p nil.method(:__id__).call 1 rescue p :b p nil.method(:__id__).call opts: 1 rescue p :c p nil.method(:__id__).to_proc.call 1 rescue p :d p nil.method(:__id__).to_proc.call opts: 1 rescue p :e p nil.method(:__id__).unbind.bind_call nil, 1 rescue p :f p nil.method(:__id__).unbind.bind_call nil, opts: 1 rescue p :g p nil.__send__ :__id__, 1 rescue p :h p nil.__send__ :__id__, opts: 1 rescue p :i ``` After applying this patch, all items will output symbols in the same way as CRuby. For this purpose, add `MRB_PROC_NOARG` to `struct RProc::flags`. --- include/mruby/proc.h | 2 ++ mrbgems/mruby-method/src/method.c | 7 ++++++- mrbgems/mruby-method/test/method.rb | 9 +++++++++ src/vm.c | 28 +++++++++++++++++++++++++++- test/t/argumenterror.rb | 5 +++++ test/t/kernel.rb | 3 +++ 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/include/mruby/proc.h b/include/mruby/proc.h index 31f40d379..1ba485783 100644 --- a/include/mruby/proc.h +++ b/include/mruby/proc.h @@ -83,6 +83,8 @@ struct RProc { } while (0) #define MRB_PROC_SCOPE 2048 #define MRB_PROC_SCOPE_P(p) (((p)->flags & MRB_PROC_SCOPE) != 0) +#define MRB_PROC_NOARG 4096 /* for MRB_PROC_CFUNC_FL, it would be something like MRB_ARGS_NONE() or MRB_METHOD_NOARG_FL */ +#define MRB_PROC_NOARG_P(p) (((p)->flags & MRB_PROC_NOARG) != 0) #define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v))) diff --git a/mrbgems/mruby-method/src/method.c b/mrbgems/mruby-method/src/method.c index b8a55e618..18fcaa03a 100644 --- a/mrbgems/mruby-method/src/method.c +++ b/mrbgems/mruby-method/src/method.c @@ -300,7 +300,12 @@ method_search_vm(mrb_state *mrb, struct RClass **cp, mrb_sym mid) return NULL; if (MRB_METHOD_PROC_P(m)) return MRB_METHOD_PROC(m); - return mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); + + struct RProc *proc = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); + if (MRB_METHOD_NOARG_P(m)) { + proc->flags |= MRB_PROC_NOARG; + } + return proc; } static mrb_value diff --git a/mrbgems/mruby-method/test/method.rb b/mrbgems/mruby-method/test/method.rb index ba4d05e3c..9089c96d6 100644 --- a/mrbgems/mruby-method/test/method.rb +++ b/mrbgems/mruby-method/test/method.rb @@ -98,6 +98,9 @@ assert 'Method#call' do }.new assert_raise(LocalJumpError) { i.method(:bar).call } assert_equal 3, i.method(:bar).call { |i| i } + + assert_raise(ArgumentError) { nil.method(:__id__).call nil, 1 } + assert_raise(ArgumentError) { nil.method(:__id__).call nil, opts: 1 } end assert 'Method#call for regression' do @@ -218,6 +221,9 @@ assert 'Method#to_proc' do assert_equal values, o.method(:baz).to_proc.call(1, 2, 3, 4, 5, 6, **{ u: 7, v: 8, s: 9, t: 10 }, &blk) assert_equal values, o.method(:baz).to_proc.call(*[1, 2, 3, 4, 5, 6], u: 7, v: 8, s: 9, t: 10, &blk) assert_equal values, o.method(:baz).to_proc.call(*[1, 2, 3, 4, 5, 6], **{ u: 7, v: 8, s: 9, t: 10 }, &blk) + + assert_raise(ArgumentError) { nil.method(:__id__).to_proc.call nil, 1 } + assert_raise(ArgumentError) { nil.method(:__id__).to_proc.call nil, opts: 1 } end assert 'to_s' do @@ -472,4 +478,7 @@ assert 'UnboundMethod#bind_call' do assert_equal values, m.bind_call(o, *[1, 2, 3, 4, 5, 6], u: 7, v: 8, s: 9, t: 10, &blk) assert_equal values, m.bind_call(o, *[1, 2, 3, 4, 5, 6], **{ u: 7, v: 8, s: 9, t: 10 }, &blk) assert_raise(ArgumentError) { m.bind_call } + + assert_raise(ArgumentError) { BasicObject.instance_method(:__id__).bind_call nil, 1 } + assert_raise(ArgumentError) { BasicObject.instance_method(:__id__).bind_call nil, opts: 1 } end diff --git a/src/vm.c b/src/vm.c index dabf12240..65af8dcc0 100644 --- a/src/vm.c +++ b/src/vm.c @@ -541,6 +541,18 @@ mrb_funcall_argv(mrb_state *mrb, mrb_value self, mrb_sym mid, mrb_int argc, cons return mrb_funcall_with_block(mrb, self, mid, argc, argv, mrb_nil_value()); } +static void +check_method_noarg(mrb_state *mrb, const mrb_callinfo *ci) +{ + int argc = ci->n == CALL_MAXARGS ? RARRAY_LEN(ci->stack[1]) : ci->n; + if (ci->nk != 0 && !mrb_hash_empty_p(mrb, ci->stack[ci->n == CALL_MAXARGS ? 2 : ci->n + 1])) { + argc++; + } + if (argc > 0) { + mrb_argnum_error(mrb, argc, 0, 0); + } +} + static mrb_value exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p) { @@ -550,6 +562,9 @@ exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p) ci->stack[0] = self; mrb_vm_ci_proc_set(ci, p); if (MRB_PROC_CFUNC_P(p)) { + if (MRB_PROC_NOARG_P(p)) { + check_method_noarg(mrb, ci); + } return MRB_PROC_CFUNC(p)(mrb, self); } nregs = p->body.irep->nregs; @@ -577,6 +592,9 @@ mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p) else { mrb_value ret; if (MRB_PROC_CFUNC_P(p)) { + if (MRB_PROC_NOARG_P(p)) { + check_method_noarg(mrb, ci); + } cipush(mrb, 0, CINFO_DIRECT, mrb_vm_ci_target_class(ci), p, ci->mid, ci->n|(ci->nk<<4)); ret = MRB_PROC_CFUNC(p)(mrb, self); cipop(mrb); @@ -665,6 +683,10 @@ mrb_f_send(mrb_state *mrb, mrb_value self) } if (MRB_METHOD_CFUNC_P(m)) { + if (MRB_METHOD_NOARG_P(m)) { + check_method_noarg(mrb, ci); + } + if (MRB_METHOD_PROC_P(m)) { mrb_vm_ci_proc_set(ci, MRB_METHOD_PROC(m)); } @@ -938,6 +960,9 @@ argnum_error(mrb_state *mrb, mrb_int num) argc = RARRAY_LEN(args); } } + if (argc == 0 && mrb->c->ci->nk != 0 && !mrb_hash_empty_p(mrb, mrb->c->ci->stack[1])) { + argc++; + } if (mrb->c->ci->mid) { str = mrb_format(mrb, "'%n': wrong number of arguments (%i for %i)", mrb->c->ci->mid, argc, num); @@ -1617,7 +1642,8 @@ RETRY_TRY_BLOCK: recv = p->body.func(mrb, recv); } else if (MRB_METHOD_NOARG_P(m) && - !(n == 0 || (n == CALL_MAXARGS && RARRAY_LEN(regs[1]) == 0))) { + (!(n == 0 || (n == CALL_MAXARGS && RARRAY_LEN(regs[1]) == 0)) || + !(nk == 0 || mrb_hash_empty_p(mrb, regs[n == CALL_MAXARGS ? 2 : n + 1])))) { argnum_error(mrb, 0); goto L_RAISE; } diff --git a/test/t/argumenterror.rb b/test/t/argumenterror.rb index 3dcb29a4b..3772f1b0f 100644 --- a/test/t/argumenterror.rb +++ b/test/t/argumenterror.rb @@ -30,3 +30,8 @@ assert("'wrong number of arguments' from mrb_get_args") do assert_argnum_error(1, 2){Object.const_set(:B)} assert_argnum_error(3, 2){Object.const_set(:C, 1, 2)} end + +assert('Call to MRB_ARGS_NONE method') do + assert_raise(ArgumentError) { nil.__id__ 1 } + assert_raise(ArgumentError) { nil.__id__ opts: 1 } +end diff --git a/test/t/kernel.rb b/test/t/kernel.rb index 0a70ec0d2..06551ae88 100644 --- a/test/t/kernel.rb +++ b/test/t/kernel.rb @@ -95,6 +95,9 @@ assert('Kernel#__send__', '15.3.1.3.4') do args = [:respond_to?, :nil?] assert_true __send__(*args) assert_equal [:respond_to?, :nil?], args + + assert_raise(ArgumentError) { nil.__send__(:__id__, 1) } + assert_raise(ArgumentError) { nil.__send__(:__id__, opts: 1) } end assert('Kernel#block_given?', '15.3.1.3.6') do -- cgit v1.2.3