From 28fae2e80acb2fed9bb62b91a5ae0dcb68cae698 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 3 Apr 2024 12:14:45 +0800 Subject: [PATCH] fix(sourcemap): using serde_json::to_string to quote sourcemap string (#2889) --- crates/oxc_codegen/examples/sourcemap.rs | 2 +- .../src/concat_sourcemap_builder.rs | 2 +- crates/oxc_sourcemap/src/encode.rs | 51 ++++++++++++++++--- crates/oxc_sourcemap/src/sourcemap.rs | 18 +++++-- crates/oxc_sourcemap/src/sourcemap_builder.rs | 2 +- tasks/benchmark/benches/sourcemap.rs | 2 +- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/crates/oxc_codegen/examples/sourcemap.rs b/crates/oxc_codegen/examples/sourcemap.rs index d056ff822..bb1d50329 100644 --- a/crates/oxc_codegen/examples/sourcemap.rs +++ b/crates/oxc_codegen/examples/sourcemap.rs @@ -33,7 +33,7 @@ fn main() -> std::io::Result<()> { .build(&ret.program); if let Some(source_map) = source_map { - let result = source_map.to_json_string(); + let result = source_map.to_json_string().unwrap(); let hash = BASE64_STANDARD.encode(format!( "{}\0{}{}\0{}", source_text.len(), diff --git a/crates/oxc_sourcemap/src/concat_sourcemap_builder.rs b/crates/oxc_sourcemap/src/concat_sourcemap_builder.rs index 3ae042c78..520cadfe4 100644 --- a/crates/oxc_sourcemap/src/concat_sourcemap_builder.rs +++ b/crates/oxc_sourcemap/src/concat_sourcemap_builder.rs @@ -130,5 +130,5 @@ fn test_concat_sourcemap_builder() { ]) ); - assert_eq!(sm.to_json_string(), sm.to_json_string()); + assert_eq!(sm.to_json_string().unwrap(), sm.to_json_string().unwrap()); } diff --git a/crates/oxc_sourcemap/src/encode.rs b/crates/oxc_sourcemap/src/encode.rs index 920a11d13..201bbd3b5 100644 --- a/crates/oxc_sourcemap/src/encode.rs +++ b/crates/oxc_sourcemap/src/encode.rs @@ -1,3 +1,4 @@ +use crate::error::{Error, Result}; /// Port from https://github.com/getsentry/rust-sourcemap/blob/master/src/encoder.rs /// It is a helper for encode `SourceMap` to vlq sourcemap string, but here some different. /// - Quote `source_content` at parallel. @@ -5,7 +6,9 @@ use crate::{token::TokenChunk, SourceMap, Token}; use rayon::prelude::*; -pub fn encode(sourcemap: &SourceMap) -> String { +// Here using `serde_json::to_string` to serialization `names/source_contents/sources`. +// It will escape the string to avoid invalid JSON string. +pub fn encode(sourcemap: &SourceMap) -> Result { let mut buf = String::new(); buf.push_str("{\"version\":3,"); if let Some(file) = sourcemap.get_file() { @@ -14,20 +17,35 @@ pub fn encode(sourcemap: &SourceMap) -> String { buf.push_str("\","); } buf.push_str("\"names\":["); - buf.push_str(&sourcemap.names.iter().map(|x| format!("{x:?}")).collect::>().join(",")); + let names = sourcemap + .names + .iter() + .map(|x| serde_json::to_string(x.as_ref())) + .collect::, serde_json::Error>>() + .map_err(Error::from)?; + buf.push_str(&names.join(",")); buf.push_str("],\"sources\":["); - buf.push_str(&sourcemap.sources.iter().map(|x| format!("{x:?}")).collect::>().join(",")); + let sources = sourcemap + .sources + .iter() + .map(|x| serde_json::to_string(x.as_ref())) + .collect::, serde_json::Error>>() + .map_err(Error::from)?; + buf.push_str(&sources.join(",")); // Quote `source_content` at parallel. if let Some(source_contents) = &sourcemap.source_contents { buf.push_str("],\"sourcesContent\":["); - buf.push_str( - &source_contents.par_iter().map(|x| format!("{x:?}")).collect::>().join(","), - ); + let quote_source_contents = source_contents + .par_iter() + .map(|x| serde_json::to_string(x.as_ref())) + .collect::, serde_json::Error>>() + .map_err(Error::from)?; + buf.push_str("e_source_contents.join(",")); } buf.push_str("],\"mappings\":\""); buf.push_str(&serialize_sourcemap_mappings(sourcemap)); buf.push_str("\"}"); - buf + Ok(buf) } #[allow(clippy::cast_possible_truncation)] @@ -130,9 +148,26 @@ fn test_encode() { "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" }"#; let sm = SourceMap::from_json_string(input).unwrap(); - let sm2 = SourceMap::from_json_string(&sm.to_json_string()).unwrap(); + let sm2 = SourceMap::from_json_string(&sm.to_json_string().unwrap()).unwrap(); for (tok1, tok2) in sm.get_tokens().zip(sm2.get_tokens()) { assert_eq!(tok1, tok2); } } + +#[test] +fn test_encode_escape_string() { + // '\0' should be escaped. + let sm = SourceMap::new( + None, + vec!["\0".into()], + vec!["\0".into()], + Some(vec!["\0".into()]), + vec![], + None, + ); + assert_eq!( + sm.to_json_string().unwrap(), + r#"{"version":3,"names":["\u0000"],"sources":["\u0000"],"sourcesContent":["\u0000"],"mappings":""}"# + ); +} diff --git a/crates/oxc_sourcemap/src/sourcemap.rs b/crates/oxc_sourcemap/src/sourcemap.rs index 095a8cfee..d6dfec5b3 100644 --- a/crates/oxc_sourcemap/src/sourcemap.rs +++ b/crates/oxc_sourcemap/src/sourcemap.rs @@ -31,21 +31,29 @@ impl SourceMap { } /// Convert `SourceMap` to vlq sourcemap string. - #[allow(clippy::missing_errors_doc)] + /// # Errors + /// + /// The `serde_json` deserialize Error. pub fn from_json_string(value: &str) -> Result { decode(value) } /// Convert the vlq sourcemap string to `SourceMap`. - pub fn to_json_string(&self) -> String { + /// # Errors + /// + /// The `serde_json` serialization Error. + pub fn to_json_string(&self) -> Result { encode(self) } /// Convert `SourceMap` to vlq sourcemap data url. - pub fn to_data_url(&self) -> String { + /// # Errors + /// + /// The `serde_json` serialization Error. + pub fn to_data_url(&self) -> Result { let base_64_str = - base64_simd::Base64::STANDARD.encode_to_boxed_str(self.to_json_string().as_bytes()); - format!("data:application/json;charset=utf-8;base64,{base_64_str}") + base64_simd::Base64::STANDARD.encode_to_boxed_str(self.to_json_string()?.as_bytes()); + Ok(format!("data:application/json;charset=utf-8;base64,{base_64_str}")) } pub fn get_file(&self) -> Option<&str> { diff --git a/crates/oxc_sourcemap/src/sourcemap_builder.rs b/crates/oxc_sourcemap/src/sourcemap_builder.rs index ca93836ec..13cc6f172 100644 --- a/crates/oxc_sourcemap/src/sourcemap_builder.rs +++ b/crates/oxc_sourcemap/src/sourcemap_builder.rs @@ -85,5 +85,5 @@ fn test_sourcemap_builder() { let expected = r#"{"version":3,"names":["x"],"sources":["baz.js"],"sourcesContent":[""],"mappings":""}"#; - assert_eq!(expected, sm.to_json_string()); + assert_eq!(expected, sm.to_json_string().unwrap()); } diff --git a/tasks/benchmark/benches/sourcemap.rs b/tasks/benchmark/benches/sourcemap.rs index af05e6f50..9bdf9694b 100644 --- a/tasks/benchmark/benches/sourcemap.rs +++ b/tasks/benchmark/benches/sourcemap.rs @@ -31,7 +31,7 @@ fn bench_sourcemap(criterion: &mut Criterion) { for i in 0..1 { concat_sourcemap_builder.add_sourcemap(&sourcemap, line * i); } - concat_sourcemap_builder.into_sourcemap().to_json_string(); + concat_sourcemap_builder.into_sourcemap().to_json_string().unwrap(); } }); });