mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Release bytearray initializer storage and check deletion indices
Reset initializer storage before zero-count callbacks and returns. Validate integer deletion indices with a nonblocking snapshot before export rejection where possible, then revalidate under the resize guard. Preserve fast export rejection when contended to avoid codec callback deadlocks. Add Python regressions for exported-empty rejection, initializer capacity release, callback ordering, and ordinary deletion error precedence. Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
1 parent
d0819a2fab
commit
fcd4f4c8ae
2 files changed
+94
-3
No files matched your search
@@ -140,7 +140,7 @@ impl PyByteArray {
|
||||
|
||||
pub fn _delitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
|
||||
match SequenceIndex::try_from_borrowed_object::<NameBytearray>(vm, needle)? {
|
||||
SequenceIndex::Int(i) => self.try_resizable(vm)?.elements.delitem_by_index(vm, i),
|
||||
SequenceIndex::Int(i) => self.delitem_by_index(i, vm),
|
||||
SequenceIndex::Slice(slice) => {
|
||||
// TODO: delete 0 elements don't need resizable
|
||||
self.try_resizable(vm)?.elements.delitem_by_slice(vm, slice)
|
||||
@@ -148,6 +148,27 @@ impl PyByteArray {
|
||||
}
|
||||
}
|
||||
|
||||
fn delitem_by_index(&self, index: isize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// Validate before export rejection when the bytes are available without
|
||||
// waiting. A codec callback may already hold a read guard with a writer
|
||||
// queued, so a blocking read here could deadlock before that rejection.
|
||||
let valid = self
|
||||
.inner
|
||||
.try_read()
|
||||
.map(|inner| inner.elements.wrap_index(index).is_some());
|
||||
if valid == Some(false) {
|
||||
return Err(vm.new_index_error("bytearray index out of range"));
|
||||
}
|
||||
let mut inner = self.try_resizable(vm)?;
|
||||
// Another thread may have resized the bytearray between the locks.
|
||||
let index = inner
|
||||
.elements
|
||||
.wrap_index(index)
|
||||
.ok_or_else(|| vm.new_index_error("bytearray index out of range"))?;
|
||||
inner.elements.remove(index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn irepeat(zelf: &Py<Self>, n: isize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if n == 1 {
|
||||
return Ok(());
|
||||
@@ -777,7 +798,7 @@ impl Initializer for PyByteArray {
|
||||
|
||||
fn init(zelf: &Py<Self>, options: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// Reject exports and clear existing contents before converting source.
|
||||
zelf.try_resizable(vm)?.elements.clear();
|
||||
zelf.try_resizable(vm)?.elements = Vec::new();
|
||||
// Conversion can call Python, so do not hold the lock across it and
|
||||
// check again for exports acquired by a callback or another thread.
|
||||
let (mut inner, zero_count) =
|
||||
@@ -918,7 +939,7 @@ impl AsSequence for PyByteArray {
|
||||
if let Some(value) = value {
|
||||
zelf._setitem_by_index(i, &value, vm)
|
||||
} else {
|
||||
zelf.try_resizable(vm)?.elements.delitem_by_index(vm, i)
|
||||
zelf.delitem_by_index(i, vm)
|
||||
}
|
||||
}),
|
||||
contains: atomic_func!(|seq, other, vm| {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Initializer storage and integer-deletion ordering with buffer exports."""
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
|
||||
class Zero:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.observations = []
|
||||
|
||||
def __index__(self):
|
||||
self.observations.append((len(self.value), self.value.__alloc__()))
|
||||
return 0
|
||||
|
||||
|
||||
# Final CPython rejects even an empty exported initializer before conversion.
|
||||
for kind in ("missing", "integer", "callback"):
|
||||
value = bytearray()
|
||||
source = Zero(value)
|
||||
with memoryview(value) as view:
|
||||
with assert_raises(BufferError):
|
||||
if kind == "missing":
|
||||
value.__init__()
|
||||
elif kind == "integer":
|
||||
value.__init__(0)
|
||||
else:
|
||||
value.__init__(source)
|
||||
assert value == b""
|
||||
assert view.tobytes() == b""
|
||||
assert source.observations == []
|
||||
|
||||
# Clearing an initializer must release its old storage before callbacks and
|
||||
# before the successful zero-count early return.
|
||||
for kind in ("missing", "integer", "callback"):
|
||||
value = bytearray(b"x" * 64)
|
||||
source = Zero(value)
|
||||
assert value.__alloc__() > 0
|
||||
if kind == "missing":
|
||||
value.__init__()
|
||||
elif kind == "integer":
|
||||
value.__init__(0)
|
||||
else:
|
||||
value.__init__(source)
|
||||
assert source.observations == [(0, 0)]
|
||||
assert value == b""
|
||||
assert value.__alloc__() == 0
|
||||
|
||||
# An invalid integer index is rejected before checking whether resizing is
|
||||
# blocked, and neither error path may alter the exported bytes.
|
||||
for initial, indices in ((b"abc", (3, -4)), (b"", (0, -1))):
|
||||
for index in indices:
|
||||
value = bytearray(initial)
|
||||
with memoryview(value) as view:
|
||||
try:
|
||||
del value[index]
|
||||
except IndexError as exc:
|
||||
assert str(exc) == "bytearray index out of range"
|
||||
else:
|
||||
raise AssertionError("invalid index was accepted")
|
||||
assert value == initial
|
||||
assert view.tobytes() == initial
|
||||
|
||||
for index in (0, -1):
|
||||
value = bytearray(b"abc")
|
||||
with memoryview(value) as view:
|
||||
with assert_raises(BufferError):
|
||||
del value[index]
|
||||
assert view.tobytes() == b"abc"
|
||||
del value[index]
|
||||
assert value == (b"bc" if index == 0 else b"ab")
|
||||
Reference in new issue
Block a user