diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py index 38cda931e7d54f..4c1431bacef0a2 100644 --- a/Lib/test/test_capi/test_bytes.py +++ b/Lib/test/test_capi/test_bytes.py @@ -317,14 +317,20 @@ def test_create(self): def test_finish_with_size(self): # Test PyBytesWriter_FinishWithSize() - writer = self.create_writer(10, b'abc') + writer = self.create_writer(10, b'abcdef') self.assertEqual(writer.get_size(), 10) self.assertEqual(writer.finish_with_size(3), self.result_type(b'abc')) + # Error if the size is negative writer = self.create_writer(3, b'abc') - with self.assertRaises(SystemError): + with self.assertRaises(ValueError): writer.finish_with_size(-3) + # Error if the requested size is larger than the allocated size + writer = self.create_writer(3, b'abc') + with self.assertRaises(ValueError): + writer.finish_with_size(4) + def test_write_bytes(self): # Test PyBytesWriter_WriteBytes() writer = self.create_writer() @@ -378,15 +384,6 @@ def test_format_i(self): writer.format_i(b'y=%i', 456) self.assertEqual(writer.finish(), self.result_type(b'x=123, y=456')) - def test_example_abc(self): - self.assertEqual(_testcapi.byteswriter_abc(), b'abc') - - def test_example_resize(self): - self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World') - - def test_example_highlevel(self): - self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!') - class BytesWriterTest(BaseWriterTest, unittest.TestCase): result_type = bytes @@ -424,6 +421,15 @@ def test_singletons(self): writer.write_bytes(unused_text, len(unused_text)) self.assertIs(writer.finish_with_size(1), singletons[ch]) + def test_example_abc(self): + self.assertEqual(_testcapi.byteswriter_abc(), b'abc') + + def test_example_resize(self): + self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World') + + def test_example_highlevel(self): + self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!') + class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase): result_type = bytearray diff --git a/Modules/_testcapi/bytes.c b/Modules/_testcapi/bytes.c index a868c684cc987c..4830cc8b54bd83 100644 --- a/Modules/_testcapi/bytes.c +++ b/Modules/_testcapi/bytes.c @@ -353,12 +353,114 @@ byteswriter_highlevel(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static size_t +pybyteswriter_small_buffer_size(void) +{ + return offsetof(PyBytesWriter, obj); +} + + +// Test the "Pointer" API of PyBytesWriter +static PyObject * +test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyBytesWriter_FinishWithPointer(): create the string "abc" + PyBytesWriter *writer = PyBytesWriter_Create(3); + if (writer == NULL) { + return NULL; + } + char *str = PyBytesWriter_GetData(writer); + memcpy(str, "abc", 3); + str += 3; + PyObject *result = PyBytesWriter_FinishWithPointer(writer, str); + if (result == NULL) { + return NULL; + } + assert(PyBytes_GET_SIZE(result) == 3); + assert(memcmp(PyBytes_AS_STRING(result), "abc", 3) == 0); + Py_DECREF(result); + + // Test PyBytesWriter_GrowAndUpdatePointer(). + // Start by using the small buffer, and then resize to use a bytes object. + writer = PyBytesWriter_Create(0); + if (writer == NULL) { + return NULL; + } + str = PyBytesWriter_GetData(writer); + + str = PyBytesWriter_GrowAndUpdatePointer(writer, 100, str); + if (str == NULL) { + PyBytesWriter_Discard(writer); + return NULL; + } + memset(str, 'x', 100); + str += 100; + + // make sure that the test switchs to a bytes object + assert((100 + 200) > pybyteswriter_small_buffer_size()); + char *old_str = str; + str = PyBytesWriter_GrowAndUpdatePointer(writer, 200, str); + if (str == NULL) { + PyBytesWriter_Discard(writer); + return NULL; + } + // make sure that we moved from the small buffer to a bytes object + assert(str != old_str); + memset(str, 'y', 200); + str += 200; + + result = PyBytesWriter_FinishWithPointer(writer, str); + if (result == NULL) { + return NULL; + } + assert(PyBytes_GET_SIZE(result) == 300); + str = PyBytes_AS_STRING(result); + for (Py_ssize_t i=0; i < 100; i++) { + assert(str[i] == 'x'); + } + for (Py_ssize_t i=0; i < 200; i++) { + assert(str[100 + i] == 'y'); + } + Py_DECREF(result); + + // Check that PyBytesWriter_FinishWithPointer() rejects pointer + // after the buffer end (create a string larger than the allocated size) + writer = PyBytesWriter_Create(3); + if (writer == NULL) { + return NULL; + } + str = PyBytesWriter_GetData(writer); + memcpy(str, "abc", 3); + str += 4; // off-by-one bug on purpose + result = PyBytesWriter_FinishWithPointer(writer, str); + assert(result == NULL); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + // Check that PyBytesWriter_FinishWithPointer() rejects pointer + // before the buffer start (negative size) + writer = PyBytesWriter_Create(3); + if (writer == NULL) { + return NULL; + } + str = PyBytesWriter_GetData(writer); + str--; // bug on purpose: go before the buffer start + result = PyBytesWriter_FinishWithPointer(writer, str); + assert(result == NULL); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + Py_RETURN_NONE; +} + + static PyMethodDef test_methods[] = { {"bytes_resize", bytes_resize, METH_VARARGS}, {"bytes_join", bytes_join, METH_VARARGS}, {"byteswriter_abc", byteswriter_abc, METH_NOARGS}, {"byteswriter_resize", byteswriter_resize, METH_NOARGS}, {"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS}, + {"test_byteswriter_ptr", test_byteswriter_ptr, METH_NOARGS}, {NULL}, }; @@ -380,7 +482,7 @@ _PyTestCapi_Init_Bytes(PyObject *m) Py_DECREF(writer_type); // PyBytesWriter.obj is the second member, small_buffer is the first member - long size = (long)offsetof(PyBytesWriter, obj); + long size = (long)pybyteswriter_small_buffer_size(); if (PyModule_AddIntConstant(m, "PyBytesWriter_small_buffer", size) < 0) { Py_DECREF(writer_type); return -1; diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 1b8dca73497415..2ae55b33f4f49d 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -31,7 +31,7 @@ class bytes "PyBytesObject *" "&PyBytes_Type" /* Forward declaration */ static void* _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *data); -static Py_ssize_t _PyBytesWriter_GetAllocated(PyBytesWriter *writer); +static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer); #define CHARACTERS _Py_SINGLETON(bytes_characters) @@ -2993,8 +2993,8 @@ _PyBytes_FromList(PyObject *x) if (writer == NULL) { return NULL; } + size = _PyBytesWriter_ResizeToAllocated(writer); char *str = PyBytesWriter_GetData(writer); - size = _PyBytesWriter_GetAllocated(writer); for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) { PyObject *item = _PyList_GetItemRef((PyListObject *)x, i); @@ -3017,7 +3017,9 @@ _PyBytes_FromList(PyObject *x) if (str == NULL) { goto error; } - size = _PyBytesWriter_GetAllocated(writer); + + // Set the writer size to its allocated size + size = _PyBytesWriter_ResizeToAllocated(writer); } *str++ = (char) value; } @@ -3075,8 +3077,8 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x) if (writer == NULL) { return NULL; } + size = _PyBytesWriter_ResizeToAllocated(writer); char *str = PyBytesWriter_GetData(writer); - size = _PyBytesWriter_GetAllocated(writer); /* Run the iterator to exhaustion */ for (i = 0; ; i++) { @@ -3110,7 +3112,9 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x) if (str == NULL) { goto error; } - size = _PyBytesWriter_GetAllocated(writer); + + // Set the writer size to its allocated size + size = _PyBytesWriter_ResizeToAllocated(writer); } *str++ = (char) value; } @@ -3747,6 +3751,19 @@ PyBytesWriter_Discard(PyBytesWriter *writer) PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size) { + // Check for negative size here to raise ValueError in all cases, rather + // than having a different exception depending on the code path. For + // example, _PyBytes_Resize() raises SystemError on negative size. + if (size < 0) { + PyErr_Format(PyExc_ValueError, "size must be positive"); + goto error; + } + + if (size > writer->size) { + PyErr_SetString(PyExc_ValueError, "size larger than allocated size"); + goto error; + } + PyObject *result; if (size == 0) { result = bytes_get_empty(); @@ -3804,12 +3821,6 @@ PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf) { Py_ssize_t size = (char*)buf - byteswriter_data(writer); - if (size < 0 || size > byteswriter_allocated(writer)) { - PyBytesWriter_Discard(writer); - PyErr_SetString(PyExc_ValueError, "invalid end pointer"); - return NULL; - } - return PyBytesWriter_FinishWithSize(writer, size); } @@ -3828,13 +3839,6 @@ PyBytesWriter_GetSize(PyBytesWriter *writer) } -static Py_ssize_t -_PyBytesWriter_GetAllocated(PyBytesWriter *writer) -{ - return byteswriter_allocated(writer); -} - - int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size) { @@ -3934,3 +3938,15 @@ PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) Py_ssize_t size = buf - byteswriter_data(writer); return PyBytesWriter_Resize(writer, size); } + + +// Resize the writer to its allocated size. +// Return the new size. +// The function cannot fail. +static Py_ssize_t +_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer) +{ + Py_ssize_t allocated = byteswriter_allocated(writer); + writer->size = allocated; + return allocated; +}