Skip to content

Commit 1ed7220

Browse files
committed
Merge branch '3.15' into writer_resize15
2 parents 2f471c9 + c38a347 commit 1ed7220

7 files changed

Lines changed: 180 additions & 27 deletions

File tree

‎Lib/copy.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ def deepcopy(x, memo=None):
122122
if memo is None:
123123
memo = {}
124124
else:
125-
y = memo.get(d, None)
126-
if y is not None:
127-
return y
125+
if d in memo:
126+
return memo[d]
128127

129128
copier = _deepcopy_dispatch.get(cls)
130129
if copier is not None:

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,14 +340,20 @@ def test_create(self):
340340

341341
def test_finish_with_size(self):
342342
# Test PyBytesWriter_FinishWithSize()
343-
writer = self.create_writer(10, b'abc')
343+
writer = self.create_writer(10, b'abcdef')
344344
self.assertEqual(writer.get_size(), 10)
345345
self.bytes_equal(writer.finish_with_size(3), b'abc')
346346

347+
# Error if the size is negative
347348
writer = self.create_writer(3, b'abc')
348-
with self.assertRaises(SystemError):
349+
with self.assertRaises(ValueError):
349350
writer.finish_with_size(-3)
350351

352+
# Error if the requested size is larger than the allocated size
353+
writer = self.create_writer(3, b'abc')
354+
with self.assertRaises(ValueError):
355+
writer.finish_with_size(4)
356+
351357
def test_write_bytes(self):
352358
# Test PyBytesWriter_WriteBytes()
353359
writer = self.create_writer()
@@ -425,8 +431,31 @@ class BytesWriterTest(BaseWriterTest, unittest.TestCase):
425431
RESULT_TYPE = bytes
426432

427433
def create_writer(self, alloc=0, string=b''):
434+
# Test PyBytesWriter_Create()
428435
return _testcapi.PyBytesWriter(alloc, string, 0)
429436

437+
# Only PyBytesWriter_Create() returns singletons
438+
def test_singletons(self):
439+
empty = b''
440+
singletons = {ch: bytes((ch,)) for ch in range(256)}
441+
small_buffer = _testcapi.PyBytesWriter_small_buffer
442+
443+
writer = self.create_writer()
444+
self.assertIs(writer.finish(), empty)
445+
446+
# Test writer larger than small_buffer
447+
writer = self.create_writer()
448+
unused_text = b'x' * (small_buffer * 2)
449+
writer.write_bytes(unused_text, len(unused_text))
450+
self.assertIs(writer.finish_with_size(0), empty)
451+
452+
for ch in range(256):
453+
text = bytes((ch,))
454+
455+
writer = self.create_writer()
456+
writer.write_bytes(text, 1)
457+
self.assertIs(writer.finish(), singletons[ch])
458+
430459
def test_example_abc(self):
431460
self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
432461

@@ -459,6 +488,7 @@ class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
459488
RESULT_TYPE = bytearray
460489

461490
def create_writer(self, alloc=0, string=b''):
491+
# Test private _PyBytesWriter_CreateByteArray()
462492
return _testcapi.PyBytesWriter(alloc, string, 1)
463493

464494

‎Lib/test/test_copy.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,17 @@ def m(self):
951951
self.assertIs(g.b.__self__, g)
952952
g.b()
953953

954+
def test_deepcopy_memo_none_result(self):
955+
# Objects whose deepcopy result is None must still be memoized.
956+
class C:
957+
call_count = 0
958+
def __deepcopy__(self, memo):
959+
C.call_count += 1
960+
return None
961+
obj = C()
962+
copy.deepcopy([obj, obj, obj])
963+
self.assertEqual(C.call_count, 1)
964+
954965

955966
class TestReplace(unittest.TestCase):
956967

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`copy.deepcopy` so that an object whose deep copy is ``None`` is
2+
still memoized. Patch by tonghuaroot.

‎Modules/_testcapi/bytes.c‎

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ writer_write_bytes(PyObject *self_raw, PyObject *args)
151151
return NULL;
152152
}
153153

154-
char *bytes;
155-
Py_ssize_t size;
156-
if (!PyArg_ParseTuple(args, "yn", &bytes, &size)) {
154+
const char *bytes;
155+
Py_ssize_t unused_size, size;
156+
if (!PyArg_ParseTuple(args, "y#n", &bytes, &unused_size, &size)) {
157157
return NULL;
158158
}
159159

@@ -383,12 +383,107 @@ pybyteswriter_small_buffer_size(void)
383383
}
384384

385385

386+
// Test the "Pointer" API of PyBytesWriter
387+
static PyObject *
388+
test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
389+
{
390+
// Test PyBytesWriter_FinishWithPointer(): create the string "abc"
391+
PyBytesWriter *writer = PyBytesWriter_Create(3);
392+
if (writer == NULL) {
393+
return NULL;
394+
}
395+
char *str = PyBytesWriter_GetData(writer);
396+
memcpy(str, "abc", 3);
397+
str += 3;
398+
PyObject *result = PyBytesWriter_FinishWithPointer(writer, str);
399+
if (result == NULL) {
400+
return NULL;
401+
}
402+
assert(PyBytes_GET_SIZE(result) == 3);
403+
assert(memcmp(PyBytes_AS_STRING(result), "abc", 3) == 0);
404+
Py_DECREF(result);
405+
406+
// Test PyBytesWriter_GrowAndUpdatePointer().
407+
// Start by using the small buffer, and then resize to use a bytes object.
408+
writer = PyBytesWriter_Create(0);
409+
if (writer == NULL) {
410+
return NULL;
411+
}
412+
str = PyBytesWriter_GetData(writer);
413+
414+
str = PyBytesWriter_GrowAndUpdatePointer(writer, 100, str);
415+
if (str == NULL) {
416+
PyBytesWriter_Discard(writer);
417+
return NULL;
418+
}
419+
memset(str, 'x', 100);
420+
str += 100;
421+
422+
// make sure that the test switchs to a bytes object
423+
assert((100 + 200) > pybyteswriter_small_buffer_size());
424+
char *old_str = str;
425+
str = PyBytesWriter_GrowAndUpdatePointer(writer, 200, str);
426+
if (str == NULL) {
427+
PyBytesWriter_Discard(writer);
428+
return NULL;
429+
}
430+
// make sure that we moved from the small buffer to a bytes object
431+
assert(str != old_str);
432+
memset(str, 'y', 200);
433+
str += 200;
434+
435+
result = PyBytesWriter_FinishWithPointer(writer, str);
436+
if (result == NULL) {
437+
return NULL;
438+
}
439+
assert(PyBytes_GET_SIZE(result) == 300);
440+
str = PyBytes_AS_STRING(result);
441+
for (Py_ssize_t i=0; i < 100; i++) {
442+
assert(str[i] == 'x');
443+
}
444+
for (Py_ssize_t i=0; i < 200; i++) {
445+
assert(str[100 + i] == 'y');
446+
}
447+
Py_DECREF(result);
448+
449+
// Check that PyBytesWriter_FinishWithPointer() rejects pointer
450+
// after the buffer end (create a string larger than the allocated size)
451+
writer = PyBytesWriter_Create(3);
452+
if (writer == NULL) {
453+
return NULL;
454+
}
455+
str = PyBytesWriter_GetData(writer);
456+
memcpy(str, "abc", 3);
457+
str += 4; // off-by-one bug on purpose
458+
result = PyBytesWriter_FinishWithPointer(writer, str);
459+
assert(result == NULL);
460+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
461+
PyErr_Clear();
462+
463+
// Check that PyBytesWriter_FinishWithPointer() rejects pointer
464+
// before the buffer start (negative size)
465+
writer = PyBytesWriter_Create(3);
466+
if (writer == NULL) {
467+
return NULL;
468+
}
469+
str = PyBytesWriter_GetData(writer);
470+
str--; // bug on purpose: go before the buffer start
471+
result = PyBytesWriter_FinishWithPointer(writer, str);
472+
assert(result == NULL);
473+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
474+
PyErr_Clear();
475+
476+
Py_RETURN_NONE;
477+
}
478+
479+
386480
static PyMethodDef test_methods[] = {
387481
{"bytes_resize", bytes_resize, METH_VARARGS},
388482
{"bytes_join", bytes_join, METH_VARARGS},
389483
{"byteswriter_abc", byteswriter_abc, METH_NOARGS},
390484
{"byteswriter_resize", byteswriter_resize, METH_NOARGS},
391485
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
486+
{"test_byteswriter_ptr", test_byteswriter_ptr, METH_NOARGS},
392487
{NULL},
393488
};
394489

‎Objects/bytesobject.c‎

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class bytes "PyBytesObject *" "&PyBytes_Type"
3232
/* Forward declaration */
3333
static void* _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter *writer,
3434
Py_ssize_t size, void *data);
35-
static Py_ssize_t _PyBytesWriter_GetAllocated(PyBytesWriter *writer);
35+
static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer);
3636

3737

3838
#define CHARACTERS _Py_SINGLETON(bytes_characters)
@@ -2982,8 +2982,8 @@ _PyBytes_FromList(PyObject *x)
29822982
if (writer == NULL) {
29832983
return NULL;
29842984
}
2985+
size = _PyBytesWriter_ResizeToAllocated(writer);
29852986
char *str = PyBytesWriter_GetData(writer);
2986-
size = _PyBytesWriter_GetAllocated(writer);
29872987

29882988
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) {
29892989
PyObject *item = _PyList_GetItemRef((PyListObject *)x, i);
@@ -3006,7 +3006,9 @@ _PyBytes_FromList(PyObject *x)
30063006
if (str == NULL) {
30073007
goto error;
30083008
}
3009-
size = _PyBytesWriter_GetAllocated(writer);
3009+
3010+
// Set the writer size to its allocated size
3011+
size = _PyBytesWriter_ResizeToAllocated(writer);
30103012
}
30113013
*str++ = (char) value;
30123014
}
@@ -3064,8 +3066,8 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
30643066
if (writer == NULL) {
30653067
return NULL;
30663068
}
3069+
size = _PyBytesWriter_ResizeToAllocated(writer);
30673070
char *str = PyBytesWriter_GetData(writer);
3068-
size = _PyBytesWriter_GetAllocated(writer);
30693071

30703072
/* Run the iterator to exhaustion */
30713073
for (i = 0; ; i++) {
@@ -3099,7 +3101,9 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
30993101
if (str == NULL) {
31003102
goto error;
31013103
}
3102-
size = _PyBytesWriter_GetAllocated(writer);
3104+
3105+
// Set the writer size to its allocated size
3106+
size = _PyBytesWriter_ResizeToAllocated(writer);
31033107
}
31043108
*str++ = (char) value;
31053109
}
@@ -3762,6 +3766,19 @@ PyBytesWriter_Discard(PyBytesWriter *writer)
37623766
PyObject*
37633767
PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
37643768
{
3769+
// Check for negative size here to raise ValueError in all cases, rather
3770+
// than having a different exception depending on the code path. For
3771+
// example, _PyBytes_Resize() raises SystemError on negative size.
3772+
if (size < 0) {
3773+
PyErr_Format(PyExc_ValueError, "size must be positive");
3774+
goto error;
3775+
}
3776+
3777+
if (size > writer->size) {
3778+
PyErr_SetString(PyExc_ValueError, "size larger than allocated size");
3779+
goto error;
3780+
}
3781+
37653782
PyObject *result;
37663783
if (size == 0 && !writer->use_bytearray) {
37673784
result = bytes_get_empty();
@@ -3809,12 +3826,6 @@ PyObject*
38093826
PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
38103827
{
38113828
Py_ssize_t size = (char*)buf - byteswriter_data(writer);
3812-
if (size < 0 || size > byteswriter_allocated(writer)) {
3813-
PyBytesWriter_Discard(writer);
3814-
PyErr_SetString(PyExc_ValueError, "invalid end pointer");
3815-
return NULL;
3816-
}
3817-
38183829
return PyBytesWriter_FinishWithSize(writer, size);
38193830
}
38203831

@@ -3833,13 +3844,6 @@ PyBytesWriter_GetSize(PyBytesWriter *writer)
38333844
}
38343845

38353846

3836-
static Py_ssize_t
3837-
_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
3838-
{
3839-
return byteswriter_allocated(writer);
3840-
}
3841-
3842-
38433847
int
38443848
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
38453849
{
@@ -3946,3 +3950,15 @@ PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
39463950
Py_ssize_t size = buf - byteswriter_data(writer);
39473951
return PyBytesWriter_Resize(writer, size);
39483952
}
3953+
3954+
3955+
// Resize the writer to its allocated size.
3956+
// Return the new size.
3957+
// The function cannot fail.
3958+
static Py_ssize_t
3959+
_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer)
3960+
{
3961+
Py_ssize_t allocated = byteswriter_allocated(writer);
3962+
writer->size = allocated;
3963+
return allocated;
3964+
}

‎PCbuild/find_python.bat‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
@rem If py.exe finds a recent enough version, use that one
4848
@rem It is fine to add new versions to this list when they have released,
4949
@rem but we do not use prerelease builds here.
50-
@for %%p in (3.14 3.13 3.12 3.11 3.10) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found
50+
@for %%p in (3.15 3.14 3.13 3.12 3.11 3.10) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found
5151

5252
@if NOT exist "%_Py_EXTERNALS_DIR%" mkdir "%_Py_EXTERNALS_DIR%"
5353
@set _Py_NUGET=%NUGET%

0 commit comments

Comments
 (0)