1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Fix evaluation order of hash values for duplicate keys

Fixes [Bug #17719]

Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Ivo Anjo <ivo@ivoanjo.me>
This commit is contained in:
Jeremy Evans 2021-10-18 07:09:07 -09:00 committed by GitHub
parent 59bec48e48
commit fac2c0f73c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
Notes: git 2021-10-19 01:09:35 +09:00
Merged: https://github.com/ruby/ruby/pull/4969

Merged-By: jeremyevans <code@jeremyevans.net>
3 changed files with 41 additions and 12 deletions

20
parse.y
View file

@ -12246,24 +12246,30 @@ remove_duplicate_keys(struct parser_params *p, NODE *hash)
{ {
st_table *literal_keys = st_init_table_with_size(&literal_type, hash->nd_alen / 2); st_table *literal_keys = st_init_table_with_size(&literal_type, hash->nd_alen / 2);
NODE *result = 0; NODE *result = 0;
NODE *last_expr = 0;
rb_code_location_t loc = hash->nd_loc; rb_code_location_t loc = hash->nd_loc;
while (hash && hash->nd_head && hash->nd_next) { while (hash && hash->nd_head && hash->nd_next) {
NODE *head = hash->nd_head; NODE *head = hash->nd_head;
NODE *value = hash->nd_next; NODE *value = hash->nd_next;
NODE *next = value->nd_next; NODE *next = value->nd_next;
VALUE key = (VALUE)head; st_data_t key = (st_data_t)head;
st_data_t data; st_data_t data;
value->nd_next = 0;
if (nd_type(head) == NODE_LIT && if (nd_type(head) == NODE_LIT &&
st_lookup(literal_keys, (key = head->nd_lit), &data)) { st_delete(literal_keys, (key = (st_data_t)head->nd_lit, &key), &data)) {
NODE *dup_value = ((NODE *)data)->nd_next;
rb_compile_warn(p->ruby_sourcefile, nd_line((NODE *)data), rb_compile_warn(p->ruby_sourcefile, nd_line((NODE *)data),
"key %+"PRIsVALUE" is duplicated and overwritten on line %d", "key %+"PRIsVALUE" is duplicated and overwritten on line %d",
head->nd_lit, nd_line(head)); head->nd_lit, nd_line(head));
head = ((NODE *)data)->nd_next; if (dup_value == last_expr) {
head->nd_head = block_append(p, head->nd_head, value->nd_head); value->nd_head = block_append(p, dup_value->nd_head, value->nd_head);
} }
else { else {
st_insert(literal_keys, (st_data_t)key, (st_data_t)hash); last_expr->nd_head = block_append(p, dup_value->nd_head, last_expr->nd_head);
}
} }
st_insert(literal_keys, (st_data_t)key, (st_data_t)hash);
last_expr = nd_type(head) == NODE_LIT ? value : head;
hash = next; hash = next;
} }
st_foreach(literal_keys, append_literal_keys, (st_data_t)&result); st_foreach(literal_keys, append_literal_keys, (st_data_t)&result);

View file

@ -474,6 +474,17 @@ class TestRubyLiteral < Test::Unit::TestCase
assert_nil(h['c']) assert_nil(h['c'])
assert_equal(nil, h.key('300')) assert_equal(nil, h.key('300'))
a = []
h = EnvUtil.suppress_warning do
eval <<~end
# This is a syntax that renders warning at very early stage.
# eval used to delay warning, to be suppressible by EnvUtil.
{"a" => a.push(100).last, "b" => a.push(200).last, "a" => a.push(300).last, "a" => a.push(400).last}
end
end
assert_equal({'a' => 400, 'b' => 200}, h)
assert_equal([100, 200, 300, 400], a)
assert_all_assertions_foreach( assert_all_assertions_foreach(
"duplicated literal key", "duplicated literal key",
':foo', ':foo',

View file

@ -200,17 +200,29 @@ class TestSyntax < Test::Unit::TestCase
bug10315 = '[ruby-core:65625] [Bug #10315]' bug10315 = '[ruby-core:65625] [Bug #10315]'
a = [] a = []
def a.add(x) push(x); x; end def a.add(x) push(x); x; end
def a.f(k:) k; end b = a.clone
def a.f(k:, **) k; end
def b.f(k:) k; end
a.clear a.clear
r = nil r = nil
assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), k: a.add(2))")} assert_warn(/duplicated/) {r = eval("b.f(k: b.add(1), k: b.add(2))")}
assert_equal(2, r) assert_equal(2, r)
assert_equal([1, 2], a, bug10315) assert_equal([1, 2], b, bug10315)
b.clear
r = nil
assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4))")}
assert_equal(4, r)
assert_equal([1, 2, 3, 4], a)
a.clear a.clear
r = nil r = nil
assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), k: a.add(2)})")} assert_warn(/duplicated/) {r = eval("b.f(**{k: b.add(1), k: b.add(2)})")}
assert_equal(2, r) assert_equal(2, r)
assert_equal([1, 2], a, bug10315) assert_equal([1, 2], b, bug10315)
b.clear
r = nil
assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4)})")}
assert_equal(4, r)
assert_equal([1, 2, 3, 4], a)
end end
def test_keyword_empty_splat def test_keyword_empty_splat