Skip to content

Commit 680ec80

Browse files
committed
[Bug #21333] Prohibit hash modification inside Hash#update block
1 parent 7afee53 commit 680ec80

2 files changed

Lines changed: 67 additions & 19 deletions

File tree

hash.c

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4155,30 +4155,70 @@ rb_hash_update_i(VALUE key, VALUE value, VALUE hash)
41554155
return ST_CONTINUE;
41564156
}
41574157

4158+
struct update_call_args {
4159+
VALUE hash, newvalue, *argv;
4160+
int argc;
4161+
bool block_given;
4162+
bool iterating;
4163+
};
4164+
41584165
static int
41594166
rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing)
41604167
{
4161-
st_data_t newvalue = arg->arg;
4168+
VALUE k = (VALUE)*key, v = (VALUE)*value;
4169+
struct update_call_args *ua = (void *)arg->arg;
4170+
VALUE newvalue = ua->newvalue, hash = arg->hash;
41624171

41634172
if (existing) {
4164-
newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue);
4173+
hash_iter_lev_inc(hash);
4174+
ua->iterating = true;
4175+
newvalue = rb_yield_values(3, k, v, newvalue);
4176+
hash_iter_lev_dec(hash);
4177+
ua->iterating = false;
41654178
}
4166-
else if (RHASH_STRING_KEY_P(arg->hash, *key) && !RB_OBJ_FROZEN(*key)) {
4167-
*key = rb_hash_key_str(*key);
4179+
else if (RHASH_STRING_KEY_P(hash, k) && !RB_OBJ_FROZEN(k)) {
4180+
*key = (st_data_t)rb_hash_key_str(k);
41684181
}
4169-
*value = newvalue;
4182+
*value = (st_data_t)newvalue;
41704183
return ST_CONTINUE;
41714184
}
41724185

41734186
NOINSERT_UPDATE_CALLBACK(rb_hash_update_block_callback)
41744187

41754188
static int
4176-
rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
4189+
rb_hash_update_block_i(VALUE key, VALUE value, VALUE args)
41774190
{
4178-
RHASH_UPDATE(hash, key, rb_hash_update_block_callback, value);
4191+
struct update_call_args *ua = (void *)args;
4192+
ua->newvalue = value;
4193+
RHASH_UPDATE(ua->hash, key, rb_hash_update_block_callback, args);
41794194
return ST_CONTINUE;
41804195
}
41814196

4197+
static VALUE
4198+
rb_hash_update_call(VALUE args)
4199+
{
4200+
struct update_call_args *arg = (void *)args;
4201+
4202+
for (int i = 0; i < arg->argc; i++){
4203+
VALUE hash = to_hash(arg->argv[i]);
4204+
if (arg->block_given) {
4205+
rb_hash_foreach(hash, rb_hash_update_block_i, args);
4206+
}
4207+
else {
4208+
rb_hash_foreach(hash, rb_hash_update_i, arg->hash);
4209+
}
4210+
}
4211+
return arg->hash;
4212+
}
4213+
4214+
static VALUE
4215+
rb_hash_update_ensure(VALUE args)
4216+
{
4217+
struct update_call_args *ua = (void *)args;
4218+
if (ua->iterating) hash_iter_lev_dec(ua->hash);
4219+
return Qnil;
4220+
}
4221+
41824222
/*
41834223
* call-seq:
41844224
* update(*other_hashes) -> self
@@ -4225,20 +4265,17 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
42254265
static VALUE
42264266
rb_hash_update(int argc, VALUE *argv, VALUE self)
42274267
{
4228-
int i;
4229-
bool block_given = rb_block_given_p();
4268+
struct update_call_args args = {
4269+
.hash = self,
4270+
.argv = argv,
4271+
.argc = argc,
4272+
.block_given = rb_block_given_p(),
4273+
.iterating = false,
4274+
};
4275+
VALUE arg = (VALUE)&args;
42304276

42314277
rb_hash_modify(self);
4232-
for (i = 0; i < argc; i++){
4233-
VALUE hash = to_hash(argv[i]);
4234-
if (block_given) {
4235-
rb_hash_foreach(hash, rb_hash_update_block_i, self);
4236-
}
4237-
else {
4238-
rb_hash_foreach(hash, rb_hash_update_i, self);
4239-
}
4240-
}
4241-
return self;
4278+
return rb_ensure(rb_hash_update_call, arg, rb_hash_update_ensure, arg);
42424279
}
42434280

42444281
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

0 commit comments

Comments
 (0)