Set up callable_method_entry for DUMMY frame on ArgumentError

Before the patch:
```
$ ./miniruby -e '[1, 2].inject(:tap)'
-e:1:in '<main>': wrong number of arguments (given 1, expected 0) (ArgumentError)
        from -e:1:in 'Enumerable#inject'
        from -e:1:in '<main>'
```

After the patch:
```
$ ./miniruby -e '[1, 2].inject(:tap)'
-e:1:in 'Kernel#tap': wrong number of arguments (given 1, expected 0) (ArgumentError)
        from -e:1:in 'Enumerable#inject'
        from -e:1:in '<main>'
```

Fixes https://bugs.ruby-lang.org/issues/20968#change-113811
This commit is contained in:
Yusuke Endoh 2025-06-23 22:15:58 +09:00
parent a18fa86351
commit 3546cedde3
4 changed files with 39 additions and 31 deletions

View file

@ -460,4 +460,10 @@ class TestBacktrace < Test::Unit::TestCase
assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>" assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>"
assert_equal("Kernel#tap", backtrace[1].label) assert_equal("Kernel#tap", backtrace[1].label)
end end
def test_backtrace_on_argument_error
lineno = __LINE__; [1, 2].inject(:tap)
rescue ArgumentError
assert_equal("#{ __FILE__ }:#{ lineno }:in 'Kernel#tap'", $!.backtrace[0].to_s)
end
end end

View file

@ -1612,7 +1612,7 @@ class TestMethod < Test::Unit::TestCase
begin begin
foo(1) foo(1)
rescue ArgumentError => e rescue ArgumentError => e
assert_equal "main.rb:#{$line_method}:in 'foo'", e.backtrace.first assert_equal "main.rb:#{$line_method}:in 'Object#foo'", e.backtrace.first
end end
EOS EOS
END_OF_BODY END_OF_BODY

View file

@ -8,9 +8,9 @@
**********************************************************************/ **********************************************************************/
NORETURN(static void raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc)); NORETURN(static void raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc));
NORETURN(static void argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int miss_argc, const int min_argc, const int max_argc)); NORETURN(static void argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const int miss_argc, const int min_argc, const int max_argc));
NORETURN(static void argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const char *error, const VALUE keys)); NORETURN(static void argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const char *error, const VALUE keys));
VALUE rb_keyword_error_new(const char *error, VALUE keys); /* class.c */ VALUE rb_keyword_error_new(const char *error, VALUE keys); /* class.c */
static VALUE method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VALUE *argv, static VALUE method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VALUE *argv,
enum method_missing_reason call_status, int kw_splat); enum method_missing_reason call_status, int kw_splat);
@ -321,7 +321,7 @@ args_setup_kw_parameters_lookup(const ID key, VALUE *ptr, const VALUE *const pas
#define KW_SPECIFIED_BITS_MAX (32-1) /* TODO: 32 -> Fixnum's max bits */ #define KW_SPECIFIED_BITS_MAX (32-1) /* TODO: 32 -> Fixnum's max bits */
static void static void
args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, const rb_callable_method_entry_t *cme,
VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords, VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords,
VALUE *const locals) VALUE *const locals)
{ {
@ -345,7 +345,7 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons
} }
} }
if (missing) argument_kw_error(ec, iseq, "missing", missing); if (missing) argument_kw_error(ec, iseq, cme, "missing", missing);
for (di=0; i<key_num; i++, di++) { for (di=0; i<key_num; i++, di++) {
if (args_setup_kw_parameters_lookup(acceptable_keywords[i], &locals[i], passed_keywords, passed_values, passed_keyword_len)) { if (args_setup_kw_parameters_lookup(acceptable_keywords[i], &locals[i], passed_keywords, passed_values, passed_keyword_len)) {
@ -386,7 +386,7 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons
else { else {
if (found != passed_keyword_len) { if (found != passed_keyword_len) {
VALUE keys = make_unknown_kw_hash(passed_keywords, passed_keyword_len, passed_values); VALUE keys = make_unknown_kw_hash(passed_keywords, passed_keyword_len, passed_values);
argument_kw_error(ec, iseq, "unknown", keys); argument_kw_error(ec, iseq, cme, "unknown", keys);
} }
} }
@ -397,7 +397,7 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons
} }
static void static void
args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, const rb_callable_method_entry_t *cme,
VALUE keyword_hash, VALUE *const locals, bool remove_hash_value) VALUE keyword_hash, VALUE *const locals, bool remove_hash_value)
{ {
const ID *acceptable_keywords = ISEQ_BODY(iseq)->param.keyword->table; const ID *acceptable_keywords = ISEQ_BODY(iseq)->param.keyword->table;
@ -430,7 +430,7 @@ args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb
} }
} }
if (missing) argument_kw_error(ec, iseq, "missing", missing); if (missing) argument_kw_error(ec, iseq, cme, "missing", missing);
for (di=0; i<key_num; i++, di++) { for (di=0; i<key_num; i++, di++) {
VALUE key = ID2SYM(acceptable_keywords[i]); VALUE key = ID2SYM(acceptable_keywords[i]);
@ -485,11 +485,11 @@ args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb
* This is simpler than writing code to check which entries in the hash do not match. * This is simpler than writing code to check which entries in the hash do not match.
* This will raise an exception, so the additional performance impact shouldn't be material. * This will raise an exception, so the additional performance impact shouldn't be material.
*/ */
args_setup_kw_parameters_from_kwsplat(ec, iseq, rb_hash_dup(keyword_hash), locals, true); args_setup_kw_parameters_from_kwsplat(ec, iseq, cme, rb_hash_dup(keyword_hash), locals, true);
} }
} }
else if (!RHASH_EMPTY_P(keyword_hash)) { else if (!RHASH_EMPTY_P(keyword_hash)) {
argument_kw_error(ec, iseq, "unknown", rb_hash_keys(keyword_hash)); argument_kw_error(ec, iseq, cme, "unknown", rb_hash_keys(keyword_hash));
} }
} }
@ -607,6 +607,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
VALUE splat_flagged_keyword_hash = 0; VALUE splat_flagged_keyword_hash = 0;
VALUE converted_keyword_hash = 0; VALUE converted_keyword_hash = 0;
VALUE rest_last = 0; VALUE rest_last = 0;
const rb_callable_method_entry_t *cme = calling->cc ? vm_cc_cme(calling->cc) : NULL;
vm_check_canary(ec, orig_sp); vm_check_canary(ec, orig_sp);
/* /*
@ -861,7 +862,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
args_extend(args, min_argc); args_extend(args, min_argc);
} }
else { else {
argument_arity_error(ec, iseq, given_argc, min_argc, max_argc); argument_arity_error(ec, iseq, cme, given_argc, min_argc, max_argc);
} }
} }
@ -872,7 +873,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
given_argc = max_argc; given_argc = max_argc;
} }
else { else {
argument_arity_error(ec, iseq, given_argc, min_argc, max_argc); argument_arity_error(ec, iseq, cme, given_argc, min_argc, max_argc);
} }
} }
@ -918,7 +919,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
if (args->kw_argv != NULL) { if (args->kw_argv != NULL) {
const struct rb_callinfo_kwarg *kw_arg = args->kw_arg; const struct rb_callinfo_kwarg *kw_arg = args->kw_arg;
args_setup_kw_parameters(ec, iseq, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals); args_setup_kw_parameters(ec, iseq, cme, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals);
} }
else if (!NIL_P(keyword_hash)) { else if (!NIL_P(keyword_hash)) {
bool remove_hash_value = false; bool remove_hash_value = false;
@ -926,7 +927,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
keyword_hash = check_kwrestarg(keyword_hash, &kw_flag); keyword_hash = check_kwrestarg(keyword_hash, &kw_flag);
remove_hash_value = true; remove_hash_value = true;
} }
args_setup_kw_parameters_from_kwsplat(ec, iseq, keyword_hash, klocals, remove_hash_value); args_setup_kw_parameters_from_kwsplat(ec, iseq, cme, keyword_hash, klocals, remove_hash_value);
} }
else { else {
#if VM_CHECK_MODE > 0 #if VM_CHECK_MODE > 0
@ -941,7 +942,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
VM_ASSERT(args_argc(args) == 1); VM_ASSERT(args_argc(args) == 1);
} }
#endif #endif
args_setup_kw_parameters(ec, iseq, NULL, 0, NULL, klocals); args_setup_kw_parameters(ec, iseq, cme, NULL, 0, NULL, klocals);
} }
} }
else if (ISEQ_BODY(iseq)->param.flags.has_kwrest) { else if (ISEQ_BODY(iseq)->param.flags.has_kwrest) {
@ -949,7 +950,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
kw_flag, ISEQ_BODY(iseq)->param.flags.anon_kwrest); kw_flag, ISEQ_BODY(iseq)->param.flags.anon_kwrest);
} }
else if (!NIL_P(keyword_hash) && RHASH_SIZE(keyword_hash) > 0 && arg_setup_type == arg_setup_method) { else if (!NIL_P(keyword_hash) && RHASH_SIZE(keyword_hash) > 0 && arg_setup_type == arg_setup_method) {
argument_kw_error(ec, iseq, "unknown", rb_hash_keys(keyword_hash)); argument_kw_error(ec, iseq, cme, "unknown", rb_hash_keys(keyword_hash));
} }
if (ISEQ_BODY(iseq)->param.flags.has_block) { if (ISEQ_BODY(iseq)->param.flags.has_block) {
@ -975,13 +976,13 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
} }
static void static void
raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc) raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc)
{ {
VALUE at; VALUE at;
if (iseq) { if (iseq) {
vm_push_frame(ec, iseq, VM_FRAME_MAGIC_DUMMY | VM_ENV_FLAG_LOCAL, Qnil /* self */, vm_push_frame(ec, iseq, VM_FRAME_MAGIC_DUMMY | VM_ENV_FLAG_LOCAL, Qnil /* self */,
VM_BLOCK_HANDLER_NONE /* specval*/, Qfalse /* me or cref */, VM_BLOCK_HANDLER_NONE /* specval*/, (VALUE) cme /* me or cref */,
ISEQ_BODY(iseq)->iseq_encoded, ISEQ_BODY(iseq)->iseq_encoded,
ec->cfp->sp, 0, 0 /* stack_max */); ec->cfp->sp, 0, 0 /* stack_max */);
at = rb_ec_backtrace_object(ec); at = rb_ec_backtrace_object(ec);
@ -997,7 +998,7 @@ raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VA
} }
static void static void
argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int miss_argc, const int min_argc, const int max_argc) argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const int miss_argc, const int min_argc, const int max_argc)
{ {
VALUE exc = rb_arity_error_new(miss_argc, min_argc, max_argc); VALUE exc = rb_arity_error_new(miss_argc, min_argc, max_argc);
if (ISEQ_BODY(iseq)->param.flags.has_kw) { if (ISEQ_BODY(iseq)->param.flags.has_kw) {
@ -1018,13 +1019,13 @@ argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const in
RSTRING_PTR(mesg)[RSTRING_LEN(mesg)-1] = ')'; RSTRING_PTR(mesg)[RSTRING_LEN(mesg)-1] = ')';
} }
} }
raise_argument_error(ec, iseq, exc); raise_argument_error(ec, iseq, cme, exc);
} }
static void static void
argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const char *error, const VALUE keys) argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const char *error, const VALUE keys)
{ {
raise_argument_error(ec, iseq, rb_keyword_error_new(error, keys)); raise_argument_error(ec, iseq, cme, rb_keyword_error_new(error, keys));
} }
static VALUE static VALUE

View file

@ -170,7 +170,7 @@ vm_check_frame_detail(VALUE type, int req_block, int req_me, int req_cref, VALUE
} }
else { /* cref or Qfalse */ else { /* cref or Qfalse */
if (cref_or_me != Qfalse && cref_or_me_type != imemo_cref) { if (cref_or_me != Qfalse && cref_or_me_type != imemo_cref) {
if (((type & VM_FRAME_FLAG_LAMBDA) || magic == VM_FRAME_MAGIC_IFUNC) && (cref_or_me_type == imemo_ment)) { if (((type & VM_FRAME_FLAG_LAMBDA) || magic == VM_FRAME_MAGIC_IFUNC || magic == VM_FRAME_MAGIC_DUMMY) && (cref_or_me_type == imemo_ment)) {
/* ignore */ /* ignore */
} }
else { else {
@ -2909,7 +2909,7 @@ vm_call_iseq_setup_tailcall_opt_start(rb_execution_context_t *ec, rb_control_fra
} }
static void static void
args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, const rb_callable_method_entry_t *cme,
VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords, VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords,
VALUE *const locals); VALUE *const locals);
@ -2953,7 +2953,7 @@ vm_call_iseq_setup_kwparm_kwarg(rb_execution_context_t *ec, rb_control_frame_t *
const int lead_num = ISEQ_BODY(iseq)->param.lead_num; const int lead_num = ISEQ_BODY(iseq)->param.lead_num;
VALUE * const ci_kws = ALLOCA_N(VALUE, ci_kw_len); VALUE * const ci_kws = ALLOCA_N(VALUE, ci_kw_len);
MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len); MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len);
args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals); args_setup_kw_parameters(ec, iseq, vm_cc_cme(cc), ci_kws, ci_kw_len, ci_keywords, klocals);
int param = ISEQ_BODY(iseq)->param.size; int param = ISEQ_BODY(iseq)->param.size;
int local = ISEQ_BODY(iseq)->local_table_size; int local = ISEQ_BODY(iseq)->local_table_size;
@ -3084,7 +3084,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
CALLER_SETUP_ARG(cfp, calling, ci, lead_num); CALLER_SETUP_ARG(cfp, calling, ci, lead_num);
if (calling->argc != lead_num) { if (calling->argc != lead_num) {
argument_arity_error(ec, iseq, calling->argc, lead_num, lead_num); argument_arity_error(ec, iseq, vm_cc_cme(cc), calling->argc, lead_num, lead_num);
} }
//VM_ASSERT(ci == calling->cd->ci); //VM_ASSERT(ci == calling->cd->ci);
@ -3114,7 +3114,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
const int opt = argc - lead_num; const int opt = argc - lead_num;
if (opt < 0 || opt > opt_num) { if (opt < 0 || opt > opt_num) {
argument_arity_error(ec, iseq, argc, lead_num, lead_num + opt_num); argument_arity_error(ec, iseq, vm_cc_cme(cc), argc, lead_num, lead_num + opt_num);
} }
if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_TAILCALL))) { if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_TAILCALL))) {
@ -3150,7 +3150,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len); MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len);
VALUE *const klocals = argv + kw_param->bits_start - kw_param->num; VALUE *const klocals = argv + kw_param->bits_start - kw_param->num;
args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals); args_setup_kw_parameters(ec, iseq, vm_cc_cme(cc), ci_kws, ci_kw_len, ci_keywords, klocals);
CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_kwarg, CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_kwarg,
vm_call_cacheable(ci, cc)); vm_call_cacheable(ci, cc));
@ -3161,7 +3161,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
else if (argc == lead_num) { else if (argc == lead_num) {
/* no kwarg */ /* no kwarg */
VALUE *const klocals = argv + kw_param->bits_start - kw_param->num; VALUE *const klocals = argv + kw_param->bits_start - kw_param->num;
args_setup_kw_parameters(ec, iseq, NULL, 0, NULL, klocals); args_setup_kw_parameters(ec, iseq, vm_cc_cme(cc), NULL, 0, NULL, klocals);
if (klocals[kw_param->num] == INT2FIX(0)) { if (klocals[kw_param->num] == INT2FIX(0)) {
/* copy from default_values */ /* copy from default_values */
@ -5207,7 +5207,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
} }
} }
else { else {
argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num); argument_arity_error(ec, iseq, NULL, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num);
} }
} }
@ -5229,6 +5229,7 @@ vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int
calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0; calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0;
calling->recv = Qundef; calling->recv = Qundef;
calling->heap_argv = 0; calling->heap_argv = 0;
calling->cc = NULL;
struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, flags, 0, 0); struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, flags, 0, 0);
return vm_callee_setup_block_arg(ec, calling, &dummy_ci, iseq, argv, arg_setup_type); return vm_callee_setup_block_arg(ec, calling, &dummy_ci, iseq, argv, arg_setup_type);