@@ -5,6 +5,12 @@ use std::{
55
66use num:: PrimInt ;
77
8+ /// Capacity (in code units, excluding the null terminator) of the result buffer that the
9+ /// user-facing string builtins write into.
10+ /// Excess input is truncated on a character boundary instead.
11+ /// Keep this in sync with the `.st` declarations.
12+ const STRING_RESULT_LEN : usize = 2048 ;
13+
814/// # Helper function
915///
1016/// Gets the amount of continuous characters before
@@ -61,6 +67,18 @@ pub trait CharsEncoder<T: PrimInt>: Iterator {
6167 /// given address large enough. It will continue to write unchecked until all characters
6268 /// have been processed and can therefore result in UB.
6369 unsafe fn encode ( self , dest : & mut * mut T ) ;
70+
71+ /// Like [`CharsEncoder::encode`], but writes at most `max_elements` code units of
72+ /// content (bytes for UTF-8, `u16` code units for UTF-16), always followed by a
73+ /// null terminator. Truncation happens on a character boundary, so the written
74+ /// string stays valid. Returns the number of content code units written (excluding
75+ /// the terminator), allowing callers to keep a running budget across several inputs.
76+ ///
77+ /// # Safety
78+ ///
79+ /// Works on raw pointers, inherently unsafe. The destination buffer must have room
80+ /// for `max_elements + 1` code units.
81+ unsafe fn encode_bounded ( self , dest : & mut * mut T , max_elements : usize ) -> usize ;
6482}
6583
6684#[ derive( Debug ) ]
@@ -86,31 +104,60 @@ impl<'a> CharsDecoder<u8> for EncodedCharsIter<Utf8Iterator<'a>> {
86104
87105impl < I : Iterator < Item = char > > CharsEncoder < u8 > for I {
88106 unsafe fn encode ( self , dest : & mut * mut u8 ) {
107+ self . encode_bounded ( dest, usize:: MAX ) ;
108+ }
109+
110+ unsafe fn encode_bounded ( self , dest : & mut * mut u8 , max_elements : usize ) -> usize {
111+ let mut remaining = max_elements;
112+ let mut written = 0 ;
89113 for char in self {
114+ let len = char. len_utf8 ( ) ;
115+ // Stop before we would exceed the budget; never truncate mid-character.
116+ if len > remaining {
117+ break ;
118+ }
90119 let mut temp = [ 0 ; 4 ] ;
91120 let slice = char. encode_utf8 ( & mut temp) ;
92121 for byte in slice. as_bytes ( ) {
93122 * * dest = * byte;
94123 * dest = dest. add ( 1 ) ;
95124 }
125+ remaining -= len;
126+ written += len;
96127 }
97128
98129 * * dest = 0 ;
130+ written
99131 }
100132}
101133
102134impl < I : Iterator < Item = Result < char , DecodeUtf16Error > > > CharsEncoder < u16 > for I {
103135 unsafe fn encode ( self , dest : & mut * mut u16 ) {
136+ self . encode_bounded ( dest, usize:: MAX ) ;
137+ }
138+
139+ unsafe fn encode_bounded ( self , dest : & mut * mut u16 , max_elements : usize ) -> usize {
140+ let mut remaining = max_elements;
141+ let mut written = 0 ;
104142 for c in self {
143+ let c = c. unwrap ( ) ;
144+ let len = c. len_utf16 ( ) ;
145+ // Stop before we would exceed the budget; never truncate mid-character.
146+ if len > remaining {
147+ break ;
148+ }
105149 let mut temp = [ 0_u16 ; 2 ] ;
106- let slice = c. unwrap ( ) . encode_utf16 ( & mut temp) ;
150+ let slice = c. encode_utf16 ( & mut temp) ;
107151 for word in slice {
108152 * * dest = * word;
109153 * dest = dest. add ( 1 ) ;
110154 }
155+ remaining -= len;
156+ written += len;
111157 }
112158
113159 * * dest = 0 ;
160+ written
114161 }
115162}
116163
@@ -417,11 +464,13 @@ pub unsafe extern "C-unwind" fn INSERT_EXT__STRING(
417464 panic ! { "Positional parameter is out of bounds." }
418465 }
419466 let pos = pos as usize ;
467+ // Truncate at the result-buffer capacity so we never overflow the caller's
468+ // `STRING[2048]` / `WSTRING[2048]` destination (see `STRING_RESULT_LEN`).
420469 EncodedCharsIter :: decode ( src_base)
421470 . take ( pos)
422471 . chain ( EncodedCharsIter :: decode ( src_to_insert) )
423472 . chain ( EncodedCharsIter :: decode ( src_base) . skip ( pos) )
424- . encode ( & mut dest) ;
473+ . encode_bounded ( & mut dest, STRING_RESULT_LEN ) ;
425474
426475 0
427476}
@@ -450,11 +499,13 @@ pub unsafe extern "C-unwind" fn INSERT_EXT__WSTRING(
450499 panic ! { "Positional parameter is out of bounds." }
451500 }
452501 let pos = pos as usize ;
502+ // Truncate at the result-buffer capacity so we never overflow the caller's
503+ // `STRING[2048]` / `WSTRING[2048]` destination (see `STRING_RESULT_LEN`).
453504 EncodedCharsIter :: decode ( src_base)
454505 . take ( pos)
455506 . chain ( EncodedCharsIter :: decode ( src_to_insert) )
456507 . chain ( EncodedCharsIter :: decode ( src_base) . skip ( pos) )
457- . encode ( & mut dest) ;
508+ . encode_bounded ( & mut dest, STRING_RESULT_LEN ) ;
458509
459510 0
460511}
@@ -583,13 +634,15 @@ pub unsafe extern "C-unwind" fn REPLACE_EXT__STRING(
583634 pos + 1
584635 )
585636 }
637+ // Truncate at the result-buffer capacity so we never overflow the caller's
638+ // `STRING[2048]` / `WSTRING[2048]` destination (see `STRING_RESULT_LEN`).
586639 EncodedCharsIter :: decode ( src_base)
587640 . take ( pos)
588641 . chain (
589642 EncodedCharsIter :: decode ( src_replacement)
590643 . chain ( EncodedCharsIter :: decode ( src_base) . skip ( pos + nreplace) ) ,
591644 )
592- . encode ( & mut dest) ;
645+ . encode_bounded ( & mut dest, STRING_RESULT_LEN ) ;
593646
594647 0
595648}
@@ -630,13 +683,15 @@ pub unsafe extern "C-unwind" fn REPLACE_EXT__WSTRING(
630683 pos + 1
631684 )
632685 }
686+ // Truncate at the result-buffer capacity so we never overflow the caller's
687+ // `STRING[2048]` / `WSTRING[2048]` destination (see `STRING_RESULT_LEN`).
633688 EncodedCharsIter :: decode ( src_base)
634689 . take ( pos)
635690 . chain (
636691 EncodedCharsIter :: decode ( src_replacement)
637692 . chain ( EncodedCharsIter :: decode ( src_base) . skip ( pos + nreplace) ) ,
638693 )
639- . encode ( & mut dest) ;
694+ . encode_bounded ( & mut dest, STRING_RESULT_LEN ) ;
640695
641696 0
642697}
@@ -677,8 +732,12 @@ pub unsafe extern "C-unwind" fn CONCAT_EXT__STRING(dest: *mut u8, argc: i32, arg
677732 }
678733 let mut dest = dest;
679734 let mut argv = argv;
735+ // Truncate at the result-buffer capacity so we never overflow the caller's
736+ // `STRING[2048]` destination (see `STRING_RESULT_LEN`).
737+ let mut remaining = STRING_RESULT_LEN ;
680738 for _ in 0 ..argc {
681- EncodedCharsIter :: decode ( * argv) . encode ( & mut dest) ;
739+ let written = EncodedCharsIter :: decode ( * argv) . encode_bounded ( & mut dest, remaining) ;
740+ remaining -= written;
682741 argv = argv. add ( 1 ) ;
683742 }
684743
@@ -725,8 +784,12 @@ pub unsafe extern "C-unwind" fn CONCAT_EXT__WSTRING(
725784 }
726785 let mut dest = dest;
727786 let mut argv = argv;
787+ // Truncate at the result-buffer capacity so we never overflow the caller's
788+ // `WSTRING[2048]` destination (see `STRING_RESULT_LEN`).
789+ let mut remaining = STRING_RESULT_LEN ;
728790 for _ in 0 ..argc {
729- EncodedCharsIter :: decode ( * argv) . encode ( & mut dest) ;
791+ let written = EncodedCharsIter :: decode ( * argv) . encode_bounded ( & mut dest, remaining) ;
792+ remaining -= written;
730793 argv = argv. add ( 1 ) ;
731794 }
732795
0 commit comments