From 5550a28098602b8fe0a68ac2529ffe2695beb589 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Fri, 23 Aug 2019 15:38:06 -0400 Subject: [PATCH 2/4] Fix freeing and clearing destination hash in Hash#initialize_copy The code was assuming the state of the destination hash based on the source hash for clearing any existing table on it. If these don't match, then that can cause the old table to be leaked. This can be seen by compiling hash.c with `#define HASH_DEBUG 1` and running the following script, which will crash from a debug assertion. ```ruby h = 9.times.map { |i| [i, i] }.to_h h.send(:initialize_copy, {}) ``` --- hash.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hash.c b/hash.c index f91958ff0f..ee5dc0e004 100644 --- a/hash.c +++ b/hash.c @@ -2792,14 +2792,20 @@ rb_hash_initialize_copy(VALUE hash, VALUE hash2) if (hash == hash2) return hash; + if (RHASH_ST_TABLE_P(hash)) { + st_free_table(RHASH_ST_TABLE(hash)); + RHASH_ST_CLEAR(hash); + } + else { + ar_free_and_clear_table(hash); + } + if (RHASH_AR_TABLE_P(hash2)) { - if (RHASH_AR_TABLE_P(hash)) ar_free_and_clear_table(hash); ar_copy(hash, hash2); if (RHASH_AR_TABLE_SIZE(hash)) rb_hash_rehash(hash); } else if (RHASH_ST_TABLE_P(hash2)) { - if (RHASH_ST_TABLE_P(hash)) st_free_table(RHASH_ST_TABLE(hash)); RHASH_ST_TABLE_SET(hash, st_copy(RHASH_ST_TABLE(hash2))); if (RHASH_ST_TABLE(hash)->num_entries) rb_hash_rehash(hash); -- 2.21.0