@@ -567,12 +567,12 @@ impl<S: BuildHasher> DroplessInternSet<S> {
567567/// A buffer for strings.
568568#[ derive( Debug , Default ) ]
569569struct StringBuffer {
570- chunks : Vec < Box < [ MaybeUninit < u8 > ] > > ,
570+ chunks : Vec < Vec < MaybeUninit < u8 > > > ,
571571 last_chunk_start : usize ,
572572}
573573
574574const INIT_CHUNK_SIZE : usize = 1 << 5 ;
575- const GLOW_MAX_CHUNK_SIZE : usize = 1 << 12 ;
575+ const GROW_MAX_CHUNK_SIZE : usize = 1 << 12 ;
576576
577577impl StringBuffer {
578578 fn speculative_alloc ( & mut self , upper_size : usize ) -> StringWriteBuffer < ' _ > {
@@ -583,21 +583,21 @@ impl StringBuffer {
583583 }
584584 Some ( last_chunk) if last_chunk. len ( ) - self . last_chunk_start < upper_size => {
585585 let chunk_size = ( last_chunk. len ( ) * 2 )
586- . min ( GLOW_MAX_CHUNK_SIZE )
586+ . min ( GROW_MAX_CHUNK_SIZE )
587587 . max ( upper_size. next_power_of_two ( ) ) ;
588588 self . append_new_chunk ( chunk_size) ;
589589 }
590590 _ => { }
591591 }
592592
593593 // Safety: We added the last chunk above.
594- let last_chunk = unsafe { self . chunks . last_mut ( ) . unwrap_unchecked ( ) } ;
595-
596- let start = self . last_chunk_start ;
597- let end = self . last_chunk_start + upper_size;
598- let buf = & mut last_chunk[ start..end] ;
594+ let buf = unsafe {
595+ let last_chunk = self . chunks . last_mut ( ) . unwrap_unchecked ( ) ;
596+ last_chunk. as_mut_ptr ( ) . add ( self . last_chunk_start )
597+ } ;
599598 StringWriteBuffer {
600599 buf,
600+ buf_cap : upper_size,
601601 last_chuck_start : & mut self . last_chunk_start ,
602602 written : 0 ,
603603 }
@@ -610,49 +610,50 @@ impl StringBuffer {
610610 // Safety: We reserved enough amount of memory, also it can contain uninitialized data.
611611 unsafe { chunk. set_len ( chunk_size) } ;
612612
613- self . chunks . push ( chunk. into_boxed_slice ( ) ) ;
613+ self . chunks . push ( chunk) ;
614614 self . last_chunk_start = 0 ;
615615 }
616616}
617617
618618struct StringWriteBuffer < ' a > {
619- buf : & ' a mut [ MaybeUninit < u8 > ] ,
619+ buf : * mut MaybeUninit < u8 > ,
620+ buf_cap : usize ,
620621 last_chuck_start : & ' a mut usize ,
621622 written : usize ,
622623}
623624
624625impl < ' a > StringWriteBuffer < ' a > {
625626 #[ inline]
626627 fn as_bytes ( & self ) -> & [ u8 ] {
627- let ptr = self . buf . as_ptr ( ) . cast :: < u8 > ( ) ;
628- unsafe { slice:: from_raw_parts ( ptr , self . written ) }
628+ // Safety: bytes 0..written were initialized by write_str.
629+ unsafe { slice:: from_raw_parts ( self . buf . cast :: < u8 > ( ) , self . written ) }
629630 }
630631
631632 #[ inline]
632633 fn commit ( self ) -> & ' a [ u8 ] {
633634 * self . last_chuck_start += self . written ;
634635
635- let ptr = self . buf . as_ptr ( ) . cast :: < u8 > ( ) ;
636- unsafe { slice:: from_raw_parts ( ptr, self . written ) }
636+ // Safety: bytes 0..written were initialized by write_str.
637+ // The lifetime 'a is valid because buf points into a Vec owned by the StringBuffer that is
638+ // mutably borrowed for 'a (evidenced by last_chuck_start: &'a mut usize).
639+ unsafe { slice:: from_raw_parts ( self . buf . cast :: < u8 > ( ) , self . written ) }
637640 }
638641}
639642
640643impl Write for StringWriteBuffer < ' _ > {
641644 fn write_str ( & mut self , s : & str ) -> std:: fmt:: Result {
642645 let size = s. len ( ) ;
643646
644- if self . buf . len ( ) - self . written >= size {
647+ if self . buf_cap - self . written >= size {
645648 let src = s. as_ptr ( ) ;
646649
647- // Safety: `written` cannot be over the buf size by the if condition
648- let buf = unsafe { self . buf . get_unchecked_mut ( self . written ..) } ;
649- let dst = buf. as_mut_ptr ( ) . cast :: < u8 > ( ) ;
650-
651650 // Safety
652651 // * `src` is valid for reading of `size` bytes
653- // * `dst` is valid for reading of `size` bytes
654- // * Two pointers are well aligned. Both alignments are 1
655- // * `src` and `dst` are not overlapping
652+ // * `dst` is valid for writing of `size` bytes
653+ // (`written + size <= buf_cap <= chunk len`)
654+ // * Both alignments are 1
655+ // * `src` and `dst` are not overlapping (src is a string literal / caller data)
656+ let dst = unsafe { self . buf . add ( self . written ) . cast :: < u8 > ( ) } ;
656657 unsafe { ptr:: copy_nonoverlapping ( src, dst, size) } ;
657658
658659 self . written += size;
@@ -694,6 +695,7 @@ mod tests {
694695 test_dropless_interner_many ( ) ;
695696 test_dropless_interner_alignment_handling ( ) ;
696697 test_dropless_interner_complex_display_type ( ) ;
698+ test_dropless_interner_consecutive_formatted_strs ( ) ;
697699 }
698700
699701 fn test_dropless_interner_int ( ) {
@@ -914,6 +916,20 @@ mod tests {
914916 assert_eq ! ( & * interned, s. as_str( ) ) ;
915917 }
916918
919+ // UB if the interner gets a mutable reference to a whole chunk while a reference(i0) is alive.
920+ fn test_dropless_interner_consecutive_formatted_strs ( ) {
921+ let dropless = DroplessInterner :: new ( ) ;
922+
923+ let value = 0 ;
924+ let i1 = dropless. intern_formatted_str ( & value, 1 ) . unwrap ( ) ;
925+
926+ let value = 1 ;
927+ let i2 = dropless. intern_formatted_str ( & value, 1 ) . unwrap ( ) ;
928+
929+ // Prevents i1 and i2 from being optimized away.
930+ let _c = format ! ( "{i1}{i2}" ) ;
931+ }
932+
917933 #[ test]
918934 fn test_string_buffer ( ) {
919935 test_string_buffer_chunk ( ) ;
@@ -946,7 +962,7 @@ mod tests {
946962 assert_eq ! ( buf. last_chunk_start, 1 ) ;
947963
948964 // Forces to make another chunk
949- let mut write_buf = buf. speculative_alloc ( GLOW_MAX_CHUNK_SIZE ) ;
965+ let mut write_buf = buf. speculative_alloc ( GROW_MAX_CHUNK_SIZE ) ;
950966 write_buf. write_str ( "aa" ) . unwrap ( ) ;
951967 assert_eq ! ( write_buf. as_bytes( ) , b"aa" ) ;
952968 write_buf. commit ( ) ;
@@ -986,7 +1002,7 @@ mod tests {
9861002 fn test_string_buffer_long_string ( ) {
9871003 let mut buf = StringBuffer :: default ( ) ;
9881004
989- let s = "a" . repeat ( GLOW_MAX_CHUNK_SIZE * 10 ) ;
1005+ let s = "a" . repeat ( GROW_MAX_CHUNK_SIZE * 10 ) ;
9901006 let mut write_buf = buf. speculative_alloc ( s. len ( ) ) ;
9911007 write_buf. write_str ( & s) . unwrap ( ) ;
9921008 assert_eq ! ( write_buf. as_bytes( ) , s. as_bytes( ) ) ;
0 commit comments