summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Gruber <luke.gruber@shopify.com>2025-08-27 09:26:57 -0400
committerAaron Patterson <aaron.patterson@gmail.com>2025-08-27 11:14:29 -0700
commit886268856ba7c70a6eaf25eeb402e6ebed9e851e (patch)
treebe8c0530cf16d90e21a6afe9d623d5084aee3097
parent76810fc34905011535f50c3f8bbcaf39cb80b6cc (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. Fixes [Bug #21535]
-rw-r--r--test/ruby/test_nomethod_error.rb28
-rw-r--r--vm_insnhelper.c2
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 408f464dab..1299de8666 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -4214,7 +4214,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;