Skip to content

Commit d2dc827

Browse files
authored
bpo-40602: _Py_hashtable_set() reports rehash failure (GH-20077)
If _Py_hashtable_set() fails to grow the hash table (rehash), it now fails rather than ignoring the error.
1 parent ce21cfc commit d2dc827

File tree

2 files changed

+26
-14
lines changed

2 files changed

+26
-14
lines changed

Modules/_testinternalcapi.c

+9-5
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
9898
return PyErr_NoMemory();
9999
}
100100

101+
// Using an newly allocated table must not crash
102+
assert(table->nentries == 0);
103+
assert(table->nbuckets > 0);
104+
assert(_Py_hashtable_get(table, TO_PTR('x')) == NULL);
105+
101106
// Test _Py_hashtable_set()
102107
char key;
103108
for (key='a'; key <= 'z'; key++) {
@@ -121,17 +126,15 @@ test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
121126
// Test _Py_hashtable_get()
122127
for (key='a'; key <= 'z'; key++) {
123128
void *value_ptr = _Py_hashtable_get(table, TO_PTR(key));
124-
int value = (int)FROM_PTR(value_ptr);
125-
assert(value == VALUE(key));
129+
assert((int)FROM_PTR(value_ptr) == VALUE(key));
126130
}
127131

128132
// Test _Py_hashtable_steal()
129133
key = 'p';
130134
void *value_ptr = _Py_hashtable_steal(table, TO_PTR(key));
131-
int value = (int)FROM_PTR(value_ptr);
132-
assert(value == VALUE(key));
133-
135+
assert((int)FROM_PTR(value_ptr) == VALUE(key));
134136
assert(table->nentries == 25);
137+
assert(_Py_hashtable_get_entry(table, TO_PTR(key)) == NULL);
135138

136139
// Test _Py_hashtable_foreach()
137140
int count = 0;
@@ -142,6 +145,7 @@ test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
142145
// Test _Py_hashtable_clear()
143146
_Py_hashtable_clear(table);
144147
assert(table->nentries == 0);
148+
assert(table->nbuckets > 0);
145149
assert(_Py_hashtable_get(table, TO_PTR('x')) == NULL);
146150

147151
_Py_hashtable_destroy(table);

Python/hashtable.c

+17-9
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
((_Py_hashtable_entry_t *)_Py_SLIST_ITEM_NEXT(ENTRY))
6161

6262
/* Forward declaration */
63-
static void hashtable_rehash(_Py_hashtable_t *ht);
63+
static int hashtable_rehash(_Py_hashtable_t *ht);
6464

6565
static void
6666
_Py_slist_init(_Py_slist_t *list)
@@ -198,6 +198,7 @@ _Py_hashtable_steal(_Py_hashtable_t *ht, const void *key)
198198
ht->alloc.free(entry);
199199

200200
if ((float)ht->nentries / (float)ht->nbuckets < HASHTABLE_LOW) {
201+
// Ignore failure: error cannot be reported to the caller
201202
hashtable_rehash(ht);
202203
}
203204
return value;
@@ -228,13 +229,17 @@ _Py_hashtable_set(_Py_hashtable_t *ht, const void *key, void *value)
228229
entry->key = (void *)key;
229230
entry->value = value;
230231

231-
size_t index = entry->key_hash & (ht->nbuckets - 1);
232-
_Py_slist_prepend(&ht->buckets[index], (_Py_slist_item_t*)entry);
233232
ht->nentries++;
234-
235233
if ((float)ht->nentries / (float)ht->nbuckets > HASHTABLE_HIGH) {
236-
hashtable_rehash(ht);
234+
if (hashtable_rehash(ht) < 0) {
235+
ht->nentries--;
236+
ht->alloc.free(entry);
237+
return -1;
238+
}
237239
}
240+
241+
size_t index = entry->key_hash & (ht->nbuckets - 1);
242+
_Py_slist_prepend(&ht->buckets[index], (_Py_slist_item_t*)entry);
238243
return 0;
239244
}
240245

@@ -271,19 +276,19 @@ _Py_hashtable_foreach(_Py_hashtable_t *ht,
271276
}
272277

273278

274-
static void
279+
static int
275280
hashtable_rehash(_Py_hashtable_t *ht)
276281
{
277282
size_t new_size = round_size((size_t)(ht->nentries * HASHTABLE_REHASH_FACTOR));
278283
if (new_size == ht->nbuckets) {
279-
return;
284+
return 0;
280285
}
281286

282287
size_t buckets_size = new_size * sizeof(ht->buckets[0]);
283288
_Py_slist_t *new_buckets = ht->alloc.malloc(buckets_size);
284289
if (new_buckets == NULL) {
285290
/* memory allocation failed */
286-
return;
291+
return -1;
287292
}
288293
memset(new_buckets, 0, buckets_size);
289294

@@ -303,6 +308,7 @@ hashtable_rehash(_Py_hashtable_t *ht)
303308
ht->alloc.free(ht->buckets);
304309
ht->nbuckets = new_size;
305310
ht->buckets = new_buckets;
311+
return 0;
306312
}
307313

308314

@@ -388,7 +394,9 @@ _Py_hashtable_clear(_Py_hashtable_t *ht)
388394
_Py_slist_init(&ht->buckets[i]);
389395
}
390396
ht->nentries = 0;
391-
hashtable_rehash(ht);
397+
// Ignore failure: clear function is not expected to fail
398+
// because of a memory allocation failure.
399+
(void)hashtable_rehash(ht);
392400
}
393401

394402

0 commit comments

Comments
 (0)