diff options
| author | Luke Gruber <luke.gruber@shopify.com> | 2025-08-27 09:26:57 -0400 |
|---|---|---|
| committer | Takashi Kokubun <takashikkbn@gmail.com> | 2025-08-27 15:19:58 -0700 |
| commit | 0d367ce65fe4e85f207a8ca5f1f331dd90c6992e (patch) | |
| tree | cbe1f4f11938dfc097e8cd1e306fd58e28aadf8a | |
| parent | 5a42d267bfabc86f86cae2e83de24b1b86bc316a (diff) | |
Fix bad NameError raised using sendforward instruction through vcall
If you called a VCALL method and the method takes forwarding arguments
and then you forward those arguments along using the sendforward
instruction, the method_missing class was wrongly chosen as NameError
instead of NoMethodError. This is because the VM looked at the CallInfo
of the vcall and determined it needed to raise NameError. Now we detect
that case and raise NoMethodError.
[Backport #21535]
| -rw-r--r-- | test/ruby/test_nomethod_error.rb | 28 | ||||
| -rw-r--r-- | vm_insnhelper.c | 2 |
2 files changed, 29 insertions, 1 deletions
diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb index 6d413e6391..aa2a88b2d8 100644 --- a/test/ruby/test_nomethod_error.rb +++ b/test/ruby/test_nomethod_error.rb @@ -106,4 +106,32 @@ class TestNoMethodError < Test::Unit::TestCase assert_match(/undefined method.+this_method_does_not_exist.+for.+Module/, err.to_s) end + + def test_send_forward_raises + t = EnvUtil.labeled_class("Test") do + def foo(...) + forward(...) + end + end + obj = t.new + assert_raise(NoMethodError) do + obj.foo + end + end + + # [Bug #21535] + def test_send_forward_raises_when_called_through_vcall + t = EnvUtil.labeled_class("Test") do + def foo(...) + forward(...) + end + def foo_indirect + foo # vcall + end + end + obj = t.new + assert_raise(NoMethodError) do + obj.foo_indirect + end + end end diff --git a/vm_insnhelper.c b/vm_insnhelper.c index c9e20e95b4..ac08a00600 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -4185,7 +4185,7 @@ static enum method_missing_reason ci_missing_reason(const struct rb_callinfo *ci) { enum method_missing_reason stat = MISSING_NOENTRY; - if (vm_ci_flag(ci) & VM_CALL_VCALL) stat |= MISSING_VCALL; + if (vm_ci_flag(ci) & VM_CALL_VCALL && !(vm_ci_flag(ci) & VM_CALL_FORWARDING)) stat |= MISSING_VCALL; if (vm_ci_flag(ci) & VM_CALL_FCALL) stat |= MISSING_FCALL; if (vm_ci_flag(ci) & VM_CALL_SUPER) stat |= MISSING_SUPER; return stat; |
