Skip to content

Commit 247b452

Browse files
committed
merge revision(s) 49b306e: [Backport #21333]
[Bug #21333] Prohibit hash modification inside Hash#update block
1 parent 1e3d24a commit 247b452

File tree

3 files changed

+68
-20
lines changed

3 files changed

+68
-20
lines changed

hash.c

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3942,30 +3942,70 @@ rb_hash_update_i(VALUE key, VALUE value, VALUE hash)
39423942
return ST_CONTINUE;
39433943
}
39443944

3945+
struct update_call_args {
3946+
VALUE hash, newvalue, *argv;
3947+
int argc;
3948+
bool block_given;
3949+
bool iterating;
3950+
};
3951+
39453952
static int
39463953
rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing)
39473954
{
3948-
st_data_t newvalue = arg->arg;
3955+
VALUE k = (VALUE)*key, v = (VALUE)*value;
3956+
struct update_call_args *ua = (void *)arg->arg;
3957+
VALUE newvalue = ua->newvalue, hash = arg->hash;
39493958

39503959
if (existing) {
3951-
newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue);
3960+
hash_iter_lev_inc(hash);
3961+
ua->iterating = true;
3962+
newvalue = rb_yield_values(3, k, v, newvalue);
3963+
hash_iter_lev_dec(hash);
3964+
ua->iterating = false;
39523965
}
3953-
else if (RHASH_STRING_KEY_P(arg->hash, *key) && !RB_OBJ_FROZEN(*key)) {
3954-
*key = rb_hash_key_str(*key);
3966+
else if (RHASH_STRING_KEY_P(hash, k) && !RB_OBJ_FROZEN(k)) {
3967+
*key = (st_data_t)rb_hash_key_str(k);
39553968
}
3956-
*value = newvalue;
3969+
*value = (st_data_t)newvalue;
39573970
return ST_CONTINUE;
39583971
}
39593972

39603973
NOINSERT_UPDATE_CALLBACK(rb_hash_update_block_callback)
39613974

39623975
static int
3963-
rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
3976+
rb_hash_update_block_i(VALUE key, VALUE value, VALUE args)
39643977
{
3965-
RHASH_UPDATE(hash, key, rb_hash_update_block_callback, value);
3978+
struct update_call_args *ua = (void *)args;
3979+
ua->newvalue = value;
3980+
RHASH_UPDATE(ua->hash, key, rb_hash_update_block_callback, args);
39663981
return ST_CONTINUE;
39673982
}
39683983

3984+
static VALUE
3985+
rb_hash_update_call(VALUE args)
3986+
{
3987+
struct update_call_args *arg = (void *)args;
3988+
3989+
for (int i = 0; i < arg->argc; i++){
3990+
VALUE hash = to_hash(arg->argv[i]);
3991+
if (arg->block_given) {
3992+
rb_hash_foreach(hash, rb_hash_update_block_i, args);
3993+
}
3994+
else {
3995+
rb_hash_foreach(hash, rb_hash_update_i, arg->hash);
3996+
}
3997+
}
3998+
return arg->hash;
3999+
}
4000+
4001+
static VALUE
4002+
rb_hash_update_ensure(VALUE args)
4003+
{
4004+
struct update_call_args *ua = (void *)args;
4005+
if (ua->iterating) hash_iter_lev_dec(ua->hash);
4006+
return Qnil;
4007+
}
4008+
39694009
/*
39704010
* call-seq:
39714011
* hash.merge! -> self
@@ -4017,20 +4057,17 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
40174057
static VALUE
40184058
rb_hash_update(int argc, VALUE *argv, VALUE self)
40194059
{
4020-
int i;
4021-
bool block_given = rb_block_given_p();
4060+
struct update_call_args args = {
4061+
.hash = self,
4062+
.argv = argv,
4063+
.argc = argc,
4064+
.block_given = rb_block_given_p(),
4065+
.iterating = false,
4066+
};
4067+
VALUE arg = (VALUE)&args;
40224068

40234069
rb_hash_modify(self);
4024-
for (i = 0; i < argc; i++){
4025-
VALUE hash = to_hash(argv[i]);
4026-
if (block_given) {
4027-
rb_hash_foreach(hash, rb_hash_update_block_i, self);
4028-
}
4029-
else {
4030-
rb_hash_foreach(hash, rb_hash_update_i, self);
4031-
}
4032-
}
4033-
return self;
4070+
return rb_ensure(rb_hash_update_call, arg, rb_hash_update_ensure, arg);
40344071
}
40354072

40364073
struct update_func_arg {

test/ruby/test_hash.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,17 @@ def test_update5
12971297
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
12981298
end
12991299

1300+
def test_update_modify_in_block
1301+
a = @cls[]
1302+
(1..1337).each {|k| a[k] = k}
1303+
b = {1=>1338}
1304+
assert_raise_with_message(RuntimeError, /rehash during iteration/) do
1305+
a.update(b) {|k, o, n|
1306+
a.rehash
1307+
}
1308+
end
1309+
end
1310+
13001311
def test_update_on_identhash
13011312
key = +'a'
13021313
i = @cls[].compare_by_identity

version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
1212
#define RUBY_VERSION_TEENY 4
1313
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
14-
#define RUBY_PATCHLEVEL 34
14+
#define RUBY_PATCHLEVEL 35
1515

1616
#include "ruby/version.h"
1717
#include "ruby/internal/abi.h"

0 commit comments

Comments
 (0)