Skip to content

Commit 60ec6ef

Browse files
authored
bpo-36389: Fix _PyBytesWriter in release mode (GH-16624)
Fix _PyBytesWriter API when Python is built in release mode with assertions.
1 parent 6876257 commit 60ec6ef

File tree

3 files changed

+48
-47
lines changed

3 files changed

+48
-47
lines changed

Include/internal/pycore_pymem.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,21 @@ PyAPI_FUNC(int) _PyMem_SetDefaultAllocator(
154154
PyMemAllocatorDomain domain,
155155
PyMemAllocatorEx *old_alloc);
156156

157+
/* Special bytes broadcast into debug memory blocks at appropriate times.
158+
Strings of these are unlikely to be valid addresses, floats, ints or
159+
7-bit ASCII.
160+
161+
- PYMEM_CLEANBYTE: clean (newly allocated) memory
162+
- PYMEM_DEADBYTE dead (newly freed) memory
163+
- PYMEM_FORBIDDENBYTE: untouchable bytes at each end of a block
164+
165+
Byte patterns 0xCB, 0xBB and 0xFB have been replaced with 0xCD, 0xDD and
166+
0xFD to use the same values than Windows CRT debug malloc() and free().
167+
If modified, _PyMem_IsPtrFreed() should be updated as well. */
168+
#define PYMEM_CLEANBYTE 0xCD
169+
#define PYMEM_DEADBYTE 0xDD
170+
#define PYMEM_FORBIDDENBYTE 0xFD
171+
157172
/* Heuristic checking if a pointer value is newly allocated
158173
(uninitialized), newly freed or NULL (is equal to zero).
159174

Objects/bytesobject.c

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -667,9 +667,6 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
667667
Py_ssize_t len = 0;
668668
char onechar; /* For byte_converter() */
669669
Py_ssize_t alloc;
670-
#ifdef Py_DEBUG
671-
char *before;
672-
#endif
673670

674671
fmt++;
675672
if (*fmt == '%') {
@@ -981,8 +978,8 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
981978
if (res == NULL)
982979
goto error;
983980
}
984-
#ifdef Py_DEBUG
985-
before = res;
981+
#ifndef NDEBUG
982+
char *before = res;
986983
#endif
987984

988985
/* Write the sign if needed */
@@ -1047,7 +1044,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
10471044
}
10481045
Py_XDECREF(temp);
10491046

1050-
#ifdef Py_DEBUG
1047+
#ifndef NDEBUG
10511048
/* check that we computed the exact size for this write */
10521049
assert((res - before) == alloc);
10531050
#endif
@@ -3168,8 +3165,9 @@ _PyBytesWriter_Init(_PyBytesWriter *writer)
31683165
{
31693166
/* Set all attributes before small_buffer to 0 */
31703167
memset(writer, 0, offsetof(_PyBytesWriter, small_buffer));
3171-
#ifdef Py_DEBUG
3172-
memset(writer->small_buffer, 0xCB, sizeof(writer->small_buffer));
3168+
#ifndef NDEBUG
3169+
memset(writer->small_buffer, PYMEM_CLEANBYTE,
3170+
sizeof(writer->small_buffer));
31733171
#endif
31743172
}
31753173

@@ -3297,8 +3295,9 @@ _PyBytesWriter_Resize(_PyBytesWriter *writer, void *str, Py_ssize_t size)
32973295
}
32983296

32993297
writer->use_small_buffer = 0;
3300-
#ifdef Py_DEBUG
3301-
memset(writer->small_buffer, 0xDB, sizeof(writer->small_buffer));
3298+
#ifndef NDEBUG
3299+
memset(writer->small_buffer, PYMEM_CLEANBYTE,
3300+
sizeof(writer->small_buffer));
33023301
#endif
33033302
}
33043303
writer->allocated = allocated;
@@ -3350,7 +3349,7 @@ _PyBytesWriter_Alloc(_PyBytesWriter *writer, Py_ssize_t size)
33503349
assert(size >= 0);
33513350

33523351
writer->use_small_buffer = 1;
3353-
#ifdef Py_DEBUG
3352+
#ifndef NDEBUG
33543353
writer->allocated = sizeof(writer->small_buffer) - 1;
33553354
/* In debug mode, don't use the full small buffer because it is less
33563355
efficient than bytes and bytearray objects to detect buffer underflow

Objects/obmalloc.c

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,20 +2031,6 @@ _Py_GetAllocatedBlocks(void)
20312031
* it wraps a real allocator, adding extra debugging info to the memory blocks.
20322032
*/
20332033

2034-
/* Special bytes broadcast into debug memory blocks at appropriate times.
2035-
* Strings of these are unlikely to be valid addresses, floats, ints or
2036-
* 7-bit ASCII. If modified, _PyMem_IsPtrFreed() should be updated as well.
2037-
*
2038-
* Byte patterns 0xCB, 0xBB and 0xFB have been replaced with 0xCD, 0xDD and
2039-
* 0xFD to use the same values than Windows CRT debug malloc() and free().
2040-
*/
2041-
#undef CLEANBYTE
2042-
#undef DEADBYTE
2043-
#undef FORBIDDENBYTE
2044-
#define CLEANBYTE 0xCD /* clean (newly allocated) memory */
2045-
#define DEADBYTE 0xDD /* dead (newly freed) memory */
2046-
#define FORBIDDENBYTE 0xFD /* untouchable bytes at each end of a block */
2047-
20482034
/* Uncomment this define to add the "serialno" field */
20492035
/* #define PYMEM_DEBUG_SERIALNO */
20502036

@@ -2106,14 +2092,14 @@ p[0: S]
21062092
p[S]
21072093
API ID. See PEP 445. This is a character, but seems undocumented.
21082094
p[S+1: 2*S]
2109-
Copies of FORBIDDENBYTE. Used to catch under- writes and reads.
2095+
Copies of PYMEM_FORBIDDENBYTE. Used to catch under- writes and reads.
21102096
p[2*S: 2*S+n]
2111-
The requested memory, filled with copies of CLEANBYTE.
2097+
The requested memory, filled with copies of PYMEM_CLEANBYTE.
21122098
Used to catch reference to uninitialized memory.
21132099
&p[2*S] is returned. Note that this is 8-byte aligned if pymalloc
21142100
handled the request itself.
21152101
p[2*S+n: 2*S+n+S]
2116-
Copies of FORBIDDENBYTE. Used to catch over- writes and reads.
2102+
Copies of PYMEM_FORBIDDENBYTE. Used to catch over- writes and reads.
21172103
p[2*S+n+S: 2*S+n+2*S]
21182104
A serial number, incremented by 1 on each call to _PyMem_DebugMalloc
21192105
and _PyMem_DebugRealloc.
@@ -2170,15 +2156,15 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes)
21702156
/* at p, write size (SST bytes), id (1 byte), pad (SST-1 bytes) */
21712157
write_size_t(p, nbytes);
21722158
p[SST] = (uint8_t)api->api_id;
2173-
memset(p + SST + 1, FORBIDDENBYTE, SST-1);
2159+
memset(p + SST + 1, PYMEM_FORBIDDENBYTE, SST-1);
21742160

21752161
if (nbytes > 0 && !use_calloc) {
2176-
memset(data, CLEANBYTE, nbytes);
2162+
memset(data, PYMEM_CLEANBYTE, nbytes);
21772163
}
21782164

21792165
/* at tail, write pad (SST bytes) and serialno (SST bytes) */
21802166
tail = data + nbytes;
2181-
memset(tail, FORBIDDENBYTE, SST);
2167+
memset(tail, PYMEM_FORBIDDENBYTE, SST);
21822168
#ifdef PYMEM_DEBUG_SERIALNO
21832169
write_size_t(tail + SST, serialno);
21842170
#endif
@@ -2204,7 +2190,7 @@ _PyMem_DebugRawCalloc(void *ctx, size_t nelem, size_t elsize)
22042190

22052191
/* The debug free first checks the 2*SST bytes on each end for sanity (in
22062192
particular, that the FORBIDDENBYTEs with the api ID are still intact).
2207-
Then fills the original bytes with DEADBYTE.
2193+
Then fills the original bytes with PYMEM_DEADBYTE.
22082194
Then calls the underlying free.
22092195
*/
22102196
static void
@@ -2222,7 +2208,7 @@ _PyMem_DebugRawFree(void *ctx, void *p)
22222208
_PyMem_DebugCheckAddress(api->api_id, p);
22232209
nbytes = read_size_t(q);
22242210
nbytes += PYMEM_DEBUG_EXTRA_BYTES;
2225-
memset(q, DEADBYTE, nbytes);
2211+
memset(q, PYMEM_DEADBYTE, nbytes);
22262212
api->alloc.free(api->alloc.ctx, q);
22272213
}
22282214

@@ -2264,14 +2250,14 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
22642250
*/
22652251
if (original_nbytes <= sizeof(save)) {
22662252
memcpy(save, data, original_nbytes);
2267-
memset(data - 2 * SST, DEADBYTE,
2253+
memset(data - 2 * SST, PYMEM_DEADBYTE,
22682254
original_nbytes + PYMEM_DEBUG_EXTRA_BYTES);
22692255
}
22702256
else {
22712257
memcpy(save, data, ERASED_SIZE);
2272-
memset(head, DEADBYTE, ERASED_SIZE + 2 * SST);
2258+
memset(head, PYMEM_DEADBYTE, ERASED_SIZE + 2 * SST);
22732259
memcpy(&save[ERASED_SIZE], tail - ERASED_SIZE, ERASED_SIZE);
2274-
memset(tail - ERASED_SIZE, DEADBYTE,
2260+
memset(tail - ERASED_SIZE, PYMEM_DEADBYTE,
22752261
ERASED_SIZE + PYMEM_DEBUG_EXTRA_BYTES - 2 * SST);
22762262
}
22772263

@@ -2293,10 +2279,10 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
22932279

22942280
write_size_t(head, nbytes);
22952281
head[SST] = (uint8_t)api->api_id;
2296-
memset(head + SST + 1, FORBIDDENBYTE, SST-1);
2282+
memset(head + SST + 1, PYMEM_FORBIDDENBYTE, SST-1);
22972283

22982284
tail = data + nbytes;
2299-
memset(tail, FORBIDDENBYTE, SST);
2285+
memset(tail, PYMEM_FORBIDDENBYTE, SST);
23002286
#ifdef PYMEM_DEBUG_SERIALNO
23012287
write_size_t(tail + SST, block_serialno);
23022288
#endif
@@ -2320,7 +2306,8 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
23202306

23212307
if (nbytes > original_nbytes) {
23222308
/* growing: mark new extra memory clean */
2323-
memset(data + original_nbytes, CLEANBYTE, nbytes - original_nbytes);
2309+
memset(data + original_nbytes, PYMEM_CLEANBYTE,
2310+
nbytes - original_nbytes);
23242311
}
23252312

23262313
return data;
@@ -2399,7 +2386,7 @@ _PyMem_DebugCheckAddress(char api, const void *p)
23992386
* the tail could lead to a segfault then.
24002387
*/
24012388
for (i = SST-1; i >= 1; --i) {
2402-
if (*(q-i) != FORBIDDENBYTE) {
2389+
if (*(q-i) != PYMEM_FORBIDDENBYTE) {
24032390
msg = "bad leading pad byte";
24042391
goto error;
24052392
}
@@ -2408,7 +2395,7 @@ _PyMem_DebugCheckAddress(char api, const void *p)
24082395
nbytes = read_size_t(q - 2*SST);
24092396
tail = q + nbytes;
24102397
for (i = 0; i < SST; ++i) {
2411-
if (tail[i] != FORBIDDENBYTE) {
2398+
if (tail[i] != PYMEM_FORBIDDENBYTE) {
24122399
msg = "bad trailing pad byte";
24132400
goto error;
24142401
}
@@ -2448,7 +2435,7 @@ _PyObject_DebugDumpAddress(const void *p)
24482435
fprintf(stderr, " The %d pad bytes at p-%d are ", SST-1, SST-1);
24492436
ok = 1;
24502437
for (i = 1; i <= SST-1; ++i) {
2451-
if (*(q-i) != FORBIDDENBYTE) {
2438+
if (*(q-i) != PYMEM_FORBIDDENBYTE) {
24522439
ok = 0;
24532440
break;
24542441
}
@@ -2457,11 +2444,11 @@ _PyObject_DebugDumpAddress(const void *p)
24572444
fputs("FORBIDDENBYTE, as expected.\n", stderr);
24582445
else {
24592446
fprintf(stderr, "not all FORBIDDENBYTE (0x%02x):\n",
2460-
FORBIDDENBYTE);
2447+
PYMEM_FORBIDDENBYTE);
24612448
for (i = SST-1; i >= 1; --i) {
24622449
const uint8_t byte = *(q-i);
24632450
fprintf(stderr, " at p-%d: 0x%02x", i, byte);
2464-
if (byte != FORBIDDENBYTE)
2451+
if (byte != PYMEM_FORBIDDENBYTE)
24652452
fputs(" *** OUCH", stderr);
24662453
fputc('\n', stderr);
24672454
}
@@ -2476,7 +2463,7 @@ _PyObject_DebugDumpAddress(const void *p)
24762463
fprintf(stderr, " The %d pad bytes at tail=%p are ", SST, (void *)tail);
24772464
ok = 1;
24782465
for (i = 0; i < SST; ++i) {
2479-
if (tail[i] != FORBIDDENBYTE) {
2466+
if (tail[i] != PYMEM_FORBIDDENBYTE) {
24802467
ok = 0;
24812468
break;
24822469
}
@@ -2485,12 +2472,12 @@ _PyObject_DebugDumpAddress(const void *p)
24852472
fputs("FORBIDDENBYTE, as expected.\n", stderr);
24862473
else {
24872474
fprintf(stderr, "not all FORBIDDENBYTE (0x%02x):\n",
2488-
FORBIDDENBYTE);
2475+
PYMEM_FORBIDDENBYTE);
24892476
for (i = 0; i < SST; ++i) {
24902477
const uint8_t byte = tail[i];
24912478
fprintf(stderr, " at tail+%d: 0x%02x",
24922479
i, byte);
2493-
if (byte != FORBIDDENBYTE)
2480+
if (byte != PYMEM_FORBIDDENBYTE)
24942481
fputs(" *** OUCH", stderr);
24952482
fputc('\n', stderr);
24962483
}

0 commit comments

Comments
 (0)