summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bootstraptest/test_yjit.rb16
-rw-r--r--test/ruby/test_call.rb883
-rw-r--r--vm.c16
-rw-r--r--vm_core.h7
-rw-r--r--vm_eval.c46
-rw-r--r--vm_insnhelper.c458
-rw-r--r--yjit/src/codegen.rs6
-rw-r--r--yjit/src/stats.rs1
8 files changed, 1221 insertions, 212 deletions
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 69e93cabee..a6525e7af9 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -3947,3 +3947,19 @@ assert_equal 'true', %q{
calling_my_func
}
+
+# Fix failed case for large splat
+assert_equal 'true', %q{
+ def d(a, b=:b)
+ end
+
+ def calling_func
+ ary = 1380888.times;
+ d(*ary)
+ end
+ begin
+ calling_func
+ rescue ArgumentError
+ true
+ end
+} unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # Not yet working on RJIT
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb
index dbdb0752be..4d5a387f42 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: false
require 'test/unit'
+require '-test-/iter'
class TestCall < Test::Unit::TestCase
def aaa(a, b=100, *rest)
@@ -116,8 +117,11 @@ class TestCall < Test::Unit::TestCase
assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504)
end
+ OVER_STACK_LEN = (ENV['RUBY_OVER_STACK_LEN'] || 150).to_i # Greater than VM_ARGC_STACK_MAX
+ OVER_STACK_ARGV = OVER_STACK_LEN.times.to_a.freeze
+
def test_call_cfunc_splat_large_array_bug_4040
- a = 1380.times.to_a # Greater than VM_ARGC_STACK_MAX
+ a = OVER_STACK_ARGV
assert_equal(a, [].push(*a))
assert_equal(a, [].push(a[0], *a[1..]))
@@ -199,4 +203,881 @@ class TestCall < Test::Unit::TestCase
# Not all tests use such a large array to reduce testing time.
assert_equal(1380888, [].push(*1380888.times.to_a).size)
end
+
+ def test_call_iseq_large_array_splat_fail
+ def self.a; end
+ def self.b(a=1); end
+ def self.c(k: 1); end
+ def self.d(**kw); end
+ def self.e(k: 1, **kw); end
+ def self.f(a=1, k: 1); end
+ def self.g(a=1, **kw); end
+ def self.h(a=1, k: 1, **kw); end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_iseq_large_array_splat_pass
+ def self.a(*a); a.length end
+ assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV)
+
+ def self.b(_, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV)
+
+ def self.c(_, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV)
+
+ def self.d(b=1, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV)
+
+ def self.e(b=1, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV)
+
+ def self.f(b, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV)
+
+ def self.g(*a, k: 1); a.length end
+ assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV)
+
+ def self.h(*a, **kw); a.length end
+ assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV)
+
+ def self.i(*a, k: 1, **kw); a.length end
+ assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV)
+
+ def self.j(b=1, *a, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV)
+
+ def self.k(b=1, *a, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV)
+
+ def self.l(b=1, *a, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV)
+
+ def self.m(b=1, *a, _, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV)
+
+ def self.n(b=1, *a, _, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV)
+
+ def self.o(b=1, *a, _, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV)
+ end
+
+ def test_call_iseq_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ singleton_class.class_eval("def a(#{args}); [#{args}] end")
+ assert_equal OVER_STACK_ARGV, a(*OVER_STACK_ARGV)
+
+ singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [0], b(*OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [[]], c(*OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], d(*OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_proc_large_array_splat_pass
+ [
+ proc{0} ,
+ proc{|a=1|a},
+ proc{|k: 1|0},
+ proc{|**kw| 0},
+ proc{|k: 1, **kw| 0},
+ proc{|a=1, k: 1| a},
+ proc{|a=1, **kw| a},
+ proc{|a=1, k: 1, **kw| a},
+ ].each do |l|
+ assert_equal 0, l.call(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, proc{|*a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|_, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|_, *a, _| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ end
+
+ def test_call_proc_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ l = instance_eval("proc{|#{args}| [#{args}]}")
+ assert_equal OVER_STACK_ARGV, l.(*OVER_STACK_ARGV)
+
+ l = instance_eval("proc{|#{args}, b| [#{args}, b]}")
+ assert_equal(OVER_STACK_ARGV + [nil], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args1}| [#{args1}]}")
+ assert_equal(OVER_STACK_ARGV[0...-1], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, *b| [#{args}, b]}")
+ assert_equal(OVER_STACK_ARGV + [[]], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args1}, *b| [#{args1}, b]}")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, b, *c| [#{args}, b, c]}")
+ assert_equal(OVER_STACK_ARGV + [nil, []], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, b, *c, d| [#{args}, b, c, d]}")
+ assert_equal(OVER_STACK_ARGV + [nil, [], nil], l.(*OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_lambda_large_array_splat_fail
+ [
+ ->{} ,
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ l.call(*OVER_STACK_ARGV)
+ end
+ end
+ end
+
+ def test_call_lambda_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, ->(*a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(_, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(_, *a, _){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ end
+
+ def test_call_yield_block_large_array_splat_pass
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ [
+ proc{0} ,
+ proc{|a=1|a},
+ proc{|k: 1|0},
+ proc{|**kw| 0},
+ proc{|k: 1, **kw| 0},
+ proc{|a=1, k: 1| a},
+ proc{|a=1, **kw| a},
+ proc{|a=1, k: 1, **kw| a},
+ ].each do |l|
+ assert_equal 0, a(&l)
+ end
+
+ assert_equal OVER_STACK_LEN, a{|*a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b, *a| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1, **kw| a.length}
+ end
+
+ def test_call_yield_large_array_splat_with_large_number_of_parameters
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ assert_equal OVER_STACK_ARGV, instance_eval("a{|#{args}| [#{args}]}", __FILE__, __LINE__)
+ assert_equal(OVER_STACK_ARGV + [nil], instance_eval("a{|#{args}, b| [#{args}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV[0...-1], instance_eval("a{|#{args1}| [#{args1}]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [[]], instance_eval("a{|#{args}, *b| [#{args}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], instance_eval("a{|#{args1}, *b| [#{args1}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [nil, []], instance_eval("a{|#{args}, b, *c| [#{args}, b, c]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [nil, [], nil], instance_eval("a{|#{args}, b, *c, d| [#{args}, b, c, d]}", __FILE__, __LINE__))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_yield_lambda_large_array_splat_fail
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+ [
+ ->{} ,
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ a(&l)
+ end
+ end
+ end
+
+ def test_call_yield_lambda_large_array_splat_pass
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, a(&->(*a){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(_, *a){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(_, *a, _){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b, *a){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, k: 1){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, **kw){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, k: 1, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1, **kw){a.length})
+ end
+
+ def test_call_send_iseq_large_array_splat_fail
+ def self.a; end
+ def self.b(a=1); end
+ def self.c(k: 1); end
+ def self.d(**kw); end
+ def self.e(k: 1, **kw); end
+ def self.f(a=1, k: 1); end
+ def self.g(a=1, **kw); end
+ def self.h(a=1, k: 1, **kw); end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ send(meth, *OVER_STACK_ARGV)
+ end
+ end
+ end
+
+ def test_call_send_iseq_large_array_splat_pass
+ def self.a(*a); a.length end
+ assert_equal OVER_STACK_LEN, send(:a, *OVER_STACK_ARGV)
+
+ def self.b(_, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:b, *OVER_STACK_ARGV)
+
+ def self.c(_, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:c, *OVER_STACK_ARGV)
+
+ def self.d(b=1, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:d, *OVER_STACK_ARGV)
+
+ def self.e(b=1, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:e, *OVER_STACK_ARGV)
+
+ def self.f(b, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:f, *OVER_STACK_ARGV)
+
+ def self.g(*a, k: 1); a.length end
+ assert_equal OVER_STACK_LEN, send(:g, *OVER_STACK_ARGV)
+
+ def self.h(*a, **kw); a.length end
+ assert_equal OVER_STACK_LEN, send(:h, *OVER_STACK_ARGV)
+
+ def self.i(*a, k: 1, **kw); a.length end
+ assert_equal OVER_STACK_LEN, send(:i, *OVER_STACK_ARGV)
+
+ def self.j(b=1, *a, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:j, *OVER_STACK_ARGV)
+
+ def self.k(b=1, *a, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:k, *OVER_STACK_ARGV)
+
+ def self.l(b=1, *a, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:l, *OVER_STACK_ARGV)
+
+ def self.m(b=1, *a, _, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:m, *OVER_STACK_ARGV)
+
+ def self.n(b=1, *a, _, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:n, *OVER_STACK_ARGV)
+
+ def self.o(b=1, *a, _, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:o, *OVER_STACK_ARGV)
+ end
+
+ def test_call_send_iseq_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ singleton_class.class_eval("def a(#{args}); [#{args}] end")
+ assert_equal OVER_STACK_ARGV, send(:a, *OVER_STACK_ARGV)
+
+ singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [0], send(:b, *OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [[]], send(:c, *OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], send(:d, *OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_send_cfunc_large_array_splat_fail
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ send(:object_id, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_send_cfunc_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, [].send(:push, *OVER_STACK_ARGV).length
+ end
+
+ def test_call_attr_reader_large_array_splat_fail
+ singleton_class.send(:attr_reader, :a)
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_attr_writer_large_array_splat_fail
+ singleton_class.send(:attr_writer, :a)
+ singleton_class.send(:alias_method, :a, :a=)
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_struct_aref_large_array_splat_fail
+ s = Struct.new(:a).new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ s.a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ s.send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_struct_aset_large_array_splat_fail
+ s = Struct.new(:a) do
+ alias b a=
+ end.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ s.b(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ s.send(:b, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_alias_large_array_splat
+ c = Class.new do
+ def a; end
+ def c(*a); a.length end
+ attr_accessor :e
+ end
+ sc = Class.new(c) do
+ alias b a
+ alias d c
+ alias f e
+ alias g e=
+ end
+
+ obj = sc.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.b(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.f(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ obj.g(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, obj.d(*OVER_STACK_ARGV)
+ end
+
+ def test_call_zsuper_large_array_splat
+ c = Class.new do
+ private
+ def a; end
+ def c(*a); a.length end
+ attr_reader :e
+ end
+ sc = Class.new(c) do
+ public :a
+ public :c
+ public :e
+ end
+
+ obj = sc.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.e(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, obj.c(*OVER_STACK_ARGV)
+ end
+
+ class RefinedModuleLargeArrayTest
+ c = self
+ using(Module.new do
+ refine c do
+ def a; end
+ def c(*a) a.length end
+ attr_reader :e
+ end
+ end)
+
+ def b
+ a(*OVER_STACK_ARGV)
+ end
+
+ def d
+ c(*OVER_STACK_ARGV)
+ end
+
+ def f
+ e(*OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_refined_large_array_splat_fail
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ RefinedModuleLargeArrayTest.new.b
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ RefinedModuleLargeArrayTest.new.f
+ end
+ end
+
+ def test_call_refined_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, RefinedModuleLargeArrayTest.new.d
+ end
+
+ def test_call_method_missing_iseq_large_array_splat_fail
+ def self.method_missing(_) end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ nonexistent_method(*OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send(:nonexistent_method, *OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_method_missing_iseq_large_array_splat_pass
+ def self.method_missing(m, *a)
+ a.length
+ end
+ assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+
+ def test_call_bmethod_large_array_splat_fail
+ define_singleton_method(:a){}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_bmethod_large_array_splat_pass
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV)
+ end
+
+ def test_call_method_missing_bmethod_large_array_splat_fail
+ define_singleton_method(:method_missing){|_|}
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ nonexistent_method(*OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send(:nonexistent_method, *OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_method_missing_bmethod_large_array_splat_pass
+ define_singleton_method(:method_missing){|_, *a| a.length}
+ assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+
+ def test_call_symproc_large_array_splat_fail
+ define_singleton_method(:a){}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval(":#{meth}.to_proc.(self, *OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_symproc_large_array_splat_pass
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, :a.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :b.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :c.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :d.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :e.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :f.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, :g.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, :h.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, :i.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :j.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :k.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :l.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :m.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :n.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :o.to_proc.(self, *OVER_STACK_ARGV)
+ end
+
+ def test_call_rb_call_iseq_large_array_splat_fail
+ extend Bug::Iter::Yield
+ l = ->(*a){}
+
+ def self.a; end
+ def self.b(a=1) end
+ def self.c(k: 1) end
+ def self.d(**kw) end
+ def self.e(k: 1, **kw) end
+ def self.f(a=1, k: 1) end
+ def self.g(a=1, **kw) end
+ def self.h(a=1, k: 1, **kw) end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(meth, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_rb_call_iseq_large_array_splat_pass
+ extend Bug::Iter::Yield
+ l = ->(*a){a.length}
+
+ def self.a(*a) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ def self.b(_, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l)
+
+ def self.c(_, *a, _) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l)
+
+ def self.d(b=1, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l)
+
+ def self.e(b=1, *a, _) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l)
+
+ def self.f(b, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l)
+
+ def self.g(*a, k: 1) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l)
+
+ def self.h(*a, **kw) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ def self.i(*a, k: 1, **kw) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ def self.j(b=1, *a, k: 1) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l)
+
+ def self.k(b=1, *a, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l)
+
+ def self.l(b=1, *a, k: 1, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l)
+
+ def self.m(b=1, *a, _, k: 1) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l)
+
+ def self.n(b=1, *a, _, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l)
+
+ def self.o(b=1, *a, _, k: 1, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l)
+ end
+
+ def test_call_rb_call_bmethod_large_array_splat_fail
+ extend Bug::Iter::Yield
+ l = ->(*a){}
+
+ define_singleton_method(:a){||}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(meth, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_rb_call_bmethod_large_array_splat_pass
+ extend Bug::Iter::Yield
+ l = ->(*a){a.length}
+
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l)
+ end
+
+ def test_call_ifunc_iseq_large_array_splat_fail
+ extend Bug::Iter::Yield
+ def self.a(*a)
+ yield(*a)
+ end
+ [
+ ->(){},
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(:a, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_ifunc_iseq_large_array_splat_pass
+ extend Bug::Iter::Yield
+ def self.a(*a)
+ yield(*a)
+ end
+
+ l = ->(*a) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(_, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(_, *a, _) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, k: 1) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, **kw) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, k: 1, **kw) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, k: 1) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, k: 1, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, k: 1) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, k: 1, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+ end
end
diff --git a/vm.c b/vm.c
index b3fce8d4fb..e1e8e78053 100644
--- a/vm.c
+++ b/vm.c
@@ -1426,17 +1426,29 @@ invoke_iseq_block_from_c(rb_execution_context_t *ec, const struct rb_captured_bl
VALUE type = VM_FRAME_MAGIC_BLOCK | (is_lambda ? VM_FRAME_FLAG_LAMBDA : 0);
rb_control_frame_t *cfp = ec->cfp;
VALUE *sp = cfp->sp;
+ int flags = (kw_splat ? VM_CALL_KW_SPLAT : 0);
+ VALUE *use_argv = (VALUE *)argv;
+ VALUE av[2];
stack_check(ec);
+#if VM_ARGC_STACK_MAX < 1
+ /* Skip ruby array for potential autosplat case */
+ if (UNLIKELY(argc > VM_ARGC_STACK_MAX && (argc != 1 || is_lambda))) {
+#else
+ if (UNLIKELY(argc > VM_ARGC_STACK_MAX)) {
+#endif
+ use_argv = vm_argv_ruby_array(av, argv, &flags, &argc, kw_splat);
+ }
+
CHECK_VM_STACK_OVERFLOW(cfp, argc);
vm_check_canary(ec, sp);
cfp->sp = sp + argc;
for (i=0; i<argc; i++) {
- sp[i] = argv[i];
+ sp[i] = use_argv[i];
}
- opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, kw_splat, passed_block_handler,
+ opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, flags, passed_block_handler,
(is_lambda ? arg_setup_method : arg_setup_block));
cfp->sp = sp;
diff --git a/vm_core.h b/vm_core.h
index c68cfaf984..f2aa4f63ca 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -296,8 +296,15 @@ struct rb_calling_info {
VALUE recv;
int argc;
bool kw_splat;
+ VALUE heap_argv;
};
+#ifndef VM_ARGC_STACK_MAX
+#define VM_ARGC_STACK_MAX 128
+#endif
+
+# define CALLING_ARGC(calling) ((calling)->heap_argv ? RARRAY_LENINT((calling)->heap_argv) : (calling)->argc)
+
struct rb_execution_context_struct;
#if 1
diff --git a/vm_eval.c b/vm_eval.c
index bacf91f5eb..220e7c9681 100644
--- a/vm_eval.c
+++ b/vm_eval.c
@@ -41,19 +41,33 @@ typedef enum call_type {
static VALUE send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope);
static VALUE vm_call0_body(rb_execution_context_t* ec, struct rb_calling_info *calling, const VALUE *argv);
+static VALUE *
+vm_argv_ruby_array(VALUE *av, const VALUE *argv, int *flags, int *argc, int kw_splat)
+{
+ *flags |= VM_CALL_ARGS_SPLAT;
+ VALUE argv_ary = rb_ary_hidden_new(*argc);
+ rb_ary_cat(argv_ary, argv, *argc);
+ *argc = 2;
+ av[0] = argv_ary;
+ if (kw_splat) {
+ av[1] = rb_ary_pop(argv_ary);
+ }
+ else {
+ // Make sure flagged keyword hash passed as regular argument
+ // isn't treated as keywords
+ *flags |= VM_CALL_KW_SPLAT;
+ av[1] = rb_hash_new();
+ }
+ return av;
+}
+
+static inline VALUE vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat);
+
VALUE
rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme, int kw_splat)
{
- struct rb_calling_info calling = {
- .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL),
- .cc = &VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme),
- .block_handler = vm_passed_block_handler(ec),
- .recv = recv,
- .argc = argc,
- .kw_splat = kw_splat,
- };
-
- return vm_call0_body(ec, &calling, argv);
+ const struct rb_callcache cc = VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme);
+ return vm_call0_cc(ec, recv, id, argc, argv, &cc, kw_splat);
}
VALUE
@@ -73,8 +87,16 @@ rb_vm_call_with_refinements(rb_execution_context_t *ec, VALUE recv, ID id, int a
static inline VALUE
vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat)
{
+ int flags = kw_splat ? VM_CALL_KW_SPLAT : 0;
+ VALUE *use_argv = (VALUE *)argv;
+ VALUE av[2];
+
+ if (UNLIKELY(vm_cc_cme(cc)->def->type == VM_METHOD_TYPE_ISEQ && argc > VM_ARGC_STACK_MAX)) {
+ use_argv = vm_argv_ruby_array(av, argv, &flags, &argc, kw_splat);
+ }
+
struct rb_calling_info calling = {
- .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL),
+ .ci = &VM_CI_ON_STACK(id, flags, argc, NULL),
.cc = cc,
.block_handler = vm_passed_block_handler(ec),
.recv = recv,
@@ -82,7 +104,7 @@ vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE
.kw_splat = kw_splat,
};
- return vm_call0_body(ec, &calling, argv);
+ return vm_call0_body(ec, &calling, use_argv);
}
static VALUE
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index aafc49bfd5..0bbae89498 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -2525,22 +2525,67 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
}
-static inline void
-vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary)
+#define ALLOW_HEAP_ARGV (-2)
+#define ALLOW_HEAP_ARGV_KEEP_KWSPLAT (-3)
+
+static inline bool
+vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary, int max_args)
{
vm_check_canary(GET_EC(), cfp->sp);
+ bool ret = false;
if (!NIL_P(ary)) {
const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
- long len = RARRAY_LEN(ary), i;
+ long len = RARRAY_LEN(ary);
+ int argc = calling->argc;
- CHECK_VM_STACK_OVERFLOW(cfp, len);
+ if (UNLIKELY(max_args <= ALLOW_HEAP_ARGV && len + argc > VM_ARGC_STACK_MAX)) {
+ /* Avoid SystemStackError when splatting large arrays by storing arguments in
+ * a temporary array, instead of trying to keeping arguments on the VM stack.
+ */
+ VALUE *argv = cfp->sp - argc;
+ VALUE argv_ary = rb_ary_hidden_new(len + argc + 1);
+ rb_ary_cat(argv_ary, argv, argc);
+ rb_ary_cat(argv_ary, ptr, len);
+ cfp->sp -= argc - 1;
+ cfp->sp[-1] = argv_ary;
+ calling->argc = 1;
+ calling->heap_argv = argv_ary;
+ RB_GC_GUARD(ary);
+ }
+ else {
+ long i;
+
+ if (max_args >= 0 && len + argc > max_args) {
+ /* If only a given max_args is allowed, copy up to max args.
+ * Used by vm_callee_setup_block_arg for non-lambda blocks,
+ * where additional arguments are ignored.
+ *
+ * Also, copy up to one more argument than the maximum,
+ * in case it is an empty keyword hash that will be removed.
+ */
+ calling->argc += len - (max_args - argc + 1);
+ len = max_args - argc + 1;
+ ret = true;
+ }
+ else {
+ /* Unset heap_argv if set originally. Can happen when
+ * forwarding modified arguments, where heap_argv was used
+ * originally, but heap_argv not supported by the forwarded
+ * method in all cases.
+ */
+ calling->heap_argv = 0;
+ }
+ CHECK_VM_STACK_OVERFLOW(cfp, len);
- for (i = 0; i < len; i++) {
- *cfp->sp++ = ptr[i];
+ for (i = 0; i < len; i++) {
+ *cfp->sp++ = ptr[i];
+ }
+ calling->argc += i;
}
- calling->argc += i;
}
+
+ return ret;
}
static inline void
@@ -2581,56 +2626,85 @@ vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE keyword_hash)
static inline void
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
struct rb_calling_info *restrict calling,
- const struct rb_callinfo *restrict ci)
+ const struct rb_callinfo *restrict ci, int max_args)
{
- if (UNLIKELY(IS_ARGS_SPLAT(ci) && IS_ARGS_KW_SPLAT(ci))) {
- // f(*a, **kw)
- VM_ASSERT(calling->kw_splat == 1);
-
- cfp->sp -= 2;
- calling->argc -= 2;
- VALUE ary = cfp->sp[0];
- VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
-
- // splat a
- vm_caller_setup_arg_splat(cfp, calling, ary);
+ if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
+ if (IS_ARGS_KW_SPLAT(ci)) {
+ // f(*a, **kw)
+ VM_ASSERT(calling->kw_splat == 1);
- // put kw
- if (!RHASH_EMPTY_P(kwh)) {
- cfp->sp[0] = kwh;
- cfp->sp++;
- calling->argc++;
+ cfp->sp -= 2;
+ calling->argc -= 2;
+ VALUE ary = cfp->sp[0];
+ VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
+
+ // splat a
+ if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) return;
+
+ // put kw
+ if (!RHASH_EMPTY_P(kwh)) {
+ if (UNLIKELY(calling->heap_argv)) {
+ rb_ary_push(calling->heap_argv, kwh);
+ ((struct RHash *)kwh)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
+ if (max_args != ALLOW_HEAP_ARGV_KEEP_KWSPLAT) {
+ calling->kw_splat = 0;
+ }
+ }
+ else {
+ cfp->sp[0] = kwh;
+ cfp->sp++;
+ calling->argc++;
- VM_ASSERT(calling->kw_splat == 1);
+ VM_ASSERT(calling->kw_splat == 1);
+ }
+ }
+ else {
+ calling->kw_splat = 0;
+ }
}
else {
- calling->kw_splat = 0;
- }
- }
- else if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
- // f(*a)
- VM_ASSERT(calling->kw_splat == 0);
+ // f(*a)
+ VM_ASSERT(calling->kw_splat == 0);
- cfp->sp -= 1;
- calling->argc -= 1;
- VALUE ary = cfp->sp[0];
+ cfp->sp -= 1;
+ calling->argc -= 1;
+ VALUE ary = cfp->sp[0];
- vm_caller_setup_arg_splat(cfp, calling, ary);
+ if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) {
+ goto check_keyword;
+ }
- // check the last argument
- VALUE last_hash;
- if (!IS_ARGS_KEYWORD(ci) &&
- calling->argc > 0 &&
- RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) &&
- (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
+ // check the last argument
+ VALUE last_hash, argv_ary;
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
+ if (!IS_ARGS_KEYWORD(ci) &&
+ RARRAY_LEN(argv_ary) > 0 &&
+ RB_TYPE_P((last_hash = rb_ary_last(0, NULL, argv_ary)), T_HASH) &&
+ (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
- if (RHASH_EMPTY_P(last_hash)) {
- calling->argc--;
- cfp->sp -= 1;
+ rb_ary_pop(argv_ary);
+ if (!RHASH_EMPTY_P(last_hash)) {
+ rb_ary_push(argv_ary, rb_hash_dup(last_hash));
+ calling->kw_splat = 1;
+ }
+ }
}
else {
- cfp->sp[-1] = rb_hash_dup(last_hash);
- calling->kw_splat = 1;
+check_keyword:
+ if (!IS_ARGS_KEYWORD(ci) &&
+ calling->argc > 0 &&
+ RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) &&
+ (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
+
+ if (RHASH_EMPTY_P(last_hash)) {
+ calling->argc--;
+ cfp->sp -= 1;
+ }
+ else {
+ cfp->sp[-1] = rb_hash_dup(last_hash);
+ calling->kw_splat = 1;
+ }
+ }
}
}
}
@@ -2811,10 +2885,11 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) {
if (LIKELY(rb_simple_iseq_p(iseq))) {
rb_control_frame_t *cfp = ec->cfp;
- CALLER_SETUP_ARG(cfp, calling, ci);
+ int lead_num = ISEQ_BODY(iseq)->param.lead_num;
+ CALLER_SETUP_ARG(cfp, calling, ci, lead_num);
- if (calling->argc != ISEQ_BODY(iseq)->param.lead_num) {
- argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num);
+ if (calling->argc != lead_num) {
+ argument_arity_error(ec, iseq, calling->argc, lead_num, lead_num);
}
VM_ASSERT(ci == calling->ci);
@@ -2835,10 +2910,11 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
}
else if (rb_iseq_only_optparam_p(iseq)) {
rb_control_frame_t *cfp = ec->cfp;
- CALLER_SETUP_ARG(cfp, calling, ci);
const int lead_num = ISEQ_BODY(iseq)->param.lead_num;
const int opt_num = ISEQ_BODY(iseq)->param.opt_num;
+
+ CALLER_SETUP_ARG(cfp, calling, ci, lead_num + opt_num);
const int argc = calling->argc;
const int opt = argc - lead_num;
@@ -3385,86 +3461,16 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp
return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom);
}
-#ifndef VM_ARGC_STACK_MAX
-#define VM_ARGC_STACK_MAX 128
-#endif
-
-static VALUE
-vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci)
-{
- const bool kwsplat_p = IS_ARGS_KW_SPLAT(ci);
- int argc = calling->argc;
- VALUE *argv = cfp->sp - argc;
- VALUE ary = argv[argc - (kwsplat_p ? 2 : 1)];
- long len = RARRAY_LEN(ary);
-
- if (UNLIKELY(len + argc > VM_ARGC_STACK_MAX)) {
- VALUE kwhash;
-
- if (kwsplat_p) {
- // the last argument is kwhash
- cfp->sp--;
- kwhash = vm_caller_setup_keyword_hash(ci, cfp->sp[0]);
- calling->argc--;
- argc--;
-
- VM_ASSERT(calling->kw_splat);
- }
-
- vm_check_canary(ec, cfp->sp);
- const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
- VALUE argv_ary = rb_ary_new_capa(len + argc - 1);
- rb_obj_hide(argv_ary);
- rb_ary_cat(argv_ary, argv, argc-1);
- rb_ary_cat(argv_ary, ptr, len);
- cfp->sp -= argc - 1;
- cfp->sp[-1] = argv_ary;
- calling->argc = 1;
-
- if (kwsplat_p) {
- if (!RHASH_EMPTY_P(kwhash)) {
- rb_ary_push(argv_ary, kwhash);
- }
- else {
- calling->kw_splat = false;
- }
- }
- else if (RARRAY_LEN(argv_ary) > 0) {
- // check the last argument
- long hash_idx = RARRAY_LEN(argv_ary) - 1;
- VALUE last_hash = RARRAY_AREF(argv_ary, hash_idx);
-
- if (RB_TYPE_P(last_hash, T_HASH) &&
- (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
- if (RHASH_EMPTY_P(last_hash)) {
- rb_ary_pop(argv_ary);
- }
- else {
- last_hash = rb_hash_dup(last_hash);
- RARRAY_ASET(argv_ary, hash_idx, last_hash);
- calling->kw_splat = 1;
- }
- }
- }
-
- return argv_ary;
- }
- else {
- return Qfalse;
- }
-}
-
static VALUE
vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling)
{
const struct rb_callinfo *ci = calling->ci;
RB_DEBUG_COUNTER_INC(ccf_cfunc);
+ CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT);
VALUE argv_ary;
-
- if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling, ci))) {
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
VM_ASSERT(!IS_ARGS_KEYWORD(ci));
-
int argc = RARRAY_LENINT(argv_ary);
VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary);
VALUE *stack_bottom = reg_cfp->sp - 2;
@@ -3476,7 +3482,6 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb
return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom);
}
else {
- CALLER_SETUP_ARG(reg_cfp, calling, ci);
CC_SET_FASTPATH(calling->cc, vm_call_cfunc_with_frame, !rb_splat_or_kwargs_p(ci) && !calling->kw_splat);
return vm_call_cfunc_with_frame(ec, reg_cfp, calling);
@@ -3545,7 +3550,7 @@ vm_call_bmethod_body(rb_execution_context_t *ec, struct rb_calling_info *calling
/* control block frame */
GetProcPtr(procv, proc);
- val = rb_vm_invoke_bmethod(ec, proc, calling->recv, calling->argc, argv, calling->kw_splat, calling->block_handler, vm_cc_cme(cc));
+ val = rb_vm_invoke_bmethod(ec, proc, calling->recv, CALLING_ARGC(calling), argv, calling->kw_splat, calling->block_handler, vm_cc_cme(cc));
return val;
}
@@ -3559,11 +3564,17 @@ vm_call_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_c
int argc;
const struct rb_callinfo *ci = calling->ci;
- CALLER_SETUP_ARG(cfp, calling, ci);
- argc = calling->argc;
- argv = ALLOCA_N(VALUE, argc);
- MEMCPY(argv, cfp->sp - argc, VALUE, argc);
- cfp->sp += - argc - 1;
+ CALLER_SETUP_ARG(cfp, calling, ci, ALLOW_HEAP_ARGV);
+ if (UNLIKELY(calling->heap_argv)) {
+ argv = RARRAY_PTR(calling->heap_argv);
+ cfp->sp -= 2;
+ }
+ else {
+ argc = calling->argc;
+ argv = ALLOCA_N(VALUE, argc);
+ MEMCPY(argv, cfp->sp - argc, VALUE, argc);
+ cfp->sp += - argc - 1;
+ }
return vm_call_bmethod_body(ec, calling, argv);
}
@@ -3666,37 +3677,53 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
missing_reason = ci_missing_reason(ci);
ec->method_missing_reason = missing_reason;
- /* E.g. when argc == 2
- *
- * | | | | TOPN
- * | | +------+
- * | | +---> | arg1 | 0
- * +------+ | +------+
- * | arg1 | -+ +-> | arg0 | 1
- * +------+ | +------+
- * | arg0 | ---+ | sym | 2
- * +------+ +------+
- * | recv | | recv | 3
- * --+------+--------+------+------
- */
- int i = argc;
- CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
- INC_SP(1);
- MEMMOVE(&TOPN(i - 1), &TOPN(i), VALUE, i);
- argc = ++calling->argc;
+ VALUE argv_ary;
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
+ if (rb_method_basic_definition_p(klass, idMethodMissing)) {
+ rb_ary_unshift(argv_ary, symbol);
- if (rb_method_basic_definition_p(klass, idMethodMissing)) {
- /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */
- TOPN(i) = symbol;
- int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL);
- const VALUE *argv = STACK_ADDR_FROM_TOP(argc);
- VALUE exc = rb_make_no_method_exception(
- rb_eNoMethodError, 0, recv, argc, argv, priv);
+ /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */
+ int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL);
+ VALUE exc = rb_make_no_method_exception(
+ rb_eNoMethodError, 0, recv, RARRAY_LENINT(argv_ary), RARRAY_CONST_PTR(argv_ary), priv);
- rb_exc_raise(exc);
+ rb_exc_raise(exc);
+ }
+ rb_ary_unshift(argv_ary, rb_str_intern(symbol));
}
else {
- TOPN(i) = rb_str_intern(symbol);
+ /* E.g. when argc == 2
+ *
+ * | | | | TOPN
+ * | | +------+
+ * | | +---> | arg1 | 0
+ * +------+ | +------+
+ * | arg1 | -+ +-> | arg0 | 1
+ * +------+ | +------+
+ * | arg0 | ---+ | sym | 2
+ * +------+ +------+
+ * | recv | | recv | 3
+ * --+------+--------+------+------
+ */
+ int i = argc;
+ CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
+ INC_SP(1);
+ MEMMOVE(&TOPN(i - 1), &TOPN(i), VALUE, i);
+ argc = ++calling->argc;
+
+ if (rb_method_basic_definition_p(klass, idMethodMissing)) {
+ /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */
+ TOPN(i) = symbol;
+ int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL);
+ const VALUE *argv = STACK_ADDR_FROM_TOP(argc);
+ VALUE exc = rb_make_no_method_exception(
+ rb_eNoMethodError, 0, recv, argc, argv, priv);
+
+ rb_exc_raise(exc);
+ }
+ else {
+ TOPN(i) = rb_str_intern(symbol);
+ }
}
}
@@ -3737,17 +3764,26 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct
{
RB_DEBUG_COUNTER_INC(ccf_opt_send);
- int i;
- VALUE sym;
+ int i, flags = VM_CALL_FCALL;
+ VALUE sym, argv_ary;
- CALLER_SETUP_ARG(reg_cfp, calling, calling->ci);
-
- i = calling->argc - 1;
-
- if (calling->argc == 0) {
- rb_raise(rb_eArgError, "no method name given");
+ CALLER_SETUP_ARG(reg_cfp, calling, calling->ci, ALLOW_HEAP_ARGV);
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
+ sym = rb_ary_shift(argv_ary);
+ flags |= VM_CALL_ARGS_SPLAT;
+ if (calling->kw_splat) {
+ VALUE last_hash = rb_ary_last(0, NULL, argv_ary);
+ ((struct RHash *)last_hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
+ calling->kw_splat = 0;
+ }
}
else {
+ i = calling->argc - 1;
+
+ if (calling->argc == 0) {
+ rb_raise(rb_eArgError, "no method name given");
+ }
+
sym = TOPN(i);
/* E.g. when i == 2
*
@@ -3768,9 +3804,9 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct
}
calling->argc -= 1;
DEC_SP(1);
-
- return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, VM_CALL_FCALL);
}
+
+ return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, flags);
}
static VALUE
@@ -3780,22 +3816,29 @@ vm_call_method_missing_body(rb_execution_context_t *ec, rb_control_frame_t *reg_
RB_DEBUG_COUNTER_INC(ccf_method_missing);
VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
- unsigned int argc;
+ unsigned int argc, flag;
- CALLER_SETUP_ARG(reg_cfp, calling, orig_ci);
- argc = calling->argc + 1;
+ CALLER_SETUP_ARG(reg_cfp, calling, orig_ci, ALLOW_HEAP_ARGV);
+ if (UNLIKELY(calling->heap_argv)) {
+ flag = VM_CALL_ARGS_SPLAT | VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
+ argc = 1;
+ rb_ary_unshift(calling->heap_argv, ID2SYM(vm_ci_mid(orig_ci)));
+ }
+ else {
+ argc = calling->argc + 1;
- unsigned int flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
- calling->argc = argc;
+ flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
+ calling->argc = argc;
- /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */
- CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
- vm_check_canary(ec, reg_cfp->sp);
- if (argc > 1) {
- MEMMOVE(argv+1, argv, VALUE, argc-1);
+ /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */
+ CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
+ vm_check_canary(ec, reg_cfp->sp);
+ if (argc > 1) {
+ MEMMOVE(argv+1, argv, VALUE, argc-1);
+ }
+ argv[0] = ID2SYM(vm_ci_mid(orig_ci));
+ INC_SP(1);
}
- argv[0] = ID2SYM(vm_ci_mid(orig_ci));
- INC_SP(1);
ec->method_missing_reason = reason;
calling->ci = &VM_CI_ON_STACK(idMethodMissing, flag, argc, vm_ci_kwarg(orig_ci));
@@ -4059,13 +4102,13 @@ vm_call_optimized(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb
CC_SET_FASTPATH(cc, vm_call_opt_block_call, TRUE);
return vm_call_opt_block_call(ec, cfp, calling);
case OPTIMIZED_METHOD_TYPE_STRUCT_AREF:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 0);
rb_check_arity(calling->argc, 0, 0);
CC_SET_FASTPATH(cc, vm_call_opt_struct_aref, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE));
return vm_call_opt_struct_aref(ec, cfp, calling);
case OPTIMIZED_METHOD_TYPE_STRUCT_ASET:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 1);
rb_check_arity(calling->argc, 1, 1);
CC_SET_FASTPATH(cc, vm_call_opt_struct_aset, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE));
return vm_call_opt_struct_aset(ec, cfp, calling);
@@ -4106,7 +4149,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
return vm_call_cfunc(ec, cfp, calling);
case VM_METHOD_TYPE_ATTRSET:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 1);
rb_check_arity(calling->argc, 1, 1);
@@ -4141,7 +4184,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
return v;
case VM_METHOD_TYPE_IVAR:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 0);
rb_check_arity(calling->argc, 0, 0);
vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
const unsigned int ivar_mask = (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT);
@@ -4191,9 +4234,14 @@ vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct
const int stat = ci_missing_reason(ci);
if (vm_ci_mid(ci) == idMethodMissing) {
- rb_control_frame_t *reg_cfp = cfp;
- VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
- vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat);
+ if (UNLIKELY(calling->heap_argv)) {
+ vm_raise_method_missing(ec, RARRAY_LENINT(calling->heap_argv), RARRAY_CONST_PTR(calling->heap_argv), calling->recv, stat);
+ }
+ else {
+ rb_control_frame_t *reg_cfp = cfp;
+ VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
+ vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat);
+ }
}
else {
return vm_call_method_missing_body(ec, cfp, calling, ci, stat);
@@ -4517,7 +4565,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
rb_control_frame_t *cfp = ec->cfp;
VALUE arg0;
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, ISEQ_BODY(iseq)->param.lead_num);
if (arg_setup_type == arg_setup_block &&
calling->argc == 1 &&
@@ -4552,16 +4600,17 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
}
static int
-vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int argc, VALUE *argv, int kw_splat, VALUE block_handler, enum arg_setup_type arg_setup_type)
+vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int argc, VALUE *argv, int flags, VALUE block_handler, enum arg_setup_type arg_setup_type)
{
struct rb_calling_info calling_entry, *calling;
calling = &calling_entry;
calling->argc = argc;
calling->block_handler = block_handler;
- calling->kw_splat = kw_splat;
+ calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0;
calling->recv = Qundef;
- struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, (kw_splat ? VM_CALL_KW_SPLAT : 0), 0, 0);
+ calling->heap_argv = 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);
}
@@ -4577,7 +4626,8 @@ vm_invoke_iseq_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq);
const int arg_size = ISEQ_BODY(iseq)->param.size;
VALUE * const rsp = GET_SP() - calling->argc;
- int opt_pc = vm_callee_setup_block_arg(ec, calling, ci, iseq, rsp, is_lambda ? arg_setup_method : arg_setup_block);
+ VALUE * const argv = rsp;
+ int opt_pc = vm_callee_setup_block_arg(ec, calling, ci, iseq, argv, is_lambda ? arg_setup_method : arg_setup_block);
SET_SP(rsp);
@@ -4597,15 +4647,29 @@ vm_invoke_symbol_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
struct rb_calling_info *calling, const struct rb_callinfo *ci,
MAYBE_UNUSED(bool is_lambda), VALUE block_handler)
{
- if (calling->argc < 1) {
- rb_raise(rb_eArgError, "no receiver given");
+ VALUE symbol = VM_BH_TO_SYMBOL(block_handler);
+ CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV);
+ int flags = 0;
+ if (UNLIKELY(calling->heap_argv)) {
+#if VM_ARGC_STACK_MAX < 0
+ if (RARRAY_LEN(calling->heap_argv) < 1) {
+ rb_raise(rb_eArgError, "no receiver given");
+ }
+#endif
+ calling->recv = rb_ary_shift(calling->heap_argv);
+ // Modify stack to avoid cfp consistency error
+ reg_cfp->sp++;
+ reg_cfp->sp[-1] = reg_cfp->sp[-2];
+ reg_cfp->sp[-2] = calling->recv;
+ flags |= VM_CALL_ARGS_SPLAT;
}
else {
- VALUE symbol = VM_BH_TO_SYMBOL(block_handler);
- CALLER_SETUP_ARG(reg_cfp, calling, ci);
+ if (calling->argc < 1) {
+ rb_raise(rb_eArgError, "no receiver given");
+ }
calling->recv = TOPN(--calling->argc);
- return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, 0);
}
+ return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, flags);
}
static VALUE
@@ -4616,9 +4680,9 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
VALUE val;
int argc;
const struct rb_captured_block *captured = VM_BH_TO_IFUNC_BLOCK(block_handler);
- CALLER_SETUP_ARG(ec->cfp, calling, ci);
+ CALLER_SETUP_ARG(ec->cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT);
argc = calling->argc;
- val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL);
+ val = vm_yield_with_cfunc(ec, captured, captured->self, CALLING_ARGC(calling), calling->heap_argv ? RARRAY_CONST_PTR(calling->heap_argv) : STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL);
POPN(argc); /* TODO: should put before C/yield? */
return val;
}
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index c6f766f0fa..ce4f87649a 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -1896,6 +1896,8 @@ pub const SEND_MAX_CHAIN_DEPTH: i32 = 20;
// up to 20 different offsets for case-when
pub const CASE_WHEN_MAX_DEPTH: i32 = 20;
+pub const MAX_SPLAT_LENGTH: i32 = 127;
+
// Codegen for setting an instance variable.
// Preconditions:
// - receiver is in REG0
@@ -5875,6 +5877,10 @@ fn gen_send_iseq(
// all the remaining arguments. In the generated code
// we test if this is true and if not side exit.
argc = argc - 1 + array_length as i32 + remaining_opt as i32;
+ if argc + asm.ctx.get_stack_size() as i32 > MAX_SPLAT_LENGTH {
+ gen_counter_incr!(asm, send_splat_too_long);
+ return None;
+ }
push_splat_args(array_length, asm);
for _ in 0..remaining_opt {
diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs
index bf90b233f7..444514b909 100644
--- a/yjit/src/stats.rs
+++ b/yjit/src/stats.rs
@@ -261,6 +261,7 @@ make_counters! {
send_args_splat_cfunc_zuper,
send_args_splat_cfunc_ruby2_keywords,
send_iseq_splat_arity_error,
+ send_splat_too_long,
send_iseq_ruby2_keywords,
send_send_not_imm,
send_send_wrong_args,