[前][次][番号順一覧][スレッド一覧]

ruby-changes:71631

From: Jeremy <ko1@a...>
Date: Tue, 5 Apr 2022 18:42:19 +0900 (JST)
Subject: [ruby-changes:71631] 752c3dad98 (master): Unflag a splatted flagged hash if the method doesn't use ruby2_keywords

https://git.ruby-lang.org/ruby.git/commit/?id=752c3dad98

From 752c3dad989bb66e4be61911a82fed992067bdc3 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Fri, 11 Mar 2022 13:49:36 -0800
Subject: Unflag a splatted flagged hash if the method doesn't use
 ruby2_keywords

For a method such as:

  def foo(*callee_args) end

If this method is called with a flagged hash (created by a method
flagged with ruby2_keywords), this previously passed the hash
through without modification.  With this change, it acts as if the
last hash was passed as keywords, so a call to:

  foo(*caller_args)

where the last element of caller_args is a flagged hash, will be
treated as:

  foo(*caller_args[0...-1], **caller_args[-1])

As a result, inside foo, callee_args[-1] is an unflagged duplicate
of caller_args[-1] (all other elements of callee_args match
caller_args).

Fixes [Bug #18625]
---
 test/ruby/test_keyword.rb | 48 +++++++++++++++++++++++++++++++++++++++++++++++
 vm_args.c                 | 14 +++++++++++++-
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 0d766905bd..afed189078 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -190,6 +190,54 @@ class TestKeywordArguments < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L190
     assert_equal(["bar", 111111], f[str: "bar", num: 111111])
   end
 
+  def test_unset_hash_flag
+    bug18625 = "[ruby-core: 107847]"
+    singleton_class.class_eval do
+      ruby2_keywords def foo(*args)
+        args
+      end
+
+      def single(arg)
+        arg
+      end
+
+      def splat(*args)
+        args.last
+      end
+
+      def kwargs(**kw)
+        kw
+      end
+    end
+
+    h = { a: 1 }
+    args = foo(**h)
+    marked = args.last
+    assert_equal(true, Hash.ruby2_keywords_hash?(marked))
+
+    method_args = [args]
+    after_usage = single(*args)
+    assert_equal(h, after_usage)
+    assert_same(marked, args.last)
+    assert_not_same(marked, after_usage)
+    assert_equal(false, Hash.ruby2_keywords_hash?(after_usage))
+
+    after_usage = splat(*args)
+    assert_equal(h, after_usage)
+    assert_same(marked, args.last)
+    assert_not_same(marked, after_usage, bug18625)
+    assert_equal(false, Hash.ruby2_keywords_hash?(after_usage), bug18625)
+
+    after_usage = kwargs(*args)
+    assert_equal(h, after_usage)
+    assert_same(marked, args.last)
+    assert_not_same(marked, after_usage, bug18625)
+    assert_not_same(marked, after_usage)
+    assert_equal(false, Hash.ruby2_keywords_hash?(after_usage))
+
+    assert_equal(true, Hash.ruby2_keywords_hash?(marked))
+  end
+
   def test_keyword_splat_new
     kw = {}
     h = {a: 1}
diff --git a/vm_args.c b/vm_args.c
index a3ddf9a633..09137df908 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -468,7 +468,9 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co https://github.com/ruby/ruby/blob/trunk/vm_args.c#L468
     VALUE * const orig_sp = ec->cfp->sp;
     unsigned int i;
     VALUE flag_keyword_hash = 0;
+    VALUE splat_flagged_keyword_hash = 0;
     VALUE converted_keyword_hash = 0;
+    VALUE rest_last = 0;
 
     vm_check_canary(ec, orig_sp);
     /*
@@ -519,7 +521,6 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co https://github.com/ruby/ruby/blob/trunk/vm_args.c#L521
     }
 
     if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) {
-        VALUE rest_last = 0;
         int len;
 	args->rest = locals[--args->argc];
 	args->rest_index = 0;
@@ -530,6 +531,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co https://github.com/ruby/ruby/blob/trunk/vm_args.c#L531
         if (!kw_flag && len > 0) {
             if (RB_TYPE_P(rest_last, T_HASH) &&
                 (((struct RHash *)rest_last)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
+                splat_flagged_keyword_hash = rest_last;
                 rest_last = rb_hash_dup(rest_last);
                 kw_flag |= VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT;
             }
@@ -658,6 +660,16 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co https://github.com/ruby/ruby/blob/trunk/vm_args.c#L660
 
     if (ISEQ_BODY(iseq)->param.flags.has_rest) {
         args_setup_rest_parameter(args, locals + ISEQ_BODY(iseq)->param.rest_start);
+        VALUE ary = *(locals + ISEQ_BODY(iseq)->param.rest_start);
+        VALUE index = RARRAY_LEN(ary) - 1;
+        if (splat_flagged_keyword_hash &&
+            !ISEQ_BODY(iseq)->param.flags.ruby2_keywords &&
+            !ISEQ_BODY(iseq)->param.flags.has_kw &&
+            !ISEQ_BODY(iseq)->param.flags.has_kwrest &&
+            RARRAY_AREF(ary, index) == splat_flagged_keyword_hash) {
+            ((struct RHash *)rest_last)->basic.flags &= ~RHASH_PASS_AS_KEYWORDS;
+            RARRAY_ASET(ary, index, rest_last);
+        }
     }
 
     if (ISEQ_BODY(iseq)->param.flags.has_kw) {
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]