Skip to content

Commit 9227f6d

Browse files
committed
added isaligned check
1 parent 1765671 commit 9227f6d

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

src/methods.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,13 @@ AK_fill_transition_slices_1d(PyArrayObject* working, npy_intp size, PyObject* sl
422422
npy_intp stride = PyArray_STRIDE(working, 0);
423423
npy_intp itemsize = PyArray_ITEMSIZE(working);
424424

425-
if (stride == itemsize) {
425+
// stride == itemsize proves the data is packed, but the typed fast-path
426+
// below casts `base` to a concrete C type and dereferences v[i] directly,
427+
// which is undefined behavior on a misaligned buffer (and can SIGBUS on
428+
// strict-alignment platforms). Contiguity and alignment are independent in
429+
// numpy, so require PyArray_ISALIGNED explicitly; anything unaligned falls
430+
// through to the memcpy/memcmp-based generic scan, which is alignment-safe.
431+
if (stride == itemsize && PyArray_ISALIGNED(working)) {
426432
switch (PyArray_TYPE(working)) {
427433
case NPY_DOUBLE: {
428434
// == honors IEEE semantics: NaN != NaN, +0.0 == -0.0

test/test_transition_slices_from_group.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,35 @@ def test_transition_slices_from_group_1d_object_a(self) -> None:
2828
[(0, 2, None), (2, 4, None), (4, None, None)],
2929
)
3030

31+
def test_transition_slices_from_group_1d_unaligned(self) -> None:
32+
# A contiguous (stride == itemsize) but *unaligned* buffer must not take
33+
# the typed fast-path, whose pointer-cast dereferences are UB on
34+
# misaligned data; it should fall through to the alignment-safe scan and
35+
# still produce results identical to an aligned copy.
36+
def make_unaligned(values, dtype):
37+
dt = np.dtype(dtype)
38+
n = len(values)
39+
raw = np.empty(n * dt.itemsize + dt.itemsize, dtype=np.uint8)
40+
# view starting one byte in -> contiguous but misaligned
41+
a = raw[1 : 1 + n * dt.itemsize].view(dt)
42+
a[:] = values
43+
return a
44+
45+
for dtype, values in (
46+
(np.float64, [1.0, 1.0, 2.0, 2.0, 2.0, 3.0]),
47+
(np.int64, [10, 10, 10, 20, 20, 30]),
48+
(np.int32, [5, 5, 6, 7, 7]),
49+
):
50+
a = make_unaligned(values, dtype)
51+
self.assertTrue(a.flags.c_contiguous)
52+
self.assertFalse(a.flags.aligned)
53+
ref = np.array(values, dtype=dtype) # fresh, aligned
54+
55+
slices, group_to_tuple = transition_slices_from_group(a)
56+
ref_slices, _ = transition_slices_from_group(ref)
57+
self.assertFalse(group_to_tuple)
58+
self.assertEqual(slices_to_pairs(slices), slices_to_pairs(ref_slices))
59+
3160
def test_transition_slices_from_group_2d_a(self) -> None:
3261
group = np.array(
3362
[

0 commit comments

Comments
 (0)