Fix crash when replacing ST hash with AR hash

With VWA, AR hashes are much larger than ST hashes. Hash#replace
attempts to directly copy the contents of AR hashes into ST hashes so
there will be memory corruption caused by writing past the end of memory.

This commit changes it so that if a ST hash is being replaced with an AR
hash it will insert each element into the ST hash.
This commit is contained in:
Peter Zhu 2023-05-23 13:45:44 -04:00
parent 061e01ee50
commit a86f798fc2
Notes: git 2023-05-23 19:28:20 +00:00
3 changed files with 37 additions and 4 deletions

30
hash.c
View File

@ -2906,14 +2906,38 @@ rb_hash_replace(VALUE hash, VALUE hash2)
else {
RHASH_ST_CLEAR(hash);
}
hash_copy(hash, hash2);
if (RHASH_AR_TABLE_P(hash2)) {
if (RHASH_AR_TABLE_P(hash)) {
ar_copy(hash, hash2);
}
else {
st_table *tab = RHASH_ST_TABLE(hash);
rb_st_init_existing_table_with_size(tab, &objhash, RHASH_AR_TABLE_SIZE(hash2));
int bound = RHASH_AR_TABLE_BOUND(hash2);
for (int i = 0; i < bound; i++) {
if (ar_cleared_entry(hash2, i)) continue;
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash2, i);
st_add_direct(tab, pair->key, pair->val);
RB_OBJ_WRITTEN(hash, Qundef, pair->key);
RB_OBJ_WRITTEN(hash, Qundef, pair->val);
}
}
}
else {
HASH_ASSERT(sizeof(st_table) <= sizeof(ar_table));
RHASH_ST_TABLE_SET(hash, st_copy(RHASH_ST_TABLE(hash2)));
rb_gc_writebarrier_remember(hash);
}
if (RHASH_EMPTY_P(hash2) && RHASH_ST_TABLE_P(hash2)) {
/* ident hash */
hash_st_table_init(hash, RHASH_TYPE(hash2), 0);
}
rb_gc_writebarrier_remember(hash);
return hash;
}

View File

@ -179,7 +179,6 @@ static inline void
RHASH_ST_CLEAR(VALUE h)
{
memset(RHASH_ST_TABLE(h), 0, sizeof(st_table));
FL_UNSET_RAW(h, RHASH_ST_TABLE_FLAG);
}
static inline unsigned

View File

@ -850,6 +850,16 @@ class TestHash < Test::Unit::TestCase
assert(true)
end
def test_replace_st_with_ar
# ST hash
h1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9 }
# AR hash
h2 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 }
# Replace ST hash with AR hash
h1.replace(h2)
assert_equal(h2, h1)
end
def test_shift
h = @h.dup