11use std:: borrow:: Cow ;
2+ #[ cfg( feature = "concurrent" ) ]
3+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
24
5+ use memchr:: memchr_iter;
36#[ cfg( feature = "concurrent" ) ]
47use rayon:: prelude:: * ;
58
6- use crate :: error:: { Error , Result } ;
9+ use crate :: error:: Result ;
710use crate :: JSONSourceMap ;
811/// Port from https://github.com/getsentry/rust-sourcemap/blob/master/src/encoder.rs
912/// It is a helper for encode `SourceMap` to vlq sourcemap string, but here some different.
@@ -29,9 +32,10 @@ pub fn encode(sourcemap: &SourceMap) -> JSONSourceMap {
2932// It will escape the string to avoid invalid JSON string.
3033pub fn encode_to_string ( sourcemap : & SourceMap ) -> Result < String > {
3134 let mut contents = PreAllocatedString :: new (
32- 10 + sourcemap. names . len ( ) * 2
35+ 9 + sourcemap. names . len ( ) * 2
3336 + sourcemap. sources . len ( ) * 2
34- + if let Some ( x) = & sourcemap. x_google_ignore_list { x. len ( ) * 2 } else { 0 } ,
37+ + if let Some ( x) = & sourcemap. x_google_ignore_list { x. len ( ) * 2 } else { 0 }
38+ + if let Some ( s) = & sourcemap. source_contents { s. len ( ) * 3 } else { 0 } ,
3539 ) ;
3640 contents. push ( "{\" version\" :3," . into ( ) ) ;
3741 if let Some ( file) = sourcemap. get_file ( ) {
@@ -45,43 +49,77 @@ pub fn encode_to_string(sourcemap: &SourceMap) -> Result<String> {
4549 contents. push ( "\" ," . into ( ) ) ;
4650 }
4751 contents. push ( "\" names\" :[" . into ( ) ) ;
48- for n in & sourcemap. names {
49- contents. push ( serde_json:: to_string ( n. as_ref ( ) ) ?. into ( ) ) ;
50- contents. push ( "," . into ( ) ) ;
51- }
5252 if !sourcemap. names . is_empty ( ) {
53+ for n in & sourcemap. names {
54+ if needs_escaping ( n) {
55+ contents. push ( serde_json:: to_string ( n. as_ref ( ) ) ?. into ( ) ) ;
56+ } else {
57+ contents. push ( "\" " . into ( ) ) ;
58+ contents. push ( ( & * * n) . into ( ) ) ;
59+ contents. push ( "\" " . into ( ) ) ;
60+ }
61+ contents. push ( "," . into ( ) ) ;
62+ }
5363 // Remove the last `,`.
5464 contents. pop ( ) ;
5565 }
5666 contents. push ( Cow :: Borrowed ( "],\" sources\" :[" ) ) ;
57- for s in & sourcemap. sources {
58- contents. push ( serde_json:: to_string ( s. as_ref ( ) ) ?. into ( ) ) ;
59- contents. push ( "," . into ( ) ) ;
60- }
6167 if !sourcemap. sources . is_empty ( ) {
68+ for s in & sourcemap. sources {
69+ if needs_escaping ( s) {
70+ contents. push ( serde_escape_content ( s) ?. into ( ) ) ;
71+ } else {
72+ contents. push ( "\" " . into ( ) ) ;
73+ contents. push ( ( & * * s) . into ( ) ) ;
74+ contents. push ( "\" " . into ( ) ) ;
75+ }
76+ contents. push ( "," . into ( ) ) ;
77+ }
6278 // Remove the last `,`.
6379 contents. pop ( ) ;
6480 }
6581 // Quote `source_content` at parallel.
6682 if let Some ( source_contents) = & sourcemap. source_contents {
6783 contents. push ( "],\" sourcesContent\" :[" . into ( ) ) ;
68- cfg_if:: cfg_if! {
69- if #[ cfg( feature = "concurrent" ) ] {
70- let quote_source_contents = source_contents
84+ if !source_contents. is_empty ( ) {
85+ #[ cfg( feature = "concurrent" ) ]
86+ {
87+ let content_len = AtomicUsize :: new ( 0 ) ;
88+ let mut quote_source_contents = source_contents
7189 . par_iter ( )
72- . map( |x| serde_json:: to_string( x. as_ref( ) ) )
73- . collect:: <std:: result:: Result <Vec <_>, serde_json:: Error >>( )
74- . map_err( Error :: from) ?;
75- } else {
76- let quote_source_contents = source_contents
77- . iter( )
78- . map( |x| serde_json:: to_string( x. as_ref( ) ) )
79- . collect:: <std:: result:: Result <Vec <_>, serde_json:: Error >>( )
80- . map_err( Error :: from) ?;
90+ . filter_map ( |s| {
91+ if needs_escaping ( s) {
92+ let escaped = serde_escape_content ( s) . ok ( ) ?;
93+ content_len. fetch_add ( escaped. len ( ) , Ordering :: Relaxed ) ;
94+ Some ( [ "" . into ( ) , escaped. into ( ) , "" . into ( ) , "," . into ( ) ] )
95+ } else {
96+ content_len. fetch_add ( s. len ( ) + 3 , Ordering :: Relaxed ) ;
97+ Some ( [ "\" " . into ( ) , Cow :: Borrowed ( & * * s) , "\" " . into ( ) , "," . into ( ) ] )
98+ }
99+ } )
100+ . flatten ( )
101+ . collect :: < Vec < _ > > ( ) ;
102+ // Remove the last `,`.
103+ quote_source_contents. pop ( ) ;
104+ contents. extend ( quote_source_contents, content_len. load ( Ordering :: Relaxed ) ) ;
81105 }
82- } ;
83-
84- contents. push ( quote_source_contents. join ( "," ) . into ( ) ) ;
106+ #[ cfg( not( feature = "concurrent" ) ) ]
107+ {
108+ for s in source_contents {
109+ if needs_escaping ( & * * s) {
110+ let escaped = serde_escape_content ( s) ?;
111+ contents. push ( escaped. into ( ) ) ;
112+ } else {
113+ contents. push ( "\" " . into ( ) ) ;
114+ contents. push ( ( & * * s) . into ( ) ) ;
115+ contents. push ( "\" " . into ( ) ) ;
116+ }
117+ contents. push ( "," . into ( ) ) ;
118+ }
119+ // Remove the last `,`.
120+ contents. pop ( ) ;
121+ }
122+ }
85123 }
86124 if let Some ( x_google_ignore_list) = & sourcemap. x_google_ignore_list {
87125 contents. push ( "],\" x_google_ignoreList\" :[" . into ( ) ) ;
@@ -230,6 +268,16 @@ impl<'a> PreAllocatedString<'a> {
230268 }
231269 }
232270
271+ #[ cfg( feature = "concurrent" ) ]
272+ #[ inline]
273+ fn extend < I > ( & mut self , iter : I , extended : usize )
274+ where
275+ I : IntoIterator < Item = Cow < ' a , str > > ,
276+ {
277+ self . buf . extend ( iter) ;
278+ self . len += extended;
279+ }
280+
233281 #[ inline]
234282 fn consume ( self ) -> String {
235283 let mut buf = String :: with_capacity ( self . len ) ;
@@ -238,6 +286,21 @@ impl<'a> PreAllocatedString<'a> {
238286 }
239287}
240288
289+ #[ inline]
290+ fn needs_escaping ( s : & str ) -> bool {
291+ let bytes = s. as_bytes ( ) ;
292+
293+ // Check for control characters (0x00-0x1F)
294+ if memchr_iter ( b'\x00' , bytes) . next ( ) . is_some ( ) {
295+ return true ;
296+ }
297+
298+ // Check for ", \, and /
299+ memchr_iter ( b'"' , bytes) . next ( ) . is_some ( )
300+ || memchr_iter ( b'\\' , bytes) . next ( ) . is_some ( )
301+ || memchr_iter ( b'/' , bytes) . next ( ) . is_some ( )
302+ }
303+
241304#[ test]
242305fn test_encode ( ) {
243306 let input = r#"{
@@ -255,6 +318,14 @@ fn test_encode() {
255318 }
256319}
257320
321+ #[ inline]
322+ fn serde_escape_content < S : AsRef < str > > ( s : S ) -> Result < String > {
323+ let mut escaped_buf = Vec :: with_capacity ( s. as_ref ( ) . len ( ) + 2 ) ;
324+ serde:: Serialize :: serialize ( s. as_ref ( ) , & mut serde_json:: Serializer :: new ( & mut escaped_buf) ) ?;
325+ // Safety: `escaped_buf` is valid utf8.
326+ Ok ( unsafe { String :: from_utf8_unchecked ( escaped_buf) } )
327+ }
328+
258329#[ test]
259330fn test_encode_escape_string ( ) {
260331 // '\0' should be escaped.
0 commit comments