Skip to content
Open
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
46 changes: 45 additions & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"BrokenIter",
"in_systemd_nspawn_sync_suppressed",
"run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
"reset_code", "on_github_actions"
"reset_code", "on_github_actions", "ctypes_py_buffer",
]


Expand Down Expand Up @@ -3184,3 +3184,47 @@ def linked_to_musl():
return _linked_to_musl
_linked_to_musl = tuple(map(int, version.split('.')))
return _linked_to_musl


try:
Copy link
Contributor

@cmaloney cmaloney Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will be easier to do in Lib/test/test_capi/test_misc.py. In particular only need to

  1. Add a new _testcapimodule.c entry point that makes a Py_buffer C API of the PyObject passed to it, gets the pointer and turns that pointer into a PyObject * (https://docs.python.org/3/c-api/long.html#c.PyLong_FromVoidPtr) which it returns
  2. one alignment test across the range of types and constructions care about.

I like how your existing test iterates through / tests all the different array typecodes.

I think it would be good to extend the test to both test empty (the size that caused this bug) + non-empty arrays (they should also be aligned)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually found a better test file for the alignment pieces to live in: Lib/test/test_buffer.py; still should implement "get the pointer" in _testcapimodule.c

import ctypes

class _py_buffer(ctypes.Structure):
_fields_ = [
("buf", ctypes.c_void_p),
("obj", ctypes.py_object),
("len", ctypes.c_ssize_t),
("itemsize", ctypes.c_ssize_t),
("readonly", ctypes.c_int),
("ndim", ctypes.c_int),
("format", ctypes.c_char_p),
("shape", ctypes.POINTER(ctypes.c_ssize_t)),
("strides", ctypes.POINTER(ctypes.c_ssize_t)),
("suboffsets", ctypes.POINTER(ctypes.c_ssize_t)),
("internal", ctypes.c_void_p),
]
except ImportError:
_py_buffer = None


@contextlib.contextmanager
def ctypes_py_buffer(ob, flags=inspect.BufferFlags.SIMPLE):
"""
Safely acquire a `Py_buffer` as a ctypes struct.

`ob` must implement the buffer protocol, and the retrieved buffer is
released on exit of the context manager.

Skips any test using it if `ctypes` is unavailable.
"""
from .import_helper import import_module

ctypes = import_module("ctypes")
buf = _py_buffer()
ctypes.pythonapi.PyObject_GetBuffer(ctypes.py_object(ob),
ctypes.byref(buf),
ctypes.c_int(flags))
try:
yield buf
finally:
ctypes.pythonapi.PyBuffer_Release(ctypes.byref(buf))
16 changes: 16 additions & 0 deletions Lib/test/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from test.support import import_helper
from test.support import os_helper
from test.support import _2G
from test.support import ctypes_py_buffer
import weakref
import pickle
import operator
Expand Down Expand Up @@ -67,6 +68,21 @@ def test_empty(self):
a += a
self.assertEqual(len(a), 0)

def test_empty_alignment(self):
# gh-140557: pointer alignment of empty allocation
ctypes = import_helper.import_module("ctypes")
self.enterContext(warnings.catch_warnings())
warnings.filterwarnings(
"ignore",
message="The 'u' type code is deprecated and "
"will be removed in Python 3.16",
category=DeprecationWarning)
max_align = ctypes.alignment(ctypes.c_longdouble)
for typecode in typecodes:
a = array.array(typecode)
with ctypes_py_buffer(a) as buf:
self.assertEqual(buf.buf % max_align, 0)


# Machine format codes.
#
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from test.support.testcase import ComplexesAreIdenticalMixin
from test.support.warnings_helper import check_warnings
from test.support import requires_IEEE_754
from test.support import ctypes_py_buffer
from unittest.mock import MagicMock, patch
try:
import pty, signal
Expand Down Expand Up @@ -2404,6 +2405,14 @@ def iterator():
yield b'B'
self.assertEqual(bytearray(b'A,B'), array.join(iterator()))

def test_bytearray_empty_alignment(self):
# gh-140557: alignment of pointer in empty allocation
ctypes = import_module("ctypes")
max_align = ctypes.alignment(ctypes.c_longdouble)
array = bytearray()
with ctypes_py_buffer(array) as buf:
self.assertEqual(buf.buf % max_align, 0)

def test_construct_singletons(self):
for const in None, Ellipsis, NotImplemented:
tp = type(const)
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,7 @@ Per Lindqvist
Eric Lindvall
Gregor Lingl
Everett Lipman
Jake Lishman
Mirko Liss
Alexander Liu
Hui Liu
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`bytearray` and :func:`array.array` now default to aligned buffers when
empty. Unaligned buffers can still be created by slicing.
2 changes: 1 addition & 1 deletion Modules/arraymodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2655,7 +2655,7 @@ array_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
}
}

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


static int
Expand Down
2 changes: 1 addition & 1 deletion Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class bytearray "PyByteArrayObject *" "&PyByteArray_Type"
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=5535b77c37a119e0]*/

/* For PyByteArray_AS_STRING(). */
char _PyByteArray_empty_string[] = "";
_Py_ALIGNED_DEF(ALIGNOF_MAX_ALIGN_T, char) _PyByteArray_empty_string[] = "";

/* Helpers */

Expand Down
Loading