mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Fix bytearray membership racing with resizing (#9091)
* Fix bytearray membership racing with resizing Search exact bytes and int needles under the data read lock instead of taking a temporary export. This prevents a concurrent slice replacement from receiving BufferError for a callback-free membership operation. Preserve export protection for generic index/buffer callbacks and actual memoryviews. Add Python coverage for these contracts. AI assistance: Codex/OpenAI diagnosed the race, implemented and reviewed the patch and regression snippet, and ran the reported validation. The runtime did not expose an exact model version. Assisted-by: Codex:model-version-unavailable * Handle bool needles and consolidate bytearray regression coverage Include exact builtin bool in the callback-free integer membership path. Keep int subclasses and generic conversion paths under export protection. Move this PR's regression coverage into the existing bytearray snippet and cover True/False membership as requested in review. AI assistance: Codex/OpenAI implemented and independently reviewed the review follow-up and ran the reported validation. The runtime did not expose an exact model version. Assisted-by: Codex:model-version-unavailable
This commit is contained in:
1 parent
8f47ced2c7
commit
e09846b4eb
2 files changed
+100
-2
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! Implementation of the python bytearray object.
|
||||
use super::{
|
||||
PositionIterInternal, PyBytes, PyDictRef, PyGenericAlias, PyStrRef, PyTuple, PyTupleRef,
|
||||
PositionIterInternal, PyBytes, PyDictRef, PyGenericAlias, PyInt, PyStrRef, PyTuple, PyTupleRef,
|
||||
PyType, PyTypeRef, iter::builtins_iter, locked_next,
|
||||
};
|
||||
use crate::{
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
bytes_inner::{
|
||||
ByteInnerFindOptions, ByteInnerHexOptions, ByteInnerNewOptions, ByteInnerPaddingOptions,
|
||||
ByteInnerReplaceOptions, ByteInnerSplitOptions, ByteInnerStripOptions, ByteInnerSub,
|
||||
ByteInnerTranslateOptions, BytesJoin, DecodeArgs, PyBytesInner, bytes_decode,
|
||||
ByteInnerTranslateOptions, ByteOr, BytesJoin, DecodeArgs, PyBytesInner, bytes_decode,
|
||||
},
|
||||
class::PyClassImpl,
|
||||
common::{
|
||||
@@ -317,6 +317,21 @@ impl PyByteArray {
|
||||
}
|
||||
|
||||
fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
// Exact builtins cannot call Python while preparing the needle. The
|
||||
// read lock alone protects their search; a temporary export would make
|
||||
// a concurrent resize fail instead of waiting for that lock.
|
||||
if let Some(bytes) = needle.downcast_ref_if_exact::<PyBytes>(vm) {
|
||||
return Ok(self.borrow_buf().contains_str(bytes.as_bytes()));
|
||||
}
|
||||
let int = if needle.class().is(vm.ctx.types.bool_type) {
|
||||
needle.downcast_ref::<PyInt>()
|
||||
} else {
|
||||
needle.downcast_ref_if_exact::<PyInt>(vm)
|
||||
};
|
||||
if let Some(int) = int {
|
||||
let byte = int.as_bigint().byte_or(vm)?;
|
||||
return Ok(self.borrow_buf().contains(&byte));
|
||||
}
|
||||
// Converting the needle runs Python, which must not resize this bytearray.
|
||||
self.exports.fetch_add(1, Ordering::Release);
|
||||
scopeguard::defer! { self.exports.fetch_sub(1, Ordering::Release); }
|
||||
|
||||
@@ -1504,3 +1504,86 @@ def test_bytearray_callback_exports():
|
||||
|
||||
|
||||
test_bytearray_callback_exports()
|
||||
|
||||
|
||||
def test_bytearray_contains_builtin_and_callback_exports():
|
||||
for value in (b"", b"a", b"ab\x00\xff", b"\x01"):
|
||||
target = bytearray(value)
|
||||
for needle in (
|
||||
b"",
|
||||
b"a",
|
||||
b"ab",
|
||||
b"z",
|
||||
b"\x00",
|
||||
b"\xff",
|
||||
0,
|
||||
97,
|
||||
255,
|
||||
False,
|
||||
True,
|
||||
):
|
||||
assert (needle in target) == (needle in value)
|
||||
assert target in target
|
||||
|
||||
def must_raise_buffer_error(action):
|
||||
try:
|
||||
action()
|
||||
except BufferError:
|
||||
return
|
||||
raise AssertionError("resize was not rejected")
|
||||
|
||||
target = bytearray(b"a")
|
||||
view = memoryview(target)
|
||||
try:
|
||||
assert b"a" in target
|
||||
assert 97 in target
|
||||
must_raise_buffer_error(lambda: target.append(0))
|
||||
target[:] = b"b"
|
||||
assert b"b" in target
|
||||
assert 98 in target
|
||||
finally:
|
||||
view.release()
|
||||
target.append(0)
|
||||
assert target == b"b\x00"
|
||||
|
||||
class IndexNeedle:
|
||||
def __index__(self):
|
||||
must_raise_buffer_error(lambda: target.append(0))
|
||||
target[0] = 98
|
||||
return 98
|
||||
|
||||
class BytesNeedle(bytes):
|
||||
__index__ = IndexNeedle.__index__
|
||||
|
||||
for needle in (IndexNeedle(), BytesNeedle(b"\xdd")):
|
||||
target[:] = b"a"
|
||||
assert needle in target
|
||||
assert target == b"b"
|
||||
target.append(0)
|
||||
assert target == b"b\x00"
|
||||
|
||||
for needle in (-1, 256):
|
||||
with assert_raises(ValueError):
|
||||
needle in target
|
||||
|
||||
events = [0, 0]
|
||||
|
||||
class BufferNeedle:
|
||||
def __buffer__(self, flags):
|
||||
must_raise_buffer_error(lambda: target.append(0))
|
||||
target[0] = 98
|
||||
events[0] += 1
|
||||
return memoryview(b"b")
|
||||
|
||||
def __release_buffer__(self, view):
|
||||
must_raise_buffer_error(lambda: target.append(0))
|
||||
events[1] += 1
|
||||
|
||||
target[:] = b"a"
|
||||
assert BufferNeedle() in target
|
||||
assert events == [1, 1]
|
||||
target.append(0)
|
||||
assert target == b"b\x00"
|
||||
|
||||
|
||||
test_bytearray_contains_builtin_and_callback_exports()
|
||||
Reference in new issue
Block a user