@@ -29,8 +29,8 @@ use crate::{
2929 PyMappingMethods , PyNumber , PyNumberMethods , PySequenceMethods ,
3030 } ,
3131 sliceable:: {
32- NameBytearray , SaturatedSlice , SaturatedSliceIter , SequenceIndex , SliceableSequenceMutOp ,
33- SliceableSequenceOp ,
32+ NameBytearray , SaturatedSlice , SaturatedSliceIter , SequenceIndex , SequenceIndexOp ,
33+ SliceableSequenceMutOp , SliceableSequenceOp ,
3434 } ,
3535 types:: {
3636 AsBuffer , AsMapping , AsNumber , AsSequence , Callable , Comparable , Constructor ,
@@ -98,6 +98,15 @@ enum ResizeAccess<'a> {
9898 Exported { len : usize } ,
9999}
100100
101+ impl ResizeAccess < ' _ > {
102+ fn len ( & self ) -> usize {
103+ match self {
104+ Self :: Exclusive ( inner) => inner. elements . len ( ) ,
105+ Self :: Exported { len } => * len,
106+ }
107+ }
108+ }
109+
101110pub ( crate ) type PyByteArrayRef = PyRef < PyByteArray > ;
102111
103112impl From < PyBytesInner > for PyByteArray {
@@ -201,6 +210,11 @@ impl PyByteArray {
201210 }
202211 }
203212
213+ fn len_snapshot ( & self ) -> usize {
214+ let exported_len = { self . exports . lock ( ) . snapshot ( ) } ;
215+ exported_len. unwrap_or_else ( || self . inner ( ) . elements . len ( ) )
216+ }
217+
204218 fn with_export < R > ( & self , f : impl FnOnce ( usize ) -> R ) -> R {
205219 let export = self . begin_export ( ) ;
206220 f ( export. len )
@@ -438,44 +452,48 @@ impl PyByteArray {
438452 }
439453 }
440454
455+ fn remove_by_index ( & self , index : isize , pop : bool , vm : & VirtualMachine ) -> PyResult < u8 > {
456+ let access = self . resize_access ( ) ;
457+ let len = access. len ( ) ;
458+ let index = index. wrapped_at ( len) . ok_or_else ( || {
459+ let message = if !pop {
460+ "bytearray index out of range"
461+ } else if len == 0 {
462+ "pop from empty bytearray"
463+ } else {
464+ "pop index out of range"
465+ } ;
466+ vm. new_index_error ( message)
467+ } ) ?;
468+ // The exported length permits exact bounds checking without waiting
469+ // on a payload lock already held by a codec callback.
470+ let ResizeAccess :: Exclusive ( mut inner) = access else {
471+ return Err ( vm. new_buffer_error ( "Existing exports of data: object cannot be re-sized" ) ) ;
472+ } ;
473+ Ok ( inner. elements . remove ( index) )
474+ }
475+
441476 fn delitem_by_index ( & self , index : isize , vm : & VirtualMachine ) -> PyResult < ( ) > {
442- // Validate before export rejection when the bytes are available without
443- // waiting. A codec callback may already hold a read guard with a writer
444- // queued, so a blocking read here could deadlock before that rejection.
445- let valid = self
446- . inner
447- . try_read ( )
448- . map ( |inner| inner. elements . wrap_index ( index) . is_some ( ) ) ;
449- if valid == Some ( false ) {
450- return Err ( vm. new_index_error ( "bytearray index out of range" ) ) ;
451- }
452- let mut inner = self . try_resizable ( vm) ?;
453- // Another thread may have resized the bytearray between the locks.
454- let index = inner
455- . elements
456- . wrap_index ( index)
457- . ok_or_else ( || vm. new_index_error ( "bytearray index out of range" ) ) ?;
458- inner. elements . remove ( index) ;
459- Ok ( ( ) )
477+ self . remove_by_index ( index, false , vm) . map ( drop)
460478 }
461479
462480 fn irepeat ( zelf : & Py < Self > , n : isize , vm : & VirtualMachine ) -> PyResult < ( ) > {
481+ if n <= 0 {
482+ return zelf. clear ( vm) ;
483+ }
463484 if n == 1 {
464485 return Ok ( ( ) ) ;
465486 }
466- let mut w = match zelf. try_resizable ( vm) {
467- Ok ( w) => w,
468- Err ( err) => {
469- return if zelf. borrow_buf ( ) . is_empty ( ) {
470- // We can multiple an empty vector by any integer
471- Ok ( ( ) )
472- } else {
473- Err ( err)
474- } ;
475- }
487+ let access = zelf. resize_access ( ) ;
488+ let len = access. len ( ) ;
489+ vm. check_repeat_or_overflow_error ( len, n) ?;
490+ if len == 0 {
491+ return Ok ( ( ) ) ;
492+ }
493+ let ResizeAccess :: Exclusive ( mut inner) = access else {
494+ return Err ( vm. new_buffer_error ( "Existing exports of data: object cannot be re-sized" ) ) ;
476495 } ;
477-
478- w. imul ( n, vm)
496+ inner. imul ( n, vm)
479497 }
480498}
481499
@@ -516,12 +534,30 @@ impl PyByteArray {
516534
517535 fn __iadd__ (
518536 zelf : PyRef < Self > ,
519- other : ArgBytesLike ,
537+ other : PyObjectRef ,
520538 vm : & VirtualMachine ,
521539 ) -> PyResult < PyRef < Self > > {
522- zelf. try_resizable ( vm) ?
523- . elements
524- . extend ( & * other. borrow_buf ( ) ) ;
540+ let buffer = ArgBytesLike :: try_from_object ( vm, other. clone ( ) ) . map_err ( |_| {
541+ vm. new_type_error ( format ! (
542+ "can't concat {} to {}" ,
543+ other. class( ) . name( ) ,
544+ zelf. class( ) . name( )
545+ ) )
546+ } ) ?;
547+ if !buffer. is_empty ( ) {
548+ // Reject aliases before reading their payload: a codec callback
549+ // may already hold that read lock with a writer queued behind it.
550+ drop ( zelf. try_resizable ( vm) ?) ;
551+ // Snapshot before locking the destination, but retain the export
552+ // until the write finishes, then recheck exports in assign_slice.
553+ let buffer = PyBuffer :: from ( buffer) ;
554+ let items = Self :: buffer_snapshot ( & buffer, vm) ?;
555+ zelf. assign_slice (
556+ SaturatedSlice :: from_parts ( isize:: MAX , isize:: MAX , 1 ) ,
557+ & items,
558+ vm,
559+ ) ?;
560+ }
525561 Ok ( zelf)
526562 }
527563
@@ -624,33 +660,50 @@ impl Py<PyByteArray> {
624660
625661 #[ pymethod]
626662 fn pop ( & self , index : PopArgs , vm : & VirtualMachine ) -> PyResult < u8 > {
627- let elements = & mut self . try_resizable ( vm) ?. elements ;
628- let index = elements
629- . wrap_index ( index. index )
630- . ok_or_else ( || vm. new_index_error ( "index out of range" ) ) ?;
631- Ok ( elements. remove ( index) )
663+ self . remove_by_index ( index. index , true , vm)
632664 }
633665
634666 #[ pymethod]
635667 fn insert ( & self , index : isize , item : PyObjectRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
636668 let value = value_from_object ( vm, & item) ?;
637669 let elements = & mut self . try_resizable ( vm) ?. elements ;
638670 let index = elements. saturate_index ( index) ;
671+ elements. try_reserve ( 1 ) . map_err ( |_| vm. no_memory_error ( ) ) ?;
639672 elements. insert ( index, value) ;
640673 Ok ( ( ) )
641674 }
642675
643676 #[ pymethod]
644677 fn append ( & self , item : PyObjectRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
645678 let value = value_from_object ( vm, & item) ?;
646- self . try_resizable ( vm) ?. elements . push ( value) ;
679+ let elements = & mut self . try_resizable ( vm) ?. elements ;
680+ elements. try_reserve ( 1 ) . map_err ( |_| vm. no_memory_error ( ) ) ?;
681+ elements. push ( value) ;
647682 Ok ( ( ) )
648683 }
649684
650685 #[ pymethod]
651686 fn remove ( & self , value : PyObjectRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
652687 let byte = value_from_object ( vm, & value) ?;
653- let elements = & mut self . try_resizable ( vm) ?. elements ;
688+ let mut inner = match self . resize_access ( ) {
689+ ResizeAccess :: Exclusive ( inner) => inner,
690+ ResizeAccess :: Exported { len } => {
691+ // Missing values precede export rejection. Do not wait for a
692+ // read lock: a reentrant codec can have a writer queued behind
693+ // its existing read guard.
694+ let missing = len == 0
695+ || self
696+ . inner
697+ . try_read ( )
698+ . is_some_and ( |inner| inner. elements . find_byte ( byte) . is_none ( ) ) ;
699+ return Err ( if missing {
700+ vm. new_value_error ( "value not found in bytearray" )
701+ } else {
702+ vm. new_buffer_error ( "Existing exports of data: object cannot be re-sized" )
703+ } ) ;
704+ }
705+ } ;
706+ let elements = & mut inner. elements ;
654707 let index = elements
655708 . find_byte ( byte)
656709 . ok_or_else ( || vm. new_value_error ( "value not found in bytearray" ) ) ?;
@@ -663,6 +716,13 @@ impl Py<PyByteArray> {
663716 if self . is ( & iterable_of_ints) {
664717 return PyByteArray :: irepeat ( self , 2 , vm) ;
665718 }
719+ // Buffer acquisition can mutate self. The buffer path inserts at the
720+ // old end, whereas the iterable path appends after all conversions.
721+ let start = if iterable_of_ints. check_buffer ( ) {
722+ self . len_snapshot ( ) as isize
723+ } else {
724+ isize:: MAX
725+ } ;
666726 // bytearray_setslice keeps the export alive across the resize, so a value
667727 // looking at this bytearray is what stops it from growing.
668728 let buffer = iterable_of_ints
@@ -679,16 +739,21 @@ impl Py<PyByteArray> {
679739 } )
680740 . transpose ( ) ?;
681741 let items = match & buffer {
682- Some ( buffer) => buffer
683- . as_contiguous ( )
684- . ok_or_else ( || {
685- vm. new_buffer_error ( "non-contiguous buffer is not a bytes-like object" )
686- } ) ?
687- . to_vec ( ) ,
742+ Some ( buffer) => {
743+ if !buffer. desc . is_contiguous ( ) {
744+ return Err (
745+ vm. new_buffer_error ( "non-contiguous buffer is not a bytes-like object" )
746+ ) ;
747+ }
748+ if buffer. desc . len == 0 {
749+ return Ok ( ( ) ) ;
750+ }
751+ drop ( self . try_resizable ( vm) ?) ;
752+ PyByteArray :: buffer_snapshot ( buffer, vm) ?
753+ }
688754 None => bytearray_extend_from_object ( vm, & iterable_of_ints) ?,
689755 } ;
690- self . try_resizable ( vm) ?. elements . extend ( items) ;
691- Ok ( ( ) )
756+ self . assign_slice ( SaturatedSlice :: from_parts ( start, start, 1 ) , & items, vm)
692757 }
693758
694759 #[ pymethod]
@@ -734,10 +799,7 @@ impl Py<PyByteArray> {
734799 }
735800 } ;
736801 let access = self . resize_access ( ) ;
737- let len = match & access {
738- ResizeAccess :: Exclusive ( inner) => inner. elements . len ( ) ,
739- ResizeAccess :: Exported { len } => * len,
740- } ;
802+ let len = access. len ( ) ;
741803 // Vec<u8> lengths fit isize. Adding a nonnegative length to a negative
742804 // index cannot overflow; preserve the adjusted index in the error.
743805 let to_take = match n {
@@ -1195,10 +1257,7 @@ impl Py<PyByteArray> {
11951257 let size = size as usize ;
11961258 // An export's stable length avoids re-entering the payload lock.
11971259 let access = self . resize_access ( ) ;
1198- let len = match & access {
1199- ResizeAccess :: Exclusive ( inner) => inner. elements . len ( ) ,
1200- ResizeAccess :: Exported { len } => * len,
1201- } ;
1260+ let len = access. len ( ) ;
12021261 if size == len {
12031262 return Ok ( ( ) ) ;
12041263 }
@@ -1458,9 +1517,8 @@ impl AsSequence for PyByteArray {
14581517 PyByteArray :: sequence_downcast( seq) . __contains__( other. to_owned( ) , vm)
14591518 } ) ,
14601519 inplace_concat : atomic_func ! ( |seq, other, vm| {
1461- let other = ArgBytesLike :: try_from_object( vm, other. to_owned( ) ) ?;
14621520 let zelf = PyByteArray :: sequence_downcast( seq) . to_owned( ) ;
1463- PyByteArray :: __iadd__( zelf, other, vm) . map( |x| x. into( ) )
1521+ PyByteArray :: __iadd__( zelf, other. to_owned ( ) , vm) . map( |x| x. into( ) )
14641522 } ) ,
14651523 inplace_repeat : atomic_func ! ( |seq, n, vm| {
14661524 let zelf = PyByteArray :: sequence_downcast( seq) . to_owned( ) ;
0 commit comments