Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Rewrite _PyMem_DebugRawRealloc().

Previously, _PyMem_DebugRawRealloc() expected that the old memory
block remains readable and unchanged after realloc(). This assumption
is wrong on OpenBSD.

Rewrite _PyMem_DebugRawRealloc() as malloc()+free() rather than
realloc(). Debug hooks are reused. The old memory block remains valid
after the new memory block was allocated, so bytes can be safely
copied in a portable way.

The drawback is that _PyMem_DebugRawRealloc() now allocates a new
memory block while the old memory block remains allocated, until the
old memory block is freed, so the peak memory usage can be the double
in the worst case.

The second drawback is that the system realloc() is no more used in
debug mode.
54 changes: 13 additions & 41 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1460,53 +1460,25 @@ static void *
_PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
{
debug_alloc_api_t *api = (debug_alloc_api_t *)ctx;
uint8_t *q = (uint8_t *)p, *oldq;
uint8_t *tail;
size_t total; /* nbytes + 4*SST */
size_t original_nbytes;
int i;
void *q;
size_t old_size;

if (p == NULL)
return _PyMem_DebugRawAlloc(0, ctx, nbytes);

_PyMem_DebugCheckAddress(api->api_id, p);
bumpserialno();
original_nbytes = read_size_t(q - 2*SST);
total = nbytes + 4*SST;
if (nbytes > PY_SSIZE_T_MAX - 4*SST)
/* overflow: can't represent total as a Py_ssize_t */
return NULL;
if (p != NULL) {
uint8_t *p2 = (uint8_t *)p - 2*SST; /* address returned from malloc */
_PyMem_DebugCheckAddress(api->api_id, p);
old_size = read_size_t(p2);
}

/* Resize and add decorations. We may get a new pointer here, in which
* case we didn't get the chance to mark the old memory with DEADBYTE,
* but we live with that.
*/
oldq = q;
q = (uint8_t *)api->alloc.realloc(api->alloc.ctx, q - 2*SST, total);
if (q == NULL)
q = _PyMem_DebugRawAlloc(0, ctx, nbytes);
if (q == NULL) {
return NULL;

if (q == oldq && nbytes < original_nbytes) {
/* shrinking: mark old extra memory dead */
memset(q + nbytes, DEADBYTE, original_nbytes - nbytes);
}

write_size_t(q, nbytes);
assert(q[SST] == (uint8_t)api->api_id);
for (i = 1; i < SST; ++i)
assert(q[SST + i] == FORBIDDENBYTE);
q += 2*SST;

tail = q + nbytes;
memset(tail, FORBIDDENBYTE, SST);
write_size_t(tail + SST, _PyRuntime.mem.serialno);

if (nbytes > original_nbytes) {
/* growing: mark new extra memory clean */
memset(q + original_nbytes, CLEANBYTE,
nbytes - original_nbytes);
if (p != NULL) {
size_t copied_bytes = Py_MIN(nbytes, old_size);
memcpy(q, p, copied_bytes);
_PyMem_DebugRawFree(ctx, p);
}

return q;
}

Expand Down