Generalize cfunc large array splat fix to fix many additional cases raising SystemStackError

Originally, when 2e7bceb34e fixed cfuncs to no
longer use the VM stack for large array splats, it was thought to have fully
fixed Bug #4040, since the issue was fixed for methods defined in Ruby (iseqs)
back in Ruby 2.2.

After additional research, I determined that same issue affects almost all
types of method calls, not just iseq and cfunc calls.  There were two main
types of remaining issues, important cases (where large array splat should
work) and pedantic cases (where large array splat raised SystemStackError
instead of ArgumentError).

Important cases:

```ruby
define_method(:a){|*a|}
a(*1380888.times)

def b(*a); end
send(:b, *1380888.times)

:b.to_proc.call(self, *1380888.times)

def d; yield(*1380888.times) end
d(&method(:b))

def self.method_missing(*a); end
not_a_method(*1380888.times)

```

Pedantic cases:

```ruby
def a; end
a(*1380888.times)
def b(_); end
b(*1380888.times)
def c(_=nil); end
c(*1380888.times)

c = Class.new do
  attr_accessor :a
  alias b a=
end.new
c.a(*1380888.times)
c.b(*1380888.times)

c = Struct.new(:a) do
  alias b a=
end.new
c.a(*1380888.times)
c.b(*1380888.times)
```

This patch fixes all usage of CALLER_SETUP_ARG with splatting a large
number of arguments, and required similar fixes to use a temporary
hidden array in three other cases where the VM would use the VM stack
for handling a large number of arguments.  However, it is possible
there may be additional cases where splatting a large number
of arguments still causes a SystemStackError.

This has a measurable performance impact, as it requires additional
checks for a large number of arguments in many additional cases.

This change is fairly invasive, as there were many different VM
functions that needed to be modified to support this. To avoid
too much API change, I modified struct rb_calling_info to add a
heap_argv member for storing the array, so I would not have to
thread it through many functions.  This struct is always stack
allocated, which helps ensure sure GC doesn't collect it early.

Because of how invasive the changes are, and how rarely large
arrays are actually splatted in Ruby code, the existing test/spec
suites are not great at testing for correct behavior.  To try to
find and fix all issues, I tested this in CI with
VM_ARGC_STACK_MAX to -1, ensuring that a temporary array is used
for all array splat method calls.  This was very helpful in
finding breaking cases, especially ones involving flagged keyword
hashes.

Fixes [Bug #4040]

Co-authored-by: Jimmy Miller <jimmy.miller@shopify.com>
This commit is contained in:
Jeremy Evans 2023-03-06 15:58:58 -08:00
Родитель e7cdce83e8
Коммит 99c6d19e50
8 изменённых файлов: 1227 добавлений и 218 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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

16
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;

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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);
for (i = 0; i < len; i++) {
*cfp->sp++ = ptr[i];
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];
}
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);
// put kw
if (!RHASH_EMPTY_P(kwh)) {
cfp->sp[0] = kwh;
cfp->sp++;
calling->argc++;
if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
if (IS_ARGS_KW_SPLAT(ci)) {
// f(*a, **kw)
VM_ASSERT(calling->kw_splat == 1);
}
else {
calling->kw_splat = 0;
}
}
else if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
// f(*a)
VM_ASSERT(calling->kw_splat == 0);
cfp->sp -= 1;
calling->argc -= 1;
VALUE ary = cfp->sp[0];
cfp->sp -= 2;
calling->argc -= 2;
VALUE ary = cfp->sp[0];
VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
vm_caller_setup_arg_splat(cfp, calling, ary);
// splat a
if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) return;
// 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)) {
// 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++;
if (RHASH_EMPTY_P(last_hash)) {
calling->argc--;
cfp->sp -= 1;
VM_ASSERT(calling->kw_splat == 1);
}
}
else {
cfp->sp[-1] = rb_hash_dup(last_hash);
calling->kw_splat = 1;
calling->kw_splat = 0;
}
}
else {
// f(*a)
VM_ASSERT(calling->kw_splat == 0);
cfp->sp -= 1;
calling->argc -= 1;
VALUE ary = cfp->sp[0];
if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) {
goto check_keyword;
}
// 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)) {
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 {
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;
unsigned int 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);
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;
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);
}
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;
}

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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,