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:
parent
59bec48e48
commit
fac2c0f73c
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
20
parse.y
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue