Generalize cfunc large array splat fix to fix many additional cases raising SystemStackError
Originally, when 2e7bceb34ea858649e1f975a934ce1894d1f06a6 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:
parent
e7cdce83e8
commit
99c6d19e50
Notes:
git
2023-04-25 15:06:42 +00:00
@ -3947,3 +3947,19 @@ assert_equal 'true', %q{
|
|||||||
|
|
||||||
calling_my_func
|
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
|
# frozen_string_literal: false
|
||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
|
require '-test-/iter'
|
||||||
|
|
||||||
class TestCall < Test::Unit::TestCase
|
class TestCall < Test::Unit::TestCase
|
||||||
def aaa(a, b=100, *rest)
|
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)
|
assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504)
|
||||||
end
|
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
|
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))
|
||||||
assert_equal(a, [].push(a[0], *a[1..]))
|
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.
|
# Not all tests use such a large array to reduce testing time.
|
||||||
assert_equal(1380888, [].push(*1380888.times.to_a).size)
|
assert_equal(1380888, [].push(*1380888.times.to_a).size)
|
||||||
end
|
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
|
end
|
||||||
|
16
vm.c
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);
|
VALUE type = VM_FRAME_MAGIC_BLOCK | (is_lambda ? VM_FRAME_FLAG_LAMBDA : 0);
|
||||||
rb_control_frame_t *cfp = ec->cfp;
|
rb_control_frame_t *cfp = ec->cfp;
|
||||||
VALUE *sp = cfp->sp;
|
VALUE *sp = cfp->sp;
|
||||||
|
int flags = (kw_splat ? VM_CALL_KW_SPLAT : 0);
|
||||||
|
VALUE *use_argv = (VALUE *)argv;
|
||||||
|
VALUE av[2];
|
||||||
|
|
||||||
stack_check(ec);
|
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);
|
CHECK_VM_STACK_OVERFLOW(cfp, argc);
|
||||||
vm_check_canary(ec, sp);
|
vm_check_canary(ec, sp);
|
||||||
cfp->sp = sp + argc;
|
cfp->sp = sp + argc;
|
||||||
for (i=0; i<argc; i++) {
|
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));
|
(is_lambda ? arg_setup_method : arg_setup_block));
|
||||||
cfp->sp = sp;
|
cfp->sp = sp;
|
||||||
|
|
||||||
|
@ -296,8 +296,15 @@ struct rb_calling_info {
|
|||||||
VALUE recv;
|
VALUE recv;
|
||||||
int argc;
|
int argc;
|
||||||
bool kw_splat;
|
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;
|
struct rb_execution_context_struct;
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
|
46
vm_eval.c
46
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 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_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
|
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)
|
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 = {
|
const struct rb_callcache cc = VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme);
|
||||||
.ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL),
|
return vm_call0_cc(ec, recv, id, argc, argv, &cc, kw_splat);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VALUE
|
VALUE
|
||||||
@ -73,8 +87,16 @@ rb_vm_call_with_refinements(rb_execution_context_t *ec, VALUE recv, ID id, int a
|
|||||||
static inline VALUE
|
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)
|
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 = {
|
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,
|
.cc = cc,
|
||||||
.block_handler = vm_passed_block_handler(ec),
|
.block_handler = vm_passed_block_handler(ec),
|
||||||
.recv = recv,
|
.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,
|
.kw_splat = kw_splat,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vm_call0_body(ec, &calling, argv);
|
return vm_call0_body(ec, &calling, use_argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
|
290
vm_insnhelper.c
290
vm_insnhelper.c
@ -2525,15 +2525,57 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
|
|||||||
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
|
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
#define ALLOW_HEAP_ARGV (-2)
|
||||||
vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary)
|
#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);
|
vm_check_canary(GET_EC(), cfp->sp);
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
if (!NIL_P(ary)) {
|
if (!NIL_P(ary)) {
|
||||||
const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
|
const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
|
||||||
long len = RARRAY_LEN(ary), i;
|
long len = RARRAY_LEN(ary);
|
||||||
|
int argc = calling->argc;
|
||||||
|
|
||||||
|
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);
|
CHECK_VM_STACK_OVERFLOW(cfp, len);
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
@ -2543,6 +2585,9 @@ vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
vm_caller_setup_arg_kw(rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci)
|
vm_caller_setup_arg_kw(rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci)
|
||||||
{
|
{
|
||||||
@ -2581,9 +2626,10 @@ vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE keyword_hash)
|
|||||||
static inline void
|
static inline void
|
||||||
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
||||||
struct rb_calling_info *restrict calling,
|
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))) {
|
if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
|
||||||
|
if (IS_ARGS_KW_SPLAT(ci)) {
|
||||||
// f(*a, **kw)
|
// f(*a, **kw)
|
||||||
VM_ASSERT(calling->kw_splat == 1);
|
VM_ASSERT(calling->kw_splat == 1);
|
||||||
|
|
||||||
@ -2593,21 +2639,30 @@ CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
|||||||
VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
|
VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
|
||||||
|
|
||||||
// splat a
|
// splat a
|
||||||
vm_caller_setup_arg_splat(cfp, calling, ary);
|
if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) return;
|
||||||
|
|
||||||
// put kw
|
// put kw
|
||||||
if (!RHASH_EMPTY_P(kwh)) {
|
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[0] = kwh;
|
||||||
cfp->sp++;
|
cfp->sp++;
|
||||||
calling->argc++;
|
calling->argc++;
|
||||||
|
|
||||||
VM_ASSERT(calling->kw_splat == 1);
|
VM_ASSERT(calling->kw_splat == 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
calling->kw_splat = 0;
|
calling->kw_splat = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
|
else {
|
||||||
// f(*a)
|
// f(*a)
|
||||||
VM_ASSERT(calling->kw_splat == 0);
|
VM_ASSERT(calling->kw_splat == 0);
|
||||||
|
|
||||||
@ -2615,10 +2670,27 @@ CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
|||||||
calling->argc -= 1;
|
calling->argc -= 1;
|
||||||
VALUE ary = cfp->sp[0];
|
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
|
// check the last argument
|
||||||
VALUE last_hash;
|
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) &&
|
if (!IS_ARGS_KEYWORD(ci) &&
|
||||||
calling->argc > 0 &&
|
calling->argc > 0 &&
|
||||||
RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) &&
|
RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) &&
|
||||||
@ -2634,6 +2706,8 @@ CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (UNLIKELY(IS_ARGS_KW_SPLAT(ci))) {
|
else if (UNLIKELY(IS_ARGS_KW_SPLAT(ci))) {
|
||||||
// f(**kw)
|
// f(**kw)
|
||||||
VM_ASSERT(calling->kw_splat == 1);
|
VM_ASSERT(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(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) {
|
||||||
if (LIKELY(rb_simple_iseq_p(iseq))) {
|
if (LIKELY(rb_simple_iseq_p(iseq))) {
|
||||||
rb_control_frame_t *cfp = ec->cfp;
|
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) {
|
if (calling->argc != lead_num) {
|
||||||
argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num);
|
argument_arity_error(ec, iseq, calling->argc, lead_num, lead_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_ASSERT(ci == calling->ci);
|
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)) {
|
else if (rb_iseq_only_optparam_p(iseq)) {
|
||||||
rb_control_frame_t *cfp = ec->cfp;
|
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 lead_num = ISEQ_BODY(iseq)->param.lead_num;
|
||||||
const int opt_num = ISEQ_BODY(iseq)->param.opt_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 argc = calling->argc;
|
||||||
const int opt = argc - lead_num;
|
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);
|
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
|
static VALUE
|
||||||
vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling)
|
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;
|
const struct rb_callinfo *ci = calling->ci;
|
||||||
RB_DEBUG_COUNTER_INC(ccf_cfunc);
|
RB_DEBUG_COUNTER_INC(ccf_cfunc);
|
||||||
|
|
||||||
|
CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT);
|
||||||
VALUE argv_ary;
|
VALUE argv_ary;
|
||||||
|
if (UNLIKELY(argv_ary = calling->heap_argv)) {
|
||||||
if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling, ci))) {
|
|
||||||
VM_ASSERT(!IS_ARGS_KEYWORD(ci));
|
VM_ASSERT(!IS_ARGS_KEYWORD(ci));
|
||||||
|
|
||||||
int argc = RARRAY_LENINT(argv_ary);
|
int argc = RARRAY_LENINT(argv_ary);
|
||||||
VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary);
|
VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary);
|
||||||
VALUE *stack_bottom = reg_cfp->sp - 2;
|
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);
|
return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
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);
|
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 */
|
/* control block frame */
|
||||||
GetProcPtr(procv, proc);
|
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;
|
return val;
|
||||||
}
|
}
|
||||||
@ -3559,11 +3564,17 @@ vm_call_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_c
|
|||||||
int argc;
|
int argc;
|
||||||
const struct rb_callinfo *ci = calling->ci;
|
const struct rb_callinfo *ci = calling->ci;
|
||||||
|
|
||||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
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;
|
argc = calling->argc;
|
||||||
argv = ALLOCA_N(VALUE, argc);
|
argv = ALLOCA_N(VALUE, argc);
|
||||||
MEMCPY(argv, cfp->sp - argc, VALUE, argc);
|
MEMCPY(argv, cfp->sp - argc, VALUE, argc);
|
||||||
cfp->sp += - argc - 1;
|
cfp->sp += - argc - 1;
|
||||||
|
}
|
||||||
|
|
||||||
return vm_call_bmethod_body(ec, calling, argv);
|
return vm_call_bmethod_body(ec, calling, argv);
|
||||||
}
|
}
|
||||||
@ -3666,6 +3677,21 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
|
|||||||
missing_reason = ci_missing_reason(ci);
|
missing_reason = ci_missing_reason(ci);
|
||||||
ec->method_missing_reason = missing_reason;
|
ec->method_missing_reason = missing_reason;
|
||||||
|
|
||||||
|
VALUE argv_ary;
|
||||||
|
if (UNLIKELY(argv_ary = calling->heap_argv)) {
|
||||||
|
if (rb_method_basic_definition_p(klass, idMethodMissing)) {
|
||||||
|
rb_ary_unshift(argv_ary, symbol);
|
||||||
|
|
||||||
|
/* 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_ary_unshift(argv_ary, rb_str_intern(symbol));
|
||||||
|
}
|
||||||
|
else {
|
||||||
/* E.g. when argc == 2
|
/* E.g. when argc == 2
|
||||||
*
|
*
|
||||||
* | | | | TOPN
|
* | | | | TOPN
|
||||||
@ -3699,6 +3725,7 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
|
|||||||
TOPN(i) = rb_str_intern(symbol);
|
TOPN(i) = rb_str_intern(symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
calling->ci = &VM_CI_ON_STACK(mid, flags, argc, vm_ci_kwarg(ci));
|
calling->ci = &VM_CI_ON_STACK(mid, flags, argc, vm_ci_kwarg(ci));
|
||||||
calling->cc = &VM_CC_ON_STACK(klass,
|
calling->cc = &VM_CC_ON_STACK(klass,
|
||||||
@ -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);
|
RB_DEBUG_COUNTER_INC(ccf_opt_send);
|
||||||
|
|
||||||
int i;
|
int i, flags = VM_CALL_FCALL;
|
||||||
VALUE sym;
|
VALUE sym, argv_ary;
|
||||||
|
|
||||||
CALLER_SETUP_ARG(reg_cfp, calling, calling->ci);
|
|
||||||
|
|
||||||
|
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;
|
i = calling->argc - 1;
|
||||||
|
|
||||||
if (calling->argc == 0) {
|
if (calling->argc == 0) {
|
||||||
rb_raise(rb_eArgError, "no method name given");
|
rb_raise(rb_eArgError, "no method name given");
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
sym = TOPN(i);
|
sym = TOPN(i);
|
||||||
/* E.g. when i == 2
|
/* 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;
|
calling->argc -= 1;
|
||||||
DEC_SP(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
|
static VALUE
|
||||||
@ -3780,12 +3816,18 @@ vm_call_method_missing_body(rb_execution_context_t *ec, rb_control_frame_t *reg_
|
|||||||
RB_DEBUG_COUNTER_INC(ccf_method_missing);
|
RB_DEBUG_COUNTER_INC(ccf_method_missing);
|
||||||
|
|
||||||
VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
|
VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
|
||||||
unsigned int argc;
|
unsigned int argc, flag;
|
||||||
|
|
||||||
CALLER_SETUP_ARG(reg_cfp, calling, orig_ci);
|
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;
|
argc = calling->argc + 1;
|
||||||
|
|
||||||
unsigned int flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
|
flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
|
||||||
calling->argc = argc;
|
calling->argc = argc;
|
||||||
|
|
||||||
/* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */
|
/* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */
|
||||||
@ -3796,6 +3838,7 @@ vm_call_method_missing_body(rb_execution_context_t *ec, rb_control_frame_t *reg_
|
|||||||
}
|
}
|
||||||
argv[0] = ID2SYM(vm_ci_mid(orig_ci));
|
argv[0] = ID2SYM(vm_ci_mid(orig_ci));
|
||||||
INC_SP(1);
|
INC_SP(1);
|
||||||
|
}
|
||||||
|
|
||||||
ec->method_missing_reason = reason;
|
ec->method_missing_reason = reason;
|
||||||
calling->ci = &VM_CI_ON_STACK(idMethodMissing, flag, argc, vm_ci_kwarg(orig_ci));
|
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);
|
CC_SET_FASTPATH(cc, vm_call_opt_block_call, TRUE);
|
||||||
return vm_call_opt_block_call(ec, cfp, calling);
|
return vm_call_opt_block_call(ec, cfp, calling);
|
||||||
case OPTIMIZED_METHOD_TYPE_STRUCT_AREF:
|
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);
|
rb_check_arity(calling->argc, 0, 0);
|
||||||
CC_SET_FASTPATH(cc, vm_call_opt_struct_aref, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE));
|
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);
|
return vm_call_opt_struct_aref(ec, cfp, calling);
|
||||||
|
|
||||||
case OPTIMIZED_METHOD_TYPE_STRUCT_ASET:
|
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);
|
rb_check_arity(calling->argc, 1, 1);
|
||||||
CC_SET_FASTPATH(cc, vm_call_opt_struct_aset, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE));
|
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);
|
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);
|
return vm_call_cfunc(ec, cfp, calling);
|
||||||
|
|
||||||
case VM_METHOD_TYPE_ATTRSET:
|
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);
|
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;
|
return v;
|
||||||
|
|
||||||
case VM_METHOD_TYPE_IVAR:
|
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);
|
rb_check_arity(calling->argc, 0, 0);
|
||||||
vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
|
vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
|
||||||
const unsigned int ivar_mask = (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT);
|
const unsigned int ivar_mask = (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT);
|
||||||
@ -4191,10 +4234,15 @@ vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct
|
|||||||
const int stat = ci_missing_reason(ci);
|
const int stat = ci_missing_reason(ci);
|
||||||
|
|
||||||
if (vm_ci_mid(ci) == idMethodMissing) {
|
if (vm_ci_mid(ci) == idMethodMissing) {
|
||||||
|
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;
|
rb_control_frame_t *reg_cfp = cfp;
|
||||||
VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
|
VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
|
||||||
vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat);
|
vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return vm_call_method_missing_body(ec, cfp, calling, ci, stat);
|
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;
|
rb_control_frame_t *cfp = ec->cfp;
|
||||||
VALUE arg0;
|
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 &&
|
if (arg_setup_type == arg_setup_block &&
|
||||||
calling->argc == 1 &&
|
calling->argc == 1 &&
|
||||||
@ -4552,16 +4600,17 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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;
|
struct rb_calling_info calling_entry, *calling;
|
||||||
|
|
||||||
calling = &calling_entry;
|
calling = &calling_entry;
|
||||||
calling->argc = argc;
|
calling->argc = argc;
|
||||||
calling->block_handler = block_handler;
|
calling->block_handler = block_handler;
|
||||||
calling->kw_splat = kw_splat;
|
calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0;
|
||||||
calling->recv = Qundef;
|
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);
|
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 rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq);
|
||||||
const int arg_size = ISEQ_BODY(iseq)->param.size;
|
const int arg_size = ISEQ_BODY(iseq)->param.size;
|
||||||
VALUE * const rsp = GET_SP() - calling->argc;
|
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);
|
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,
|
struct rb_calling_info *calling, const struct rb_callinfo *ci,
|
||||||
MAYBE_UNUSED(bool is_lambda), VALUE block_handler)
|
MAYBE_UNUSED(bool is_lambda), VALUE block_handler)
|
||||||
{
|
{
|
||||||
|
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 {
|
||||||
if (calling->argc < 1) {
|
if (calling->argc < 1) {
|
||||||
rb_raise(rb_eArgError, "no receiver given");
|
rb_raise(rb_eArgError, "no receiver given");
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
VALUE symbol = VM_BH_TO_SYMBOL(block_handler);
|
|
||||||
CALLER_SETUP_ARG(reg_cfp, calling, ci);
|
|
||||||
calling->recv = TOPN(--calling->argc);
|
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
|
static VALUE
|
||||||
@ -4616,9 +4680,9 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
|
|||||||
VALUE val;
|
VALUE val;
|
||||||
int argc;
|
int argc;
|
||||||
const struct rb_captured_block *captured = VM_BH_TO_IFUNC_BLOCK(block_handler);
|
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;
|
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? */
|
POPN(argc); /* TODO: should put before C/yield? */
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@ -1896,6 +1896,8 @@ pub const SEND_MAX_CHAIN_DEPTH: i32 = 20;
|
|||||||
// up to 20 different offsets for case-when
|
// up to 20 different offsets for case-when
|
||||||
pub const CASE_WHEN_MAX_DEPTH: i32 = 20;
|
pub const CASE_WHEN_MAX_DEPTH: i32 = 20;
|
||||||
|
|
||||||
|
pub const MAX_SPLAT_LENGTH: i32 = 127;
|
||||||
|
|
||||||
// Codegen for setting an instance variable.
|
// Codegen for setting an instance variable.
|
||||||
// Preconditions:
|
// Preconditions:
|
||||||
// - receiver is in REG0
|
// - receiver is in REG0
|
||||||
@ -5875,6 +5877,10 @@ fn gen_send_iseq(
|
|||||||
// all the remaining arguments. In the generated code
|
// all the remaining arguments. In the generated code
|
||||||
// we test if this is true and if not side exit.
|
// we test if this is true and if not side exit.
|
||||||
argc = argc - 1 + array_length as i32 + remaining_opt as i32;
|
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);
|
push_splat_args(array_length, asm);
|
||||||
|
|
||||||
for _ in 0..remaining_opt {
|
for _ in 0..remaining_opt {
|
||||||
|
@ -261,6 +261,7 @@ make_counters! {
|
|||||||
send_args_splat_cfunc_zuper,
|
send_args_splat_cfunc_zuper,
|
||||||
send_args_splat_cfunc_ruby2_keywords,
|
send_args_splat_cfunc_ruby2_keywords,
|
||||||
send_iseq_splat_arity_error,
|
send_iseq_splat_arity_error,
|
||||||
|
send_splat_too_long,
|
||||||
send_iseq_ruby2_keywords,
|
send_iseq_ruby2_keywords,
|
||||||
send_send_not_imm,
|
send_send_not_imm,
|
||||||
send_send_wrong_args,
|
send_send_wrong_args,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user