Skip to content

Commit f0d08d1

Browse files
committed
Fix memory leak when copying ST tables
st_copy allocates a st_table, which is not needed for hashes since it is allocated by VWA and embedded, so this causes a memory leak. The following script demonstrates the issue: ```ruby 20.times do 100_000.times do {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9} end puts `ps -o rss= -p #{$$}` end ```
1 parent df2b3a2 commit f0d08d1

File tree

5 files changed

+33
-13
lines changed

5 files changed

+33
-13
lines changed

hash.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,9 @@ hash_copy(VALUE ret, VALUE hash)
14821482
else {
14831483
HASH_ASSERT(sizeof(st_table) <= sizeof(ar_table));
14841484

1485-
RHASH_ST_TABLE_SET(ret, st_copy(RHASH_ST_TABLE(hash)));
1485+
RHASH_SET_ST_FLAG(ret);
1486+
st_replace(RHASH_ST_TABLE(ret), RHASH_ST_TABLE(hash));
1487+
14861488
rb_gc_writebarrier_remember(ret);
14871489
}
14881490
return ret;
@@ -1776,7 +1778,8 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
17761778
}
17771779
else {
17781780
hash = hash_alloc(klass);
1779-
hash_copy(hash, tmp);
1781+
if (!RHASH_EMPTY_P(tmp))
1782+
hash_copy(hash, tmp);
17801783
return hash;
17811784
}
17821785
}

include/ruby/st.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ void rb_st_cleanup_safe(st_table *, st_data_t);
162162
#define st_cleanup_safe rb_st_cleanup_safe
163163
void rb_st_clear(st_table *);
164164
#define st_clear rb_st_clear
165+
st_table *rb_st_replace(st_table *new_tab, st_table *old_tab);
166+
#define st_replace rb_st_replace
165167
st_table *rb_st_copy(st_table *);
166168
#define st_copy rb_st_copy
167169
CONSTFUNC(int rb_st_numcmp(st_data_t, st_data_t));

parser_st.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ nonempty_memcpy(void *dest, const void *src, size_t n)
109109
#define st_add_direct rb_parser_st_add_direct
110110
#undef st_insert2
111111
#define st_insert2 rb_parser_st_insert2
112+
#undef st_replace
113+
#define st_replace rb_parser_st_replace
112114
#undef st_copy
113115
#define st_copy rb_parser_st_copy
114116
#undef st_delete_safe

parser_st.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ void rb_parser_st_add_direct(parser_st_table *, parser_st_data_t, parser_st_data
137137
void rb_parser_st_free_table(parser_st_table *);
138138
void rb_parser_st_cleanup_safe(parser_st_table *, parser_st_data_t);
139139
void rb_parser_st_clear(parser_st_table *);
140+
parser_st_table *rb_parser_st_replace(parser_st_table *, parser_st_table *);
140141
parser_st_table *rb_parser_st_copy(parser_st_table *);
141142
CONSTFUNC(int rb_parser_st_numcmp(parser_st_data_t, parser_st_data_t));
142143
CONSTFUNC(parser_st_index_t rb_parser_st_numhash(parser_st_data_t));

st.c

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,25 +1228,17 @@ st_insert2(st_table *tab, st_data_t key, st_data_t value,
12281228
return 1;
12291229
}
12301230

1231-
/* Create and return a copy of table OLD_TAB. */
1231+
/* Create a copy of old_tab into new_tab. */
12321232
st_table *
1233-
st_copy(st_table *old_tab)
1233+
st_replace(st_table *new_tab, st_table *old_tab)
12341234
{
1235-
st_table *new_tab;
1236-
1237-
new_tab = (st_table *) malloc(sizeof(st_table));
1238-
#ifndef RUBY
1239-
if (new_tab == NULL)
1240-
return NULL;
1241-
#endif
12421235
*new_tab = *old_tab;
12431236
if (old_tab->bins == NULL)
12441237
new_tab->bins = NULL;
12451238
else {
12461239
new_tab->bins = (st_index_t *) malloc(bins_size(old_tab));
12471240
#ifndef RUBY
12481241
if (new_tab->bins == NULL) {
1249-
free(new_tab);
12501242
return NULL;
12511243
}
12521244
#endif
@@ -1255,14 +1247,34 @@ st_copy(st_table *old_tab)
12551247
* sizeof(st_table_entry));
12561248
#ifndef RUBY
12571249
if (new_tab->entries == NULL) {
1258-
st_free_table(new_tab);
12591250
return NULL;
12601251
}
12611252
#endif
12621253
MEMCPY(new_tab->entries, old_tab->entries, st_table_entry,
12631254
get_allocated_entries(old_tab));
12641255
if (old_tab->bins != NULL)
12651256
MEMCPY(new_tab->bins, old_tab->bins, char, bins_size(old_tab));
1257+
1258+
return new_tab;
1259+
}
1260+
1261+
/* Create and return a copy of table OLD_TAB. */
1262+
st_table *
1263+
st_copy(st_table *old_tab)
1264+
{
1265+
st_table *new_tab;
1266+
1267+
new_tab = (st_table *) malloc(sizeof(st_table));
1268+
#ifndef RUBY
1269+
if (new_tab == NULL)
1270+
return NULL;
1271+
#endif
1272+
1273+
if (st_replace(new_tab, old_tab) == NULL) {
1274+
st_free_table(new_tab);
1275+
return NULL;
1276+
}
1277+
12661278
return new_tab;
12671279
}
12681280

0 commit comments

Comments
 (0)