summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2022-03-11 13:49:36 -0800
committerBenoit Daloze <eregontp@gmail.com>2022-04-05 11:42:02 +0200
commit752c3dad989bb66e4be61911a82fed992067bdc3 (patch)
tree7e45be9773104da5d4cfcebaa53052e4e631dd80
parent5e7ebc7e6e626db01766294edbe41019b98b2e84 (diff)
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]
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/5684
-rw-r--r--test/ruby/test_keyword.rb48
-rw-r--r--vm_args.c14
2 files changed, 61 insertions, 1 deletions
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
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
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
}
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
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
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) {