diff options
author | Peter Zhu <[email protected]> | 2023-05-23 13:45:44 -0400 |
---|---|---|
committer | Peter Zhu <[email protected]> | 2023-05-23 15:27:56 -0400 |
commit | a86f798fc2e979ea83ec55744f906b2a816c8681 (patch) | |
tree | 897aff087429971d6e7532572a42e46a759338fa | |
parent | 061e01ee5088b491ab702a567eb0d5bed1c5d429 (diff) |
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.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/7846
-rw-r--r-- | hash.c | 30 | ||||
-rw-r--r-- | internal/hash.h | 1 | ||||
-rw-r--r-- | test/ruby/test_hash.rb | 10 |
3 files changed, 37 insertions, 4 deletions
@@ -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; } diff --git a/internal/hash.h b/internal/hash.h index 248a53429d..3874abeaa5 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -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 diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 8b6065146c..d9d1ca7dde 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -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 |