From 056497319658cbefe22351c6ec5c9fa6e4df72bd Mon Sep 17 00:00:00 2001 From: Daniel Colson Date: Wed, 21 May 2025 22:00:12 -0400 Subject: [PATCH] [Bug #21357] Fix crash in Hash#merge with block Prior to https://github.com/ruby/ruby/commit/49b306ecb9e2e9e06e0b1590bacc5f4b38169c3c the `optional_arg` passed from `rb_hash_update_block_i` to `tbl_update` was a hash value (i.e. a VALUE). After that commit it changed to an `update_call_args`. If the block sets or changes the value, `tbl_update_modify` will set the `arg.value` back to an actual value and we won't crash. But in the case where the block returns the original value we end up calling `RB_OBJ_WRITTEN` with the `update_call_args` which is not expected and may crash. `arg.value` appears to only be used to pass to `RB_OBJ_WRITTEN` (others who need the `update_call_args` get it from `arg.arg`), so I don't think it needs to be set to anything upfront. And `tbl_update_modify` will set the `arg.value` in the cases we need the write barrier. --- hash.c | 4 ++-- test/ruby/test_hash.rb | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hash.c b/hash.c index 14f3b038f9..608738aab5 100644 --- a/hash.c +++ b/hash.c @@ -1723,14 +1723,14 @@ tbl_update(VALUE hash, VALUE key, tbl_update_func func, st_data_t optional_arg) .func = func, .hash = hash, .key = key, - .value = (VALUE)optional_arg, + .value = 0 }; int ret = rb_hash_stlike_update(hash, key, tbl_update_modify, (st_data_t)&arg); /* write barrier */ RB_OBJ_WRITTEN(hash, Qundef, arg.key); - RB_OBJ_WRITTEN(hash, Qundef, arg.value); + if (arg.value) RB_OBJ_WRITTEN(hash, Qundef, arg.value); return ret; } diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index e75cdfe7f9..7b8cf1c6c4 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -2355,6 +2355,11 @@ class TestHashOnly < Test::Unit::TestCase end end + def test_bug_21357 + h = {x: []}.merge(x: nil) { |_k, v1, _v2| v1 } + assert_equal({x: []}, h) + end + def test_any_hash_fixable 20.times do assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")