mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
[Bug #21333] Prohibit hash modification inside Hash#update block
This commit is contained in:
parent
a5da3682ef
commit
49b306ecb9
Notes:
git
2025-05-15 06:39:28 +00:00
2 changed files with 67 additions and 19 deletions
75
hash.c
75
hash.c
|
@ -4155,30 +4155,70 @@ rb_hash_update_i(VALUE key, VALUE value, VALUE hash)
|
||||||
return ST_CONTINUE;
|
return ST_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct update_call_args {
|
||||||
|
VALUE hash, newvalue, *argv;
|
||||||
|
int argc;
|
||||||
|
bool block_given;
|
||||||
|
bool iterating;
|
||||||
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing)
|
rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing)
|
||||||
{
|
{
|
||||||
st_data_t newvalue = arg->arg;
|
VALUE k = (VALUE)*key, v = (VALUE)*value;
|
||||||
|
struct update_call_args *ua = (void *)arg->arg;
|
||||||
|
VALUE newvalue = ua->newvalue, hash = arg->hash;
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue);
|
hash_iter_lev_inc(hash);
|
||||||
|
ua->iterating = true;
|
||||||
|
newvalue = rb_yield_values(3, k, v, newvalue);
|
||||||
|
hash_iter_lev_dec(hash);
|
||||||
|
ua->iterating = false;
|
||||||
}
|
}
|
||||||
else if (RHASH_STRING_KEY_P(arg->hash, *key) && !RB_OBJ_FROZEN(*key)) {
|
else if (RHASH_STRING_KEY_P(hash, k) && !RB_OBJ_FROZEN(k)) {
|
||||||
*key = rb_hash_key_str(*key);
|
*key = (st_data_t)rb_hash_key_str(k);
|
||||||
}
|
}
|
||||||
*value = newvalue;
|
*value = (st_data_t)newvalue;
|
||||||
return ST_CONTINUE;
|
return ST_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
NOINSERT_UPDATE_CALLBACK(rb_hash_update_block_callback)
|
NOINSERT_UPDATE_CALLBACK(rb_hash_update_block_callback)
|
||||||
|
|
||||||
static int
|
static int
|
||||||
rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
|
rb_hash_update_block_i(VALUE key, VALUE value, VALUE args)
|
||||||
{
|
{
|
||||||
RHASH_UPDATE(hash, key, rb_hash_update_block_callback, value);
|
struct update_call_args *ua = (void *)args;
|
||||||
|
ua->newvalue = value;
|
||||||
|
RHASH_UPDATE(ua->hash, key, rb_hash_update_block_callback, args);
|
||||||
return ST_CONTINUE;
|
return ST_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
rb_hash_update_call(VALUE args)
|
||||||
|
{
|
||||||
|
struct update_call_args *arg = (void *)args;
|
||||||
|
|
||||||
|
for (int i = 0; i < arg->argc; i++){
|
||||||
|
VALUE hash = to_hash(arg->argv[i]);
|
||||||
|
if (arg->block_given) {
|
||||||
|
rb_hash_foreach(hash, rb_hash_update_block_i, args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rb_hash_foreach(hash, rb_hash_update_i, arg->hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
rb_hash_update_ensure(VALUE args)
|
||||||
|
{
|
||||||
|
struct update_call_args *ua = (void *)args;
|
||||||
|
if (ua->iterating) hash_iter_lev_dec(ua->hash);
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* update(*other_hashes) -> self
|
* update(*other_hashes) -> self
|
||||||
|
@ -4225,20 +4265,17 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
|
||||||
static VALUE
|
static VALUE
|
||||||
rb_hash_update(int argc, VALUE *argv, VALUE self)
|
rb_hash_update(int argc, VALUE *argv, VALUE self)
|
||||||
{
|
{
|
||||||
int i;
|
struct update_call_args args = {
|
||||||
bool block_given = rb_block_given_p();
|
.hash = self,
|
||||||
|
.argv = argv,
|
||||||
|
.argc = argc,
|
||||||
|
.block_given = rb_block_given_p(),
|
||||||
|
.iterating = false,
|
||||||
|
};
|
||||||
|
VALUE arg = (VALUE)&args;
|
||||||
|
|
||||||
rb_hash_modify(self);
|
rb_hash_modify(self);
|
||||||
for (i = 0; i < argc; i++){
|
return rb_ensure(rb_hash_update_call, arg, rb_hash_update_ensure, arg);
|
||||||
VALUE hash = to_hash(argv[i]);
|
|
||||||
if (block_given) {
|
|
||||||
rb_hash_foreach(hash, rb_hash_update_block_i, self);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rb_hash_foreach(hash, rb_hash_update_i, self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct update_func_arg {
|
struct update_func_arg {
|
||||||
|
|
|
@ -1297,6 +1297,17 @@ class TestHash < Test::Unit::TestCase
|
||||||
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
|
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_update_modify_in_block
|
||||||
|
a = @cls[]
|
||||||
|
(1..1337).each {|k| a[k] = k}
|
||||||
|
b = {1=>1338}
|
||||||
|
assert_raise_with_message(RuntimeError, /rehash during iteration/) do
|
||||||
|
a.update(b) {|k, o, n|
|
||||||
|
a.rehash
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_update_on_identhash
|
def test_update_on_identhash
|
||||||
key = +'a'
|
key = +'a'
|
||||||
i = @cls[].compare_by_identity
|
i = @cls[].compare_by_identity
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue