Skip to content

Commit c275be5

Browse files
authored
bpo-35368: Make PyMem_Malloc() thread-safe in debug mode (GH-10828)
When Python is compiled in debug mode, PyMem_Malloc() uses debug hooks, but it also uses pymalloc allocator instead of malloc(). Problem: pymalloc is not thread-safe, whereas PyMem_Malloc() is thread-safe in release mode (it's a thin wrapper to malloc() in this case). Modify the debug hook to use malloc() for PyMem_Malloc().
1 parent dfd4a1d commit c275be5

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:c:func:`PyMem_Malloc` is now also thread-safe in debug mode.

Objects/obmalloc.c

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,38 @@ pool_is_in_list(const poolp target, poolp list)
14131413

14141414
#endif /* Py_DEBUG */
14151415

1416+
static void *
1417+
_PyMem_Malloc(size_t nbytes)
1418+
{
1419+
if (nbytes > (size_t)PY_SSIZE_T_MAX) {
1420+
return NULL;
1421+
}
1422+
if (nbytes == 0) {
1423+
nbytes = 1;
1424+
}
1425+
return malloc(nbytes);
1426+
}
1427+
1428+
static void *
1429+
_PyMem_Realloc(void *p, size_t nbytes)
1430+
{
1431+
if (nbytes > (size_t)PY_SSIZE_T_MAX) {
1432+
return NULL;
1433+
}
1434+
if (nbytes == 0) {
1435+
nbytes = 1;
1436+
}
1437+
return realloc(p, nbytes);
1438+
}
1439+
1440+
1441+
static void
1442+
_PyMem_Free(void *p)
1443+
{
1444+
free(p);
1445+
}
1446+
1447+
14161448
/* Let S = sizeof(size_t). The debug malloc asks for 4*S extra bytes and
14171449
fills them with useful stuff, here calling the underlying malloc's result p:
14181450
@@ -1479,7 +1511,7 @@ _PyObject_DebugCheckAddress(const void *p)
14791511

14801512
/* generic debug memory api, with an "id" to identify the API in use */
14811513
void *
1482-
_PyObject_DebugMallocApi(char id, size_t nbytes)
1514+
_PyObject_DebugMallocApi(char api, size_t nbytes)
14831515
{
14841516
uchar *p; /* base address of malloc'ed block */
14851517
uchar *tail; /* p + 2*SST + nbytes == pointer to tail pad bytes */
@@ -1491,13 +1523,18 @@ _PyObject_DebugMallocApi(char id, size_t nbytes)
14911523
/* overflow: can't represent total as a size_t */
14921524
return NULL;
14931525

1494-
p = (uchar *)PyObject_Malloc(total);
1526+
if (api == _PYMALLOC_OBJ_ID) {
1527+
p = (uchar *)PyObject_Malloc(total);
1528+
}
1529+
else {
1530+
p = (uchar *)_PyMem_Malloc(total);
1531+
}
14951532
if (p == NULL)
14961533
return NULL;
14971534

1498-
/* at p, write size (SST bytes), id (1 byte), pad (SST-1 bytes) */
1535+
/* at p, write size (SST bytes), api (1 byte), pad (SST-1 bytes) */
14991536
write_size_t(p, nbytes);
1500-
p[SST] = (uchar)id;
1537+
p[SST] = (uchar)api;
15011538
memset(p + SST + 1 , FORBIDDENBYTE, SST-1);
15021539

15031540
if (nbytes > 0)
@@ -1529,7 +1566,12 @@ _PyObject_DebugFreeApi(char api, void *p)
15291566
nbytes += 4*SST;
15301567
if (nbytes > 0)
15311568
memset(q, DEADBYTE, nbytes);
1532-
PyObject_Free(q);
1569+
if (api == _PYMALLOC_OBJ_ID) {
1570+
PyObject_Free(q);
1571+
}
1572+
else {
1573+
_PyMem_Free(q);
1574+
}
15331575
}
15341576

15351577
void *
@@ -1561,7 +1603,12 @@ _PyObject_DebugReallocApi(char api, void *p, size_t nbytes)
15611603
* case we didn't get the chance to mark the old memory with DEADBYTE,
15621604
* but we live with that.
15631605
*/
1564-
q = (uchar *)PyObject_Realloc(q - 2*SST, total);
1606+
if (api == _PYMALLOC_OBJ_ID) {
1607+
q = (uchar *)PyObject_Realloc(q - 2*SST, total);
1608+
}
1609+
else {
1610+
q = (uchar *)_PyMem_Realloc(q - 2*SST, total);
1611+
}
15651612
if (q == NULL) {
15661613
if (nbytes <= original_nbytes) {
15671614
/* bpo-31626: the memset() above expects that realloc never fails

0 commit comments

Comments
 (0)