8000 gh-140557: Force alignment of empty `bytearray` and `array.array` buf… · python/cpython@19de10d · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 19de10d

Browse files
authored
gh-140557: Force alignment of empty bytearray and array.array buffers (GH-140559)
This ensures the buffers used by the empty `bytearray` and `array.array` are aligned the same as a pointer returned by the allocator. This is a more convenient default for interop with other languages that have stricter requirements of type-safe buffers (e.g. Rust's `&[T]` type) even when empty.
1 parent 1f55caf commit 19de10d

File tree

6 files changed

+65
-1
lines changed

6 files changed

+65
-1
lines changed

Lib/test/test_buffer.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4447,6 +4447,41 @@ def test_bytearray_release_buffer_read_flag(self):
44474447
with self.assertRaises(SystemError):
44484448
obj.__buffer__(inspect.BufferFlags.WRITE)
44494449

4450+
@support.cpython_only
4451+
@unittest.skipIf(_testcapi is None, "requires _testcapi")
4452+
def test_bytearray_alignment(self):
4453+
# gh-140557: pointer alignment of buffers including empty allocation
4454+
# should be at least to `size_t`.
4455+
align = struct.calcsize("N")
4456+
cases = [
4457+
bytearray(),
4458+
bytearray(1),
4459+
bytearray(b"0123456789abcdef"),
4460+
bytearray(16),
4461+
]
4462+
ptrs = [_testcapi.buffer_pointer_as_int(array) for array in cases]
4463+
self.assertEqual([ptr % align for ptr in ptrs], [0]*len(ptrs))
4464+
4465+
@support.cpython_only
4466+
@unittest.skipIf(_testcapi is None, "requires _testcapi")
4467+
def test_array_alignment(self):
4468+
# gh-140557: pointer alignment of buffers including empty allocation
4469+
# should match the maximum array alignment.
4470+
align = max(struct.calcsize(fmt) for fmt in ARRAY)
4471+
cases = [array.array(fmt) for fmt in ARRAY]
4472+
# Empty arrays
4473+
self.assertEqual(
4474+
[_testcapi.buffer_pointer_as_int(case) % align for case in cases],
4475+
[0] * len(cases),
4476+
)
4477+
for case in cases:
4478+
case.append(0)
4479+
# Allocated arrays
4480+
self.assertEqual(
4481+
[_testcapi.buffer_pointer_as_int(case) % align for case in cases],
4482+
[0] * len(cases),
4483+
)
4484+
44504485
@support.cpython_only
44514486
def test_pybuffer_size_from_format(self):
44524487
# basic tests

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,7 @@ Per Lindqvist
11511151
Eric Lindvall
11521152
Gregor Lingl
11531153
Everett Lipman
1154+
Jake Lishman
11541155
Mirko Liss
11551156
Alexander Liu
11561157
Hui Liu
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`bytearray` buffers now have the same alignment
2+
when empty as when allocated. Unaligned buffers can still be created by slicing.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`array.array` buffers now have the same alignment when empty as when
2+
allocated. Unaligned buffers can still be created by slicing.

Modules/_testcapi/buffer.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,27 @@ static PyTypeObject testBufType = {
9898
.tp_members = testbuf_members
9999
};
100100

101+
/* Get the pointer from a buffer-supporting object as a PyLong.
102+
*
103+
* Used to test alignment properties. */
104+
static PyObject *
105+
buffer_pointer_as_int(PyObject *Py_UNUSED(module), PyObject *obj)
106+
{
107+
PyObject *out;
108+
Py_buffer view;
109+
if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) != 0) {
110+
return NULL;
111+
}
112+
out = PyLong_FromVoidPtr(view.buf);
113+
PyBuffer_Release(&view);
114+
return out;
115+
}
116+
117+
static PyMethodDef test_methods[] = {
118+
{"buffer_pointer_as_int", buffer_pointer_as_int, METH_O},
119+
{NULL},
120+
};
121+
101122
int
102123
_PyTestCapi_Init_Buffer(PyObject *m) {
103124
if (PyType_Ready(&testBufType) < 0) {
@@ -106,6 +127,9 @@ _PyTestCapi_Init_Buffer(PyObject *m) {
106127
if (PyModule_AddObjectRef(m, "testBuf", (PyObject *)&testBufType)) {
107128
return -1;
108129
}
130+
if (PyModule_AddFunctions(m, test_methods) < 0) {
131+
return -1;
132+
}
109133

110134
return 0;
111135
}

Modules/arraymodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2664,7 +2664,7 @@ array_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
26642664
}
26652665
}
26662666

2667-
static const void *emptybuf = "";
2667+
static const _Py_ALIGNED_DEF(ALIGNOF_MAX_ALIGN_T, char) emptybuf[] = "";
26682668

26692669

26702670
static int

0 commit comments

Comments
 (0)
0