summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornagachika <[email protected]>2025-05-24 10:25:59 +0900
committernagachika <[email protected]>2025-05-24 10:25:59 +0900
commit24c994b91a67f0023e8fe22a5428581110564332 (patch)
treec2b8debe8d5ea990c27861119b96d62f2c5aa84f
parent8f895758d9512466e9ec24ce8e588f059c16136c (diff)
merge revision(s) 056497319658cbefe22351c6ec5c9fa6e4df72bd: [Backport #21357]
[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.
-rw-r--r--hash.c4
-rw-r--r--test/ruby/test_hash.rb5
-rw-r--r--version.h2
3 files changed, 8 insertions, 3 deletions
diff --git a/hash.c b/hash.c
index b68965e074..2142132597 100644
--- a/hash.c
+++ b/hash.c
@@ -1710,14 +1710,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 1fc5295707..6fa8087100 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -2315,6 +2315,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;'}")
diff --git a/version.h b/version.h
index 05452cb67a..3b8d50b4bf 100644
--- a/version.h
+++ b/version.h
@@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 8
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
-#define RUBY_PATCHLEVEL 154
+#define RUBY_PATCHLEVEL 155
#include "ruby/version.h"
#include "ruby/internal/abi.h"