summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_allocation.rb53
-rw-r--r--test/ruby/test_call.rb29
-rw-r--r--vm_args.c26
3 files changed, 102 insertions, 6 deletions
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
index 6ade391c95..90d7c04f9b 100644
--- a/test/ruby/test_allocation.rb
+++ b/test/ruby/test_allocation.rb
@@ -527,6 +527,59 @@ class TestAllocation < Test::Unit::TestCase
RUBY
end
+ def test_anonymous_splat_parameter
+ only_block = block.empty? ? block : block[2..]
+ check_allocations(<<~RUBY)
+ def self.anon_splat(*#{block}); end
+
+ check_allocations(1, 1, "anon_splat(1, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat(1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat(*nil, **nill#{block})")
+ check_allocations(0, 0, "anon_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat(#{only_block})")
+ check_allocations(1, 1, "anon_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat(**empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **hash1, **empty_hash#{block})")
+
+ unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+ check_allocations(0, 0, "anon_splat(*array1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat(*r2k_empty_array#{block})")
+ check_allocations(1, 1, "anon_splat(*r2k_array#{block})")
+ check_allocations(1, 0, "anon_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "anon_splat(*r2k_array1#{block})")
+ end
+ RUBY
+ end
+
def test_anonymous_splat_and_anonymous_keyword_splat_parameters
only_block = block.empty? ? block : block[2..]
check_allocations(<<~RUBY)
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb
index 7843f3b476..1b30ed34d8 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -423,6 +423,35 @@ class TestCall < Test::Unit::TestCase
assert_equal([1, 2, {}], s(*r2ka))
end
+ def test_anon_splat_mutated_bug_21757
+ args = [1, 2]
+ kw = {bug: true}
+
+ def self.m(*); end
+ m(*args, bug: true)
+ assert_equal(2, args.length)
+
+ proc = ->(*) { }
+ proc.(*args, bug: true)
+ assert_equal(2, args.length)
+
+ def self.m2(*); end
+ m2(*args, **kw)
+ assert_equal(2, args.length)
+
+ proc = ->(*) { }
+ proc.(*args, **kw)
+ assert_equal(2, args.length)
+
+ def self.m3(*, **nil); end
+ assert_raise(ArgumentError) { m3(*args, bug: true) }
+ assert_equal(2, args.length)
+
+ proc = ->(*, **nil) { }
+ assert_raise(ArgumentError) { proc.(*args, bug: true) }
+ assert_equal(2, args.length)
+ end
+
def test_kwsplat_block_eval_order
def self.t(**kw, &b) [kw, b] end
diff --git a/vm_args.c b/vm_args.c
index 64ed88d0e1..8d4042e355 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -638,12 +638,26 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
given_argc == ISEQ_BODY(iseq)->param.lead_num + (kw_flag ? 2 : 1) &&
!ISEQ_BODY(iseq)->param.flags.has_opt &&
!ISEQ_BODY(iseq)->param.flags.has_post &&
- !ISEQ_BODY(iseq)->param.flags.ruby2_keywords &&
- (!kw_flag ||
- !ISEQ_BODY(iseq)->param.flags.has_kw ||
- !ISEQ_BODY(iseq)->param.flags.has_kwrest ||
- !ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg)) {
- args->rest_dupped = true;
+ !ISEQ_BODY(iseq)->param.flags.ruby2_keywords) {
+ if (kw_flag) {
+ if (ISEQ_BODY(iseq)->param.flags.has_kw ||
+ ISEQ_BODY(iseq)->param.flags.has_kwrest) {
+ args->rest_dupped = true;
+ }
+ else if (kw_flag & VM_CALL_KW_SPLAT) {
+ VALUE kw_hash = locals[args->argc - 1];
+ if (kw_hash == Qnil ||
+ (RB_TYPE_P(kw_hash, T_HASH) && RHASH_EMPTY_P(kw_hash))) {
+ args->rest_dupped = true;
+ }
+ }
+
+ }
+ else if (!ISEQ_BODY(iseq)->param.flags.has_kw &&
+ !ISEQ_BODY(iseq)->param.flags.has_kwrest &&
+ !ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg) {
+ args->rest_dupped = true;
+ }
}
}