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
6 changes: 3 additions & 3 deletions Doc/library/struct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ The module defines the following exception and functions:

Unpack from *buffer* starting at position *offset*, according to the format
string *format*. The result is a tuple even if it contains exactly one
item. The buffer's size in bytes, minus *offset*, must be at least
the size required by the format, as reflected by :func:`calcsize`.
item. The buffer's size in bytes, starting at position *offset*, must be at
least the size required by the format, as reflected by :func:`calcsize`.


.. function:: iter_unpack(format, buffer)
Expand Down Expand Up @@ -428,7 +428,7 @@ The :mod:`struct` module also defines the following type:
.. method:: unpack_from(buffer, offset=0)

Identical to the :func:`unpack_from` function, using the compiled format.
The buffer's size in bytes, minus *offset*, must be at least
The buffer's size in bytes, starting at position *offset*, must be at least
:attr:`size`.


Expand Down
34 changes: 30 additions & 4 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,14 +579,22 @@ def test__sizeof__(self):
self.check_sizeof('0c', 0)

def test_boundary_error_message(self):
regex = (
regex1 = (
r'pack_into requires a buffer of at least 6 '
r'bytes for packing 1 bytes at offset 5 '
r'\(actual buffer size is 1\)'
)
with self.assertRaisesRegex(struct.error, regex):
with self.assertRaisesRegex(struct.error, regex1):
struct.pack_into('b', bytearray(1), 5, 1)

regex2 = (
r'unpack_from requires a buffer of at least 6 '
r'bytes for unpacking 1 bytes at offset 5 '
r'\(actual buffer size is 1\)'
)
with self.assertRaisesRegex(struct.error, regex2):
struct.unpack_from('b', bytearray(1), 5)

def test_boundary_error_message_with_negative_offset(self):
byte_list = bytearray(10)
with self.assertRaisesRegex(
Expand All @@ -599,16 +607,34 @@ def test_boundary_error_message_with_negative_offset(self):
'offset -11 out of range for 10-byte buffer'):
struct.pack_into('<B', byte_list, -11, 123)

with self.assertRaisesRegex(
struct.error,
r'not enough data to unpack 4 bytes at offset -2'):
struct.unpack_from('<I', byte_list, -2)

with self.assertRaisesRegex(
struct.error,
"offset -11 out of range for 10-byte buffer"):
struct.unpack_from('<B', byte_list, -11)

def test_boundary_error_message_with_large_offset(self):
# Test overflows cause by large offset and value size (issue 30245)
regex = (
regex1 = (
r'pack_into requires a buffer of at least ' + str(sys.maxsize + 4) +
r' bytes for packing 4 bytes at offset ' + str(sys.maxsize) +
r' \(actual buffer size is 10\)'
)
with self.assertRaisesRegex(struct.error, regex):
with self.assertRaisesRegex(struct.error, regex1):
struct.pack_into('<I', bytearray(10), sys.maxsize, 1)

regex2 = (
r'unpack_from requires a buffer of at least ' + str(sys.maxsize + 4) +
r' bytes for unpacking 4 bytes at offset ' + str(sys.maxsize) +
r' \(actual buffer size is 10\)'
)
with self.assertRaisesRegex(struct.error, regex2):
struct.unpack_from('<I', bytearray(10), sys.maxsize)

def test_issue29802(self):
# When the second argument of struct.unpack() was of wrong type
# the Struct object was decrefed twice and the reference to
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve struct.unpack_from() exception messages for problems with the buffer
size and offset.
35 changes: 29 additions & 6 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -1553,24 +1553,47 @@ Return a tuple containing unpacked values.

Values are unpacked according to the format string Struct.format.

The buffer's size in bytes, minus offset, must be at least Struct.size.
The buffer's size in bytes, starting at position offset, must be
at least Struct.size.

See help(struct) for more on format strings.
[clinic start generated code]*/

static PyObject *
Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer,
Py_ssize_t offset)
/*[clinic end generated code: output=57fac875e0977316 input=97ade52422f8962f]*/
/*[clinic end generated code: output=57fac875e0977316 input=cafd4851d473c894]*/
{
assert(self->s_codes != NULL);

if (offset < 0)
if (offset < 0) {
if (offset + self->s_size > 0) {
PyErr_Format(StructError,
"not enough data to unpack %zd bytes at offset %zd",
self->s_size,
offset);
return NULL;
}

if (offset + buffer->len < 0) {
PyErr_Format(StructError,
"offset %zd out of range for %zd-byte buffer",
offset,
buffer->len);
return NULL;
}
offset += buffer->len;
if (offset < 0 || buffer->len - offset < self->s_size) {
}

if ((buffer->len - offset) < self->s_size) {
PyErr_Format(StructError,
"unpack_from requires a buffer of at least %zd bytes",
self->s_size);
"unpack_from requires a buffer of at least %zu bytes for "
"unpacking %zd bytes at offset %zd "
"(actual buffer size is %zd)",
(size_t)self->s_size + (size_t)offset,
self->s_size,
offset,
buffer->len);
return NULL;
}
return s_unpack_internal(self, (char*)buffer->buf + offset);
Expand Down
5 changes: 3 additions & 2 deletions Modules/clinic/_struct.c.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ PyDoc_STRVAR(Struct_unpack_from__doc__,
"\n"
"Values are unpacked according to the format string Struct.format.\n"
"\n"
"The buffer\'s size in bytes, minus offset, must be at least Struct.size.\n"
"The buffer\'s size in bytes, starting at position offset, must be\n"
"at least Struct.size.\n"
"\n"
"See help(struct) for more on format strings.");

Expand Down Expand Up @@ -302,4 +303,4 @@ iter_unpack(PyObject *module, PyObject *const *args, Py_ssize_t nargs)

return return_value;
}
/*[clinic end generated code: output=9119f213a951e4cc input=a9049054013a1b77]*/
/*[clinic end generated code: output=d79b009652ae0b89 input=a9049054013a1b77]*/