diff --git a/ChangeLog b/ChangeLog index 52591cfaec..412a7d5aba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Sun Feb 28 13:40:46 2016 Nobuyoshi Nakada + + * error.c (nometh_err_initialize): add private_call? parameter. + + * error.c (nometh_err_private_call_p): add private_call? method, + to tell if the exception raised in private form FCALL or VCALL. + [Feature #12043] + + * vm_eval.c (make_no_method_exception): append private_call? + argument. + + * vm_insnhelper.c (ci_missing_reason): copy FCALL flag. + Sun Feb 28 10:19:47 2016 Ryan T. Hosford * array.c (rb_ary_and): clarify that set intersection returns the diff --git a/error.c b/error.c index 204a9a6302..93cb6f03d6 100644 --- a/error.c +++ b/error.c @@ -697,6 +697,7 @@ static VALUE rb_eNOERROR; static ID id_new, id_cause, id_message, id_backtrace; static ID id_name, id_args, id_Errno, id_errno, id_i_path; static ID id_receiver, id_iseq, id_local_variables; +static ID id_private_call_p; extern ID ruby_static_id_status; #define id_bt idBt #define id_bt_locations idBt_locations @@ -1203,9 +1204,11 @@ name_err_local_variables(VALUE self) static VALUE nometh_err_initialize(int argc, VALUE *argv, VALUE self) { + VALUE priv = (argc > 3) && (--argc, RTEST(argv[argc])) ? Qtrue : Qfalse; VALUE args = (argc > 2) ? argv[--argc] : Qnil; name_err_initialize(argc, argv, self); rb_ivar_set(self, id_args, args); + rb_ivar_set(self, id_private_call_p, RTEST(priv) ? Qtrue : Qfalse); return self; } @@ -1392,6 +1395,12 @@ nometh_err_args(VALUE self) return rb_attr_get(self, id_args); } +static VALUE +nometh_err_private_call_p(VALUE self) +{ + return rb_attr_get(self, id_private_call_p); +} + void rb_invalid_str(const char *str, const char *type) { @@ -2019,6 +2028,7 @@ Init_Exception(void) rb_eNoMethodError = rb_define_class("NoMethodError", rb_eNameError); rb_define_method(rb_eNoMethodError, "initialize", nometh_err_initialize, -1); rb_define_method(rb_eNoMethodError, "args", nometh_err_args, 0); + rb_define_method(rb_eNoMethodError, "private_call?", nometh_err_private_call_p, 0); rb_eRuntimeError = rb_define_class("RuntimeError", rb_eStandardError); rb_eSecurityError = rb_define_class("SecurityError", rb_eException); @@ -2043,6 +2053,7 @@ Init_Exception(void) id_name = rb_intern_const("name"); id_args = rb_intern_const("args"); id_receiver = rb_intern_const("receiver"); + id_private_call_p = rb_intern_const("private_call?"); id_local_variables = rb_intern_const("local_variables"); id_Errno = rb_intern_const("Errno"); id_errno = rb_intern_const("errno"); diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 7c0674857a..262d27f080 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -762,6 +762,7 @@ end.join assert_equal(:foo, e.name) assert_equal([1, 2], e.args) assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) e = assert_raise(NoMethodError) { obj.instance_eval {foo(1, 2)} @@ -769,6 +770,7 @@ end.join assert_equal(:foo, e.name) assert_equal([1, 2], e.args) assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) end def test_name_error_info_local_variables @@ -787,6 +789,29 @@ end.join assert_equal(%i[a b c d e f g], e.local_variables.sort) end + def test_name_error_info_method_missing + obj = PrettyObject.new + def obj.method_missing(*) + super + end + + e = assert_raise(NoMethodError) { + obj.foo(1, 2) + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) + + e = assert_raise(NoMethodError) { + obj.instance_eval {foo(1, 2)} + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) + end + def test_name_error_info_parent_iseq_mark assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;') -> {require ARGV[0]}.call diff --git a/vm_core.h b/vm_core.h index 38de35636f..8b5e853d57 100644 --- a/vm_core.h +++ b/vm_core.h @@ -200,10 +200,11 @@ enum method_missing_reason { MISSING_NOENTRY = 0x00, MISSING_PRIVATE = 0x01, MISSING_PROTECTED = 0x02, - MISSING_VCALL = 0x04, - MISSING_SUPER = 0x08, - MISSING_MISSING = 0x10, - MISSING_NONE = 0x20 + MISSING_FCALL = 0x04, + MISSING_VCALL = 0x08, + MISSING_SUPER = 0x10, + MISSING_MISSING = 0x20, + MISSING_NONE = 0x40 }; struct rb_call_info { diff --git a/vm_eval.c b/vm_eval.c index 5bdc948c75..7eda5c24d8 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -682,13 +682,15 @@ rb_method_missing(int argc, const VALUE *argv, VALUE obj) } static VALUE -make_no_method_exception(VALUE exc, VALUE format, VALUE obj, int argc, const VALUE *argv) +make_no_method_exception(VALUE exc, VALUE format, VALUE obj, + int argc, const VALUE *argv, int priv) { int n = 0; enum { arg_mesg, arg_name, arg_args, + arg_priv, args_size }; VALUE args[args_size]; @@ -700,6 +702,7 @@ make_no_method_exception(VALUE exc, VALUE format, VALUE obj, int argc, const VAL args[n++] = argv[0]; if (exc == rb_eNoMethodError) { args[n++] = rb_ary_new4(argc - 1, argv + 1); + args[n++] = priv ? Qtrue : Qfalse; } return rb_class_new_instance(n, args, exc); } @@ -737,7 +740,8 @@ raise_method_missing(rb_thread_t *th, int argc, const VALUE *argv, VALUE obj, } { - exc = make_no_method_exception(exc, format, obj, argc, argv); + exc = make_no_method_exception(exc, format, obj, argc, argv, + last_call_status & (MISSING_FCALL|MISSING_VCALL)); if (!(last_call_status & MISSING_MISSING)) { rb_vm_pop_cfunc_frame(); } @@ -929,7 +933,8 @@ send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope) if (!id) { if (rb_method_basic_definition_p(CLASS_OF(recv), idMethodMissing)) { VALUE exc = make_no_method_exception(rb_eNoMethodError, 0, - recv, argc, argv); + recv, argc, argv, + scope != CALL_PUBLIC); rb_exc_raise(exc); } if (!SYMBOL_P(*argv)) { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 022a64df12..39ca48a578 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1782,6 +1782,7 @@ ci_missing_reason(const struct rb_call_info *ci) { enum method_missing_reason stat = MISSING_NOENTRY; if (ci->flag & VM_CALL_VCALL) stat |= MISSING_VCALL; + if (ci->flag & VM_CALL_FCALL) stat |= MISSING_FCALL; if (ci->flag & VM_CALL_SUPER) stat |= MISSING_SUPER; return stat; } @@ -1823,7 +1824,8 @@ vm_call_opt_send(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling if (!(ci->mid = rb_check_id(&sym))) { if (rb_method_basic_definition_p(CLASS_OF(calling->recv), idMethodMissing)) { VALUE exc = make_no_method_exception(rb_eNoMethodError, 0, calling->recv, - rb_long2int(calling->argc), &TOPN(i)); + rb_long2int(calling->argc), &TOPN(i), + ci->flag & (VM_CALL_FCALL|VM_CALL_VCALL)); rb_exc_raise(exc); } TOPN(i) = rb_str_intern(sym); diff --git a/vm_insnhelper.h b/vm_insnhelper.h index f2a2a88c72..69eaaacf2e 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -185,8 +185,8 @@ enum vm_regan_acttype { #define GET_GLOBAL_CONSTANT_STATE() (ruby_vm_global_constant_state) #define INC_GLOBAL_CONSTANT_STATE() (++ruby_vm_global_constant_state) -static VALUE make_no_method_exception(VALUE exc, VALUE format, - VALUE obj, int argc, const VALUE *argv); +static VALUE make_no_method_exception(VALUE exc, VALUE format, VALUE obj, + int argc, const VALUE *argv, int priv); static inline struct vm_throw_data * THROW_DATA_NEW(VALUE val, rb_control_frame_t *cf, VALUE st)