Finish source-less bytearray initialization after clearing

Preserve option validation and initial export rejection, then avoid a redundant resize guard and storage swap when no source was provided. Keep zero-count callback behavior and all existing regressions.

Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
dot committed 2026-10-12 14:48:48 +09:00
1 parent f163e13710
commit 108171daa2
2 files changed
+55 -4

No files matched your search

+6 -4
View File
@@ -23,7 +23,7 @@ use crate::{
}, },
}, },
convert::{ToPyObject, ToPyResult}, convert::{ToPyObject, ToPyResult},
function::{ArgBytesLike, FuncArgs, PyComparisonValue, PySsize}, function::{ArgBytesLike, FuncArgs, OptionalArg, PyComparisonValue, PySsize},
protocol::{ protocol::{
BufferDescriptor, BufferFlags, BufferMethods, BufferResizeGuard, PyBuffer, PyIterReturn, BufferDescriptor, BufferFlags, BufferMethods, BufferResizeGuard, PyBuffer, PyIterReturn,
PyMappingMethods, PyNumberMethods, PySequenceMethods, PyMappingMethods, PyNumberMethods, PySequenceMethods,
@@ -924,15 +924,17 @@ impl Initializer for PyByteArray {
type Args = ByteInnerNewOptions; type Args = ByteInnerNewOptions;
fn init(zelf: &Py<Self>, options: Self::Args, vm: &VirtualMachine) -> PyResult<()> { fn init(zelf: &Py<Self>, options: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
let source_is_missing = matches!(&options.source, OptionalArg::Missing);
// Reject exports and clear existing contents before converting source. // Reject exports and clear existing contents before converting source.
zelf.try_resizable(vm)?.elements = Vec::new(); zelf.try_resizable(vm)?.elements = Vec::new();
// Conversion can call Python, so do not hold the lock across it and // Conversion can call Python, so do not hold the lock across it and
// check again for exports acquired by a callback or another thread. // check again for exports acquired by a callback or another thread.
let (mut inner, zero_count) = let (mut inner, zero_count) =
options.get_inner_with_zero_count(bytearray_from_object, vm)?; options.get_inner_with_zero_count(bytearray_from_object, vm)?;
// A zero integer count performs no further writes, including when its // Missing sources and zero integer counts perform no further writes.
// __index__ callback changed this bytearray or acquired an export. // Argument validation above still rejects encoding/errors without a
if zero_count { // source; a zero __index__ callback may have changed or exported self.
if source_is_missing || zero_count {
return Ok(()); return Ok(());
} }
core::mem::swap(&mut *zelf.try_resizable(vm)?, &mut inner); core::mem::swap(&mut *zelf.try_resizable(vm)?, &mut inner);
+49
View File
@@ -1504,3 +1504,52 @@ def test_bytearray_callback_exports():
test_bytearray_callback_exports() test_bytearray_callback_exports()
def test_bytearray_missing_source_init():
from testutils import assert_raises
for initial in (b"", b"previous contents"):
value = bytearray(initial)
assert value.__init__() is None
assert value == b""
assert value.__alloc__() == 0
for kwargs, message in (
({"encoding": "utf-8"}, "encoding without a string argument"),
({"errors": "strict"}, "errors without a string argument"),
(
{"encoding": "utf-8", "errors": "strict"},
"encoding without a string argument",
),
):
value = bytearray(initial)
with assert_raises(TypeError) as caught:
value.__init__(**kwargs)
assert str(caught.exception) == message
assert value == b""
assert value.__alloc__() == 0
for kwargs in (
{},
{"encoding": "utf-8"},
{"errors": "strict"},
{"encoding": "utf-8", "errors": "strict"},
):
value = bytearray(initial)
with memoryview(value) as view:
with assert_raises(BufferError):
value.__init__(**kwargs)
assert value == initial
assert view.tobytes() == initial
assert value.__init__() is None
assert value == b""
# Explicit None is a source and must still go through conversion.
value = bytearray(initial)
with assert_raises(TypeError):
value.__init__(None)
assert value == b""
test_bytearray_missing_source_init()