@@ -37,6 +37,21 @@ use crate::protocol::Version;
3737use crate :: stack:: { InstanceObject , StackObject , StackObjectRef } ;
3838use std:: collections:: { HashMap , HashSet } ;
3939
40+ fn decode_signed_le_i64 ( int_bytes : & [ u8 ] ) -> i64 {
41+ let size = int_bytes. len ( ) . min ( 8 ) ;
42+ let mut value: i64 = 0 ;
43+
44+ for ( index, byte) in int_bytes. iter ( ) . take ( size) . enumerate ( ) {
45+ value |= ( * byte as i64 ) << ( index * 8 ) ;
46+ }
47+
48+ if size > 0 && size < 8 && int_bytes[ size - 1 ] & 0x80 != 0 {
49+ value |= !0i64 << ( size * 8 ) ;
50+ }
51+
52+ value
53+ }
54+
4055impl Generator {
4156 /// clean up the stack to prepare for the STOP opcode.
4257 ///
@@ -47,7 +62,8 @@ impl Generator {
4762 /// - if stack is empty: push a None value
4863 /// - if stack has one MARK: pop it and push None
4964 /// - if stack has multiple items with MARK: combine items above MARK into tuple
50- /// - if stack has multiple items without MARK: combine top items into tuples
65+ /// - if stack has multiple items without MARK: use protocol-appropriate
66+ /// cleanup to reduce the stack to one item
5167 ///
5268 /// this method is called at the end of generation to ensure the pickle is
5369 /// valid before emitting the final STOP opcode.
@@ -62,15 +78,17 @@ impl Generator {
6278 self . emit_opcode ( Tuple ) ;
6379 }
6480
65- // at this point, stack has no MARKs, just regular items
66- // keep combining until we have exactly 1 item
67- // use TUPLE2/TUPLE3 which don't require MARKs
81+ // at this point, stack has no MARKs, just regular items.
82+ // protocol 2+ can use TUPLE2/TUPLE3, but protocol 0/1 must not emit
83+ // those shortcut opcodes during cleanup.
6884 let mut safety_counter = 0 ;
6985 while self . state . stack . len ( ) > 1 && safety_counter < 10000 {
7086 safety_counter += 1 ;
7187
7288 let stack_len = self . state . stack . len ( ) ;
73- if stack_len >= 3 {
89+ if self . state . version < Version :: V2 {
90+ self . emit_opcode ( Pop ) ;
91+ } else if stack_len >= 3 {
7492 self . emit_opcode ( Tuple3 ) ;
7593 } else if stack_len == 2 {
7694 self . emit_opcode ( Tuple2 ) ;
@@ -237,8 +255,8 @@ impl Generator {
237255 self . push ( StackObject :: Dict ( HashMap :: new ( ) ) ) ;
238256 }
239257 Dict => {
240- // allow interior mutability in hash keys - our Hash/Eq implementations
241- // are value-based and we never mutate objects used as dict keys
258+ // allow interior mutability in hash keys because StackObjectRef
259+ // hashes and compares by Rc identity, not the borrowed value
242260 #[ allow( clippy:: mutable_key_type) ]
243261 let mut accumulated = HashMap :: new ( ) ;
244262
@@ -324,8 +342,8 @@ impl Generator {
324342 }
325343 }
326344 FrozenSet => {
327- // allow interior mutability in hash keys - our Hash/Eq implementations
328- // are value-based and we never mutate objects used as set members
345+ // allow interior mutability in hash keys because StackObjectRef
346+ // hashes and compares by Rc identity, not the borrowed value
329347 #[ allow( clippy:: mutable_key_type) ]
330348 let mut accumulated = HashSet :: new ( ) ;
331349
@@ -353,10 +371,9 @@ impl Generator {
353371 } ;
354372 // in protocol 0-1, INT opcode with 00/01 represents booleans
355373 // protocol 2+ has dedicated NEWTRUE/NEWFALSE opcodes
356- if matches ! ( self . state. version, Version :: V0 | Version :: V1 )
357- && ( value == 0 || value == 1 )
358- {
359- self . push ( StackObject :: Bool ( value == 1 ) ) ;
374+ let is_bool_literal = matches ! ( arg_bytes, Some ( b"00\n " | b"01\n " ) ) ;
375+ if matches ! ( self . state. version, Version :: V0 | Version :: V1 ) && is_bool_literal {
376+ self . push ( StackObject :: Bool ( value != 0 ) ) ;
360377 } else {
361378 self . push ( StackObject :: Int ( value) ) ;
362379 }
@@ -416,14 +433,7 @@ impl Generator {
416433 let size = arg_bytes[ 0 ] as usize ;
417434 if arg_bytes. len ( ) > size {
418435 let int_bytes = & arg_bytes[ 1 ..1 + size] ;
419- // interpret as little-endian integer without static size
420- // limit to i64 size (8 bytes) to prevent overflow
421- let mut value: i64 = 0 ;
422- for ( i, & b) in int_bytes. iter ( ) . enumerate ( ) . take ( 8 ) {
423- // safe: i < 8, so i*8 < 64, shift is always valid
424- value |= ( b as i64 ) << ( i * 8 ) ;
425- }
426- self . push ( StackObject :: Int ( value) ) ;
436+ self . push ( StackObject :: Int ( decode_signed_le_i64 ( int_bytes) ) ) ;
427437 }
428438 }
429439 }
@@ -438,14 +448,7 @@ impl Generator {
438448 ] ) as usize ;
439449 if arg_bytes. len ( ) >= 4 + size {
440450 let int_bytes = & arg_bytes[ 4 ..4 + size] ;
441- // interpret as little-endian integer without static size
442- // limit to i64 size (8 bytes) to prevent overflow
443- let mut value: i64 = 0 ;
444- for ( i, & b) in int_bytes. iter ( ) . enumerate ( ) . take ( 8 ) {
445- // safe: i < 8, so i*8 < 64, shift is always valid
446- value |= ( b as i64 ) << ( i * 8 ) ;
447- }
448- self . push ( StackObject :: Int ( value) ) ;
451+ self . push ( StackObject :: Int ( decode_signed_le_i64 ( int_bytes) ) ) ;
449452 }
450453 }
451454 }
@@ -578,12 +581,16 @@ impl Generator {
578581 return ;
579582 }
580583
581- // pops state and instance, updates instance's args
582- if let ( Some ( state) , Some ( instance_ref) ) = ( self . pop ( ) , self . pop ( ) ) {
583- if let StackObject :: Instance ( ref mut inst) = * instance_ref. borrow_mut ( ) {
584+ // BUILD pops the state but mutates the existing instance in place.
585+ if let Some ( state) = self . pop ( ) {
586+ let Some ( instance_ref) = self . peek ( ) . cloned ( ) else {
587+ return ;
588+ } ;
589+
590+ let mut instance = instance_ref. borrow_mut ( ) ;
591+ if let StackObject :: Instance ( ref mut inst) = * instance {
584592 inst. args = state;
585593 }
586- self . push ( instance_ref. borrow ( ) . clone ( ) ) ;
587594 }
588595 }
589596 Inst => {
@@ -709,8 +716,7 @@ impl Generator {
709716 if let Ok ( index_str) = std:: str:: from_utf8 ( arg_bytes) {
710717 if let Ok ( index) = index_str. trim ( ) . parse ( ) {
711718 if let Some ( obj) = self . get ( index) {
712- let cloned = obj. borrow ( ) . clone ( ) ;
713- self . push ( cloned) ;
719+ self . push_ref ( obj) ;
714720 }
715721 }
716722 }
@@ -720,8 +726,7 @@ impl Generator {
720726 if let Some ( arg_bytes) = arg_bytes {
721727 let index = arg_bytes[ 0 ] as usize ;
722728 if let Some ( obj) = self . get ( index) {
723- let cloned = obj. borrow ( ) . clone ( ) ;
724- self . push ( cloned) ;
729+ self . push_ref ( obj) ;
725730 }
726731 }
727732 }
@@ -734,8 +739,7 @@ impl Generator {
734739 arg_bytes[ 3 ] ,
735740 ] ) as usize ;
736741 if let Some ( obj) = self . get ( index) {
737- let cloned = obj. borrow ( ) . clone ( ) ;
738- self . push ( cloned) ;
742+ self . push_ref ( obj) ;
739743 }
740744 }
741745 }
@@ -745,9 +749,8 @@ impl Generator {
745749 if let Ok ( index_str) = std:: str:: from_utf8 ( arg_bytes) {
746750 if let Ok ( index) = index_str. trim ( ) . parse ( ) {
747751 if let Some ( top) = self . peek ( ) {
748- let obj = top. borrow ( ) . clone ( ) ;
749- if !matches ! ( obj, StackObject :: Mark ) {
750- self . put ( index, obj) ;
752+ if !matches ! ( * top. borrow( ) , StackObject :: Mark ) {
753+ self . put ( index, top. clone ( ) ) ;
751754 }
752755 }
753756 }
@@ -759,9 +762,8 @@ impl Generator {
759762 if let Some ( arg_bytes) = arg_bytes {
760763 let index = arg_bytes[ 0 ] as usize ;
761764 if let Some ( top) = self . peek ( ) {
762- let obj = top. borrow ( ) . clone ( ) ;
763- if !matches ! ( obj, StackObject :: Mark ) {
764- self . put ( index, obj) ;
765+ if !matches ! ( * top. borrow( ) , StackObject :: Mark ) {
766+ self . put ( index, top. clone ( ) ) ;
765767 }
766768 }
767769 }
@@ -776,17 +778,15 @@ impl Generator {
776778 arg_bytes[ 3 ] ,
777779 ] ) as usize ;
778780 if let Some ( top) = self . peek ( ) {
779- let obj = top. borrow ( ) . clone ( ) ;
780- if !matches ! ( obj, StackObject :: Mark ) {
781- self . put ( index, obj) ;
781+ if !matches ! ( * top. borrow( ) , StackObject :: Mark ) {
782+ self . put ( index, top. clone ( ) ) ;
782783 }
783784 }
784785 }
785786 }
786787 Memoize => {
787- if let Some ( top) = self . pop ( ) {
788- self . put ( self . state . memo . len ( ) , top. borrow ( ) . clone ( ) ) ;
789- self . push ( top. borrow ( ) . clone ( ) )
788+ if let Some ( top) = self . peek ( ) . cloned ( ) {
789+ self . put ( self . state . memo . len ( ) , top) ;
790790 }
791791 }
792792 Ext1 | Ext2 | Ext4 => {
@@ -804,7 +804,17 @@ impl Generator {
804804 // NEXT_BUFFER pushes a buffer object to the stack
805805 self . push ( StackObject :: Bytes ( Vec :: new ( ) ) ) ; // Use empty bytes as placeholder
806806 }
807- Proto | ReadOnlyBuffer | Stop | Frame => {
807+ ReadOnlyBuffer => {
808+ if let Some ( buffer) = self . pop ( ) {
809+ let readonly_buffer = match & * buffer. borrow ( ) {
810+ StackObject :: Bytes ( bytes) => StackObject :: Bytes ( bytes. clone ( ) ) ,
811+ StackObject :: ByteArray ( bytes) => StackObject :: Bytes ( bytes. clone ( ) ) ,
812+ other => other. clone ( ) ,
813+ } ;
814+ self . push ( readonly_buffer) ;
815+ }
816+ }
817+ Proto | Stop | Frame => {
808818 // these opcodes don't manipulate the stack, but we're being
809819 // explicit about it so that we know we've covered all opcodes
810820 }
@@ -817,3 +827,148 @@ impl Generator {
817827 // pos, format!("{:?}", opcode), before, after, delta);
818828 }
819829}
830+
831+ #[ cfg( test) ]
832+ mod tests {
833+ use super :: * ;
834+ use crate :: opcodes:: OpcodeKind ;
835+ use crate :: { Generator , Version } ;
836+ use std:: rc:: Rc ;
837+
838+ #[ test]
839+ fn cleanup_for_stop_uses_pop_for_protocol_0 ( ) {
840+ let mut generator = Generator :: new ( Version :: V0 ) ;
841+ generator. push ( StackObject :: Int ( 1 ) ) ;
842+ generator. push ( StackObject :: Int ( 2 ) ) ;
843+ generator. push ( StackObject :: Int ( 3 ) ) ;
844+
845+ generator. cleanup_for_stop ( ) ;
846+
847+ assert_eq ! ( generator. state. stack. len( ) , 1 ) ;
848+ assert ! ( generator. output. contains( & OpcodeKind :: Pop . as_u8( ) ) ) ;
849+ assert ! ( !generator. output. contains( & OpcodeKind :: Tuple2 . as_u8( ) ) ) ;
850+ assert ! ( !generator. output. contains( & OpcodeKind :: Tuple3 . as_u8( ) ) ) ;
851+ }
852+
853+ #[ test]
854+ fn cleanup_for_stop_keeps_tuple_shortcuts_for_protocol_2 ( ) {
855+ let mut generator = Generator :: new ( Version :: V2 ) ;
856+ generator. push ( StackObject :: Int ( 1 ) ) ;
857+ generator. push ( StackObject :: Int ( 2 ) ) ;
858+ generator. push ( StackObject :: Int ( 3 ) ) ;
859+
860+ generator. cleanup_for_stop ( ) ;
861+
862+ assert_eq ! ( generator. state. stack. len( ) , 1 ) ;
863+ assert ! ( generator. output. contains( & OpcodeKind :: Tuple3 . as_u8( ) ) ) ;
864+ }
865+
866+ #[ test]
867+ fn int_opcode_only_treats_00_and_01_as_bools ( ) {
868+ let mut generator = Generator :: new ( Version :: V0 ) ;
869+ generator. process_stack_ops ( OpcodeKind :: Int , Some ( b"1\n " ) ) ;
870+ assert ! ( matches!(
871+ * generator. peek( ) . unwrap( ) . borrow( ) ,
872+ StackObject :: Int ( 1 )
873+ ) ) ;
874+
875+ generator. reset ( ) ;
876+ generator. process_stack_ops ( OpcodeKind :: Int , Some ( b"01\n " ) ) ;
877+ assert ! ( matches!(
878+ * generator. peek( ) . unwrap( ) . borrow( ) ,
879+ StackObject :: Bool ( true )
880+ ) ) ;
881+
882+ generator. reset ( ) ;
883+ generator. process_stack_ops ( OpcodeKind :: Int , Some ( b"00\n " ) ) ;
884+ assert ! ( matches!(
885+ * generator. peek( ) . unwrap( ) . borrow( ) ,
886+ StackObject :: Bool ( false )
887+ ) ) ;
888+ }
889+
890+ #[ test]
891+ fn long_opcodes_sign_extend_short_negative_values ( ) {
892+ let mut generator = Generator :: new ( Version :: V4 ) ;
893+ generator. process_stack_ops ( OpcodeKind :: Long1 , Some ( & [ 1 , 0xff ] ) ) ;
894+ assert ! ( matches!(
895+ * generator. peek( ) . unwrap( ) . borrow( ) ,
896+ StackObject :: Int ( -1 )
897+ ) ) ;
898+
899+ generator. reset ( ) ;
900+ generator. process_stack_ops ( OpcodeKind :: Long4 , Some ( & [ 1 , 0 , 0 , 0 , 0x80 ] ) ) ;
901+ assert ! ( matches!(
902+ * generator. peek( ) . unwrap( ) . borrow( ) ,
903+ StackObject :: Int ( -128 )
904+ ) ) ;
905+ }
906+
907+ #[ test]
908+ fn get_put_and_memoize_preserve_aliasing ( ) {
909+ let mut generator = Generator :: new ( Version :: V4 ) ;
910+ let shared = StackObjectRef :: new ( StackObject :: List ( Vec :: new ( ) ) ) ;
911+ generator. push_ref ( shared. clone ( ) ) ;
912+
913+ generator. process_stack_ops ( OpcodeKind :: BinPut , Some ( & [ 0 ] ) ) ;
914+ generator. process_stack_ops ( OpcodeKind :: BinGet , Some ( & [ 0 ] ) ) ;
915+
916+ let top = generator. peek ( ) . unwrap ( ) . clone ( ) ;
917+ let below = generator. peek_at ( 1 ) . unwrap ( ) . clone ( ) ;
918+ let memoized = generator. get ( 0 ) . unwrap ( ) ;
919+ assert ! ( Rc :: ptr_eq( & top. 0 , & shared. 0 ) ) ;
920+ assert ! ( Rc :: ptr_eq( & below. 0 , & shared. 0 ) ) ;
921+ assert ! ( Rc :: ptr_eq( & memoized. 0 , & shared. 0 ) ) ;
922+
923+ generator. reset ( ) ;
924+ generator. push_ref ( shared. clone ( ) ) ;
925+ generator. process_stack_ops ( OpcodeKind :: Memoize , None ) ;
926+ let memoized = generator. get ( 0 ) . unwrap ( ) ;
927+ let top = generator. peek ( ) . unwrap ( ) . clone ( ) ;
928+ assert_eq ! ( generator. state. stack. len( ) , 1 ) ;
929+ assert ! ( Rc :: ptr_eq( & memoized. 0 , & shared. 0 ) ) ;
930+ assert ! ( Rc :: ptr_eq( & top. 0 , & shared. 0 ) ) ;
931+ }
932+
933+ #[ test]
934+ fn build_mutates_existing_instance_in_place ( ) {
935+ let mut generator = Generator :: new ( Version :: V4 ) ;
936+ let callable = StackObjectRef :: new ( StackObject :: Global {
937+ module : "builtins" . to_string ( ) ,
938+ name : "object" . to_string ( ) ,
939+ } ) ;
940+ let initial_args = StackObjectRef :: new ( StackObject :: Tuple ( Vec :: new ( ) ) ) ;
941+ let instance = StackObjectRef :: new ( StackObject :: Instance ( InstanceObject {
942+ callable,
943+ args : initial_args,
944+ } ) ) ;
945+ let state = StackObjectRef :: new ( StackObject :: Dict ( HashMap :: new ( ) ) ) ;
946+
947+ generator. push_ref ( instance. clone ( ) ) ;
948+ generator. push_ref ( state. clone ( ) ) ;
949+ generator. process_stack_ops ( OpcodeKind :: Build , None ) ;
950+
951+ assert_eq ! ( generator. state. stack. len( ) , 1 ) ;
952+ let top = generator. peek ( ) . unwrap ( ) . clone ( ) ;
953+ assert ! ( Rc :: ptr_eq( & top. 0 , & instance. 0 ) ) ;
954+
955+ let borrowed = top. borrow ( ) ;
956+ let StackObject :: Instance ( instance) = & * borrowed else {
957+ panic ! ( "expected instance on stack after BUILD" ) ;
958+ } ;
959+ assert ! ( Rc :: ptr_eq( & instance. args. 0 , & state. 0 ) ) ;
960+ }
961+
962+ #[ test]
963+ fn readonly_buffer_turns_bytearray_into_bytes ( ) {
964+ let mut generator = Generator :: new ( Version :: V5 ) ;
965+ generator. push ( StackObject :: ByteArray ( vec ! [ 1 , 2 , 3 ] ) ) ;
966+
967+ generator. process_stack_ops ( OpcodeKind :: ReadOnlyBuffer , None ) ;
968+
969+ assert ! ( matches!(
970+ & * generator. peek( ) . unwrap( ) . borrow( ) ,
971+ StackObject :: Bytes ( bytes) if bytes == & vec![ 1 , 2 , 3 ]
972+ ) ) ;
973+ }
974+ }
0 commit comments