Repository navigation
Conversation
92f4673 to
1148eba
Compare
Use a single unsigned bounds compare, say in the error message that negative indices are not supported, document the behavior on the accessors, and cover offset_at/offset_at_t in test_bounds_check. Assisted-by: ClaudeCode:claude-opus-5-5
henryiii
left a comment
There was a problem hiding this comment.
Since negative indices currently don't work, and we do check the upper edge, seems to make sense to do this. I pushed a slightly faster version.
|
This PR is fine, but I'm exploring supporting negative indices, because that's more in line with the usual expectations for NumPy arrays. |
|
codex GPT-6.1-Sol xhigh (some links below are broken; I didn't want to spend the time weeding them out) User — 2026-10-10T11:08:56-07:00 (PDT) · +6m 19s after Codex · prior Codex turn: 56sHm, so as it stands, the PR goes from UB to "negative indices not supported", i.e. from UB to deliberately defined behavior. If we want to support negative indices later, it would be a change of defined behavior. That doesn't seem desirable. I guess the cost for supporting negative indices is:
Does that sound right? WDYT about this reasoning? Do you think it's worth working out the negative index wrapping code, to see how it compares in detail? Codex — 2026-10-10T11:25:25-07:00 (PDT) · +3m 5s after Codex · +16m 29s since UserI agree with your reasoning. Once rejection is documented, callers can rely on I worked out a prototype patch. It uses NumPy’s rule: add the dimension size once when an index is negative, then check bounds. Values below The implementation cost is small:
The runtime cost is measurable. For positive indices, an isolated C++ benchmark gave these median times per offset calculation:
These used I also tried moving normalization behind the existing bounds check into a separate helper. That helped Clang but hurt GCC, so I kept the simpler implementation. I’d lean toward supporting negative indices in this PR. Your compatibility argument is persuasive, the implementation stays small, and the remaining tradeoff is performance in tight checked-access loops. The uncommitted prototype is on |
Description
array::check_dimensions_impl(reached throughbyte_offset,offset_at,index_at,data,mutable_data, andarray_t::at/mutable_at) only testedi >= *shape, andarray::shape(dim)/array::strides(dim)only testeddim >= ndim(). A negative index or axis forwarded from Python passed the out-of-bounds guard and yielded a negative byte offset, i.e. an access before the start of the buffer. Forat/datathat is an out-of-bounds read; formutable_at/mutable_datait is an out-of-bounds write at a caller-chosen negative offset.Built against
py::array_t<uint16_t>,a.at(-1)reads two bytes before the allocation (ASan reports heap-buffer-overflow) anda.mutable_at(-4096) = vwrites far below it. These accessors already document that they throw when an index is out of bounds; a negative value is out of bounds, so the lower-bound check belongs in the accessor rather than at every binding call site. With the fix all three raiseindex_errorthe same way an over-large index does.Added negative-index and negative-axis cases to the existing
test_bounds_checkandtest_array_attributes; they fail on the current code and pass with the fix.Suggested changelog entry:
array/array_telement and shape/stride accessors now raiseIndexErrorinstead of addressing memory before the start of the buffer.