Skip to content
Merged
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
28 changes: 17 additions & 11 deletions Lib/test/test_capi/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
104 changes: 103 additions & 1 deletion Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};

Expand All @@ -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;
Expand Down
52 changes: 34 additions & 18 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}

Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
}
Loading