diff options
author | Jeremy Evans <code@jeremyevans.net> | 2023-11-21 09:02:31 -0800 |
---|---|---|
committer | Jeremy Evans <code@jeremyevans.net> | 2023-12-07 11:27:55 -0800 |
commit | 13cd963500106b366c8df9eec5c1b6815e93f07f (patch) | |
tree | 40b919fb6153ecfb2da0fc52176659f725874596 | |
parent | aa808204bb3b84ad37e2c4722f9d00e07ab8f281 (diff) | |
download | ruby-13cd963500106b366c8df9eec5c1b6815e93f07f.tar.gz |
Prevent modification of splat array inside setup_parameters_complex
For the following:
```
def f(*a); a end
p f(*a, kw: 3)
```
`setup_parameters_complex` pushes `{kw: 3}` onto `a`. This worked fine
back when `concatarray true` was used and `a` was already a copy. It
does not work correctly with the optimization to switch to
`concatarray false`. This duplicates the array on the callee side
in such a case.
This affects cases when passing a regular splat and a keyword splat
(or literal keywords) in a method call, where the method does not
accept keywords.
This allocation could probably be avoided, but doing so would
make `setup_parameters_complex` more complicated.
-rw-r--r-- | test/ruby/test_call.rb | 6 | ||||
-rw-r--r-- | vm_args.c | 2 |
2 files changed, 8 insertions, 0 deletions
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index a272d9d0dd..67a94ed7af 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -100,6 +100,12 @@ class TestCall < Test::Unit::TestCase } end + def test_frozen_splat_and_keywords + a = [1, 2].freeze + def self.f(*a); a end + assert_equal([1, 2, {kw: 3}], f(*a, kw: 3)) + end + def test_call_bmethod_proc pr = proc{|sym| sym} define_singleton_method(:a, &pr) @@ -542,11 +542,13 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co else if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) { converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag); flag_keyword_hash = converted_keyword_hash; + arg_rest_dup(args); rb_ary_push(args->rest, converted_keyword_hash); keyword_hash = Qnil; } else if (!ISEQ_BODY(iseq)->param.flags.has_kwrest && !ISEQ_BODY(iseq)->param.flags.has_kw) { converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag); + arg_rest_dup(args); rb_ary_push(args->rest, converted_keyword_hash); keyword_hash = Qnil; } else { |