summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKoichi Sasada <ko1@atdot.net>2023-03-14 03:42:47 +0900
committerKoichi Sasada <ko1@atdot.net>2023-03-15 18:05:13 +0900
commit6462c1a042fec4017f7e3bf2c13ec6a29efd23d6 (patch)
tree01b3c6e3819e91525acce8c68bd0de8931551fe9
parent7fd53eeb46db261bbc20025cdab70096245a5cbe (diff)
`Hash#dup` for kwsplat arguments
On `f(*a, **kw)` method calls, a rest keyword parameter is identically same Hash object is passed and it should make `#dup`ed Hahs. fix https://bugs.ruby-lang.org/issues/19526
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/7507
-rw-r--r--compile.c14
-rw-r--r--test/ruby/test_keyword.rb10
-rw-r--r--vm_args.c20
3 files changed, 40 insertions, 4 deletions
diff --git a/compile.c b/compile.c
index 8aa2228c53..08d21d760e 100644
--- a/compile.c
+++ b/compile.c
@@ -5789,6 +5789,16 @@ check_keyword(const NODE *node)
}
#endif
+static bool
+keyword_node_single_splat_p(NODE *kwnode)
+{
+ RUBY_ASSERT(keyword_node_p(kwnode));
+
+ NODE *node = kwnode->nd_head;
+ return node->nd_head == NULL &&
+ node->nd_next->nd_next == NULL;
+}
+
static int
setup_args_core(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn,
int dup_rest, unsigned int *flag_ptr, struct rb_callinfo_kwarg **kwarg_ptr)
@@ -5881,7 +5891,9 @@ setup_args_core(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn,
if (kwnode) {
// f(*a, k:1)
*flag_ptr |= VM_CALL_KW_SPLAT;
- *flag_ptr |= VM_CALL_KW_SPLAT_MUT;
+ if (!keyword_node_single_splat_p(kwnode)) {
+ *flag_ptr |= VM_CALL_KW_SPLAT_MUT;
+ }
compile_hash(iseq, args, kwnode, TRUE, FALSE);
argc += 1;
}
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 94b5323ab0..ef594bd52e 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -447,6 +447,16 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(false, public_send(:yo, **{}).frozen?)
assert_equal_not_same(kw, public_send(:yo, **kw))
assert_equal_not_same(h, public_send(:yo, **h))
+
+ def self.yo(*a, **kw) = kw
+ assert_equal_not_same kw, yo(**kw)
+ assert_equal_not_same kw, yo(**kw, **kw)
+
+ singleton_class.send(:remove_method, :yo)
+ def self.yo(opts) = opts
+ assert_equal_not_same h, yo(*[], **h)
+ a = []
+ assert_equal_not_same h, yo(*a, **h)
end
def test_regular_kwsplat
diff --git a/vm_args.c b/vm_args.c
index 1af69cceee..cc7f1e29b5 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -450,6 +450,18 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned
RHASH_EMPTY_P(keyword_hash);
}
+static VALUE
+check_kwrestarg(VALUE keyword_hash, unsigned int *kw_flag)
+{
+ if (!(*kw_flag & VM_CALL_KW_SPLAT_MUT)) {
+ *kw_flag |= VM_CALL_KW_SPLAT_MUT;
+ return rb_hash_dup(keyword_hash);
+ }
+ else {
+ return keyword_hash;
+ }
+}
+
static int
setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq,
struct rb_calling_info *const calling,
@@ -528,12 +540,14 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
keyword_hash = Qnil;
}
else if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) {
- flag_keyword_hash = keyword_hash;
- rb_ary_push(args->rest, keyword_hash);
+ converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag);
+ flag_keyword_hash = converted_keyword_hash;
+ 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) {
- rb_ary_push(args->rest, keyword_hash);
+ converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag);
+ rb_ary_push(args->rest, converted_keyword_hash);
keyword_hash = Qnil;
}