Skip to main content

swc_sourcemap/
decoder.rs

1use std::{
2    io,
3    io::{BufReader, Read},
4};
5
6use bitvec::{field::BitField, order::Lsb0, vec::BitVec};
7use serde_json::Value;
8
9use crate::{
10    errors::{Error, Result},
11    hermes::decode_hermes,
12    jsontypes::RawSourceMap,
13    types::{DecodedMap, RawToken, SourceMap, SourceMapIndex, SourceMapSection},
14    vlq::parse_vlq_segment_into,
15};
16
17const DATA_PREAMBLE: &str = "data:application/json;base64,";
18
19#[derive(PartialEq, Eq)]
20enum HeaderState {
21    Undecided,
22    Junk,
23    AwaitingNewline,
24    PastHeader,
25}
26
27pub struct StripHeaderReader<R: Read> {
28    r: R,
29    header_state: HeaderState,
30}
31
32impl<R: Read> StripHeaderReader<R> {
33    pub fn new(reader: R) -> StripHeaderReader<R> {
34        StripHeaderReader {
35            r: reader,
36            header_state: HeaderState::Undecided,
37        }
38    }
39}
40
41fn is_junk_json(byte: u8) -> bool {
42    byte == b')' || byte == b']' || byte == b'}' || byte == b'\''
43}
44
45impl<R: Read> Read for StripHeaderReader<R> {
46    #[inline(always)]
47    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
48        if self.header_state == HeaderState::PastHeader {
49            return self.r.read(buf);
50        }
51        self.strip_head_read(buf)
52    }
53}
54
55impl<R: Read> StripHeaderReader<R> {
56    fn strip_head_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
57        let mut backing = vec![0; buf.len()];
58        let local_buf: &mut [u8] = &mut backing;
59
60        loop {
61            let read = self.r.read(local_buf)?;
62            if read == 0 {
63                return Ok(0);
64            }
65            for (offset, &byte) in local_buf[0..read].iter().enumerate() {
66                self.header_state = match self.header_state {
67                    HeaderState::Undecided => {
68                        if is_junk_json(byte) {
69                            HeaderState::Junk
70                        } else {
71                            buf[..read].copy_from_slice(&local_buf[..read]);
72                            self.header_state = HeaderState::PastHeader;
73                            return Ok(read);
74                        }
75                    }
76                    HeaderState::Junk => {
77                        if byte == b'\r' {
78                            HeaderState::AwaitingNewline
79                        } else if byte == b'\n' {
80                            HeaderState::PastHeader
81                        } else {
82                            HeaderState::Junk
83                        }
84                    }
85                    HeaderState::AwaitingNewline => {
86                        if byte == b'\n' {
87                            HeaderState::PastHeader
88                        } else {
89                            Err(io::Error::new(
90                                io::ErrorKind::InvalidData,
91                                "expected newline",
92                            ))?
93                        }
94                    }
95                    HeaderState::PastHeader => {
96                        let rem = read - offset;
97                        buf[..rem].copy_from_slice(&local_buf[offset..read]);
98                        return Ok(rem);
99                    }
100                };
101            }
102        }
103    }
104}
105
106pub fn strip_junk_header(slice: &[u8]) -> io::Result<&[u8]> {
107    if slice.is_empty() || !is_junk_json(slice[0]) {
108        return Ok(slice);
109    }
110    let mut need_newline = false;
111    for (idx, &byte) in slice.iter().enumerate() {
112        if need_newline && byte != b'\n' {
113            Err(io::Error::new(
114                io::ErrorKind::InvalidData,
115                "expected newline",
116            ))?
117        } else if is_junk_json(byte) {
118            continue;
119        } else if byte == b'\r' {
120            need_newline = true;
121        } else if byte == b'\n' {
122            return Ok(&slice[idx..]);
123        }
124    }
125    Ok(&slice[slice.len()..])
126}
127
128/// Decodes range mappping bitfield string into index
129pub(crate) fn decode_rmi(rmi_str: &str, val: &mut BitVec<u8, Lsb0>) -> Result<()> {
130    val.clear();
131    val.resize(rmi_str.len() * 6, false);
132
133    for (idx, &byte) in rmi_str.as_bytes().iter().enumerate() {
134        let byte = match byte {
135            b'A'..=b'Z' => byte - b'A',
136            b'a'..=b'z' => byte - b'a' + 26,
137            b'0'..=b'9' => byte - b'0' + 52,
138            b'+' => 62,
139            b'/' => 63,
140            _ => {
141                return Err(Error::InvalidBase64(byte as char));
142            }
143        };
144
145        val[6 * idx..6 * (idx + 1)].store_le::<u8>(byte);
146    }
147
148    Ok(())
149}
150
151pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
152    let mut dst_col;
153
154    // Source IDs, lines, columns, and names are "running" values.
155    // Each token (except the first) contains the delta from the previous value.
156    let mut running_src_id = 0;
157    let mut running_src_line = 0;
158    let mut running_src_col = 0;
159    let mut running_name_id = 0;
160
161    let names = rsm.names.unwrap_or_default();
162    let sources = rsm.sources.unwrap_or_default();
163    let range_mappings = rsm.range_mappings.unwrap_or_default();
164    let mappings = rsm.mappings.unwrap_or_default();
165    let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10;
166    let mut tokens = Vec::with_capacity(allocation_size);
167
168    let mut nums = Vec::with_capacity(6);
169    let mut rmi = BitVec::new();
170
171    for (dst_line, (line, rmi_str)) in mappings
172        .split(';')
173        .zip(range_mappings.split(';').chain(std::iter::repeat("")))
174        .enumerate()
175    {
176        if line.is_empty() {
177            continue;
178        }
179
180        dst_col = 0;
181
182        decode_rmi(rmi_str, &mut rmi)?;
183
184        for (line_index, segment) in line.split(',').enumerate() {
185            if segment.is_empty() {
186                continue;
187            }
188
189            nums.clear();
190            parse_vlq_segment_into(segment, &mut nums)?;
191            match nums.len() {
192                1 | 4 | 5 => {}
193                _ => return Err(Error::BadSegmentSize(nums.len() as u32)),
194            }
195
196            dst_col = (i64::from(dst_col) + nums[0]) as u32;
197
198            // The source file , source line, source column, and name
199            // may not be present in the current token. We use `u32::MAX`
200            // as the placeholder for missing values.
201            let mut current_src_id = !0;
202            let mut current_src_line = !0;
203            let mut current_src_col = !0;
204            let mut current_name_id = !0;
205
206            if nums.len() > 1 {
207                running_src_id = (i64::from(running_src_id) + nums[1]) as u32;
208
209                if running_src_id >= sources.len() as u32 {
210                    return Err(Error::BadSourceReference(running_src_id));
211                }
212
213                running_src_line = (i64::from(running_src_line) + nums[2]) as u32;
214                running_src_col = (i64::from(running_src_col) + nums[3]) as u32;
215
216                current_src_id = running_src_id;
217                current_src_line = running_src_line;
218                current_src_col = running_src_col;
219
220                if nums.len() > 4 {
221                    running_name_id = (i64::from(running_name_id) + nums[4]) as u32;
222                    if running_name_id >= names.len() as u32 {
223                        return Err(Error::BadNameReference(running_name_id));
224                    }
225                    current_name_id = running_name_id;
226                }
227            }
228
229            let is_range = rmi.get(line_index).map(|v| *v).unwrap_or_default();
230
231            tokens.push(RawToken {
232                dst_line: dst_line as u32,
233                dst_col,
234                src_line: current_src_line,
235                src_col: current_src_col,
236                src_id: current_src_id,
237                name_id: current_name_id,
238                is_range,
239            });
240        }
241    }
242
243    let sources = sources
244        .into_iter()
245        .map(Option::unwrap_or_default)
246        .map(Into::into)
247        .collect();
248
249    // apparently we can encounter some non string types in real world
250    // sourcemaps :(
251    let names = names
252        .into_iter()
253        .map(|val| match val {
254            Value::String(s) => s.into(),
255            Value::Number(num) => num.to_string().into(),
256            _ => "".into(),
257        })
258        .collect::<Vec<_>>();
259
260    // file sometimes is not a string for unexplicable reasons
261    let file = rsm.file.map(|val| match val {
262        Value::String(s) => s.into(),
263        _ => "<invalid>".into(),
264    });
265
266    let source_content = rsm
267        .sources_content
268        .map(|x| x.into_iter().map(|v| v.map(Into::into)).collect::<Vec<_>>());
269
270    let mut sm = SourceMap::new(file, tokens, names, sources, source_content);
271    sm.set_scopes(rsm.scopes);
272    sm.set_source_root(rsm.source_root);
273    // Use _debug_id_new (from "debugId" key) only if debug_id
274    // from ( "debug_id" key) is unset
275    sm.set_debug_id(rsm.debug_id.or(rsm._debug_id_new));
276    if let Some(ignore_list) = rsm.ignore_list {
277        for idx in ignore_list {
278            sm.add_to_ignore_list(idx);
279        }
280    }
281
282    Ok(sm)
283}
284
285fn decode_index(rsm: RawSourceMap) -> Result<SourceMapIndex> {
286    let mut sections = vec![];
287
288    for mut raw_section in rsm.sections.unwrap_or_default() {
289        sections.push(SourceMapSection::new(
290            (raw_section.offset.line, raw_section.offset.column),
291            raw_section.url,
292            match raw_section.map.take() {
293                Some(map) => Some(decode_common(*map)?),
294                None => None,
295            },
296        ));
297    }
298
299    sections.sort_by_key(SourceMapSection::get_offset);
300
301    // file sometimes is not a string for unexplicable reasons
302    let file = rsm.file.map(|val| match val {
303        Value::String(s) => s.into(),
304        _ => "<invalid>".into(),
305    });
306
307    Ok(SourceMapIndex::new_ram_bundle_compatible(
308        file,
309        sections,
310        rsm.x_facebook_offsets,
311        rsm.x_metro_module_paths,
312    )
313    .with_debug_id(rsm._debug_id_new.or(rsm.debug_id)))
314}
315
316fn decode_common(rsm: RawSourceMap) -> Result<DecodedMap> {
317    Ok(if rsm.sections.is_some() {
318        DecodedMap::Index(decode_index(rsm)?)
319    } else if rsm.x_facebook_sources.is_some() {
320        DecodedMap::Hermes(decode_hermes(rsm)?)
321    } else {
322        DecodedMap::Regular(decode_regular(rsm)?)
323    })
324}
325
326/// Decodes a sourcemap or sourcemap index from a reader
327///
328/// This supports both sourcemaps and sourcemap indexes unless the
329/// specialized methods on the individual types.
330pub fn decode<R: Read>(rdr: R) -> Result<DecodedMap> {
331    let mut rdr = StripHeaderReader::new(rdr);
332    let mut rdr = BufReader::new(&mut rdr);
333    let rsm: RawSourceMap = serde_json::from_reader(&mut rdr)?;
334    decode_common(rsm)
335}
336
337/// Decodes a sourcemap or sourcemap index from a byte slice
338///
339/// This supports both sourcemaps and sourcemap indexes unless the
340/// specialized methods on the individual types.
341pub fn decode_slice(slice: &[u8]) -> Result<DecodedMap> {
342    let content = strip_junk_header(slice)?;
343    let rsm: RawSourceMap = serde_json::from_slice(content)?;
344    decode_common(rsm)
345}
346
347/// Loads a sourcemap from a data URL
348pub fn decode_data_url(url: &str) -> Result<DecodedMap> {
349    if !url.starts_with(DATA_PREAMBLE) {
350        return Err(Error::InvalidDataUrl);
351    }
352    let data_b64 = &url[DATA_PREAMBLE.len()..];
353    let data = data_encoding::BASE64
354        .decode(data_b64.as_bytes())
355        .map_err(|_| Error::InvalidDataUrl)?;
356    decode_slice(&data[..])
357}
358
359#[cfg(test)]
360mod tests {
361    use std::io::{self, BufRead};
362
363    use super::*;
364
365    #[test]
366    fn test_strip_header() {
367        let input: &[_] = b")]}garbage\r\n[1, 2, 3]";
368        let mut reader = io::BufReader::new(StripHeaderReader::new(input));
369        let mut text = String::new();
370        reader.read_line(&mut text).ok();
371        assert_eq!(text, "[1, 2, 3]");
372    }
373
374    #[test]
375    fn test_bad_newline() {
376        let input: &[_] = b")]}'\r[1, 2, 3]";
377        let mut reader = io::BufReader::new(StripHeaderReader::new(input));
378        let mut text = String::new();
379        match reader.read_line(&mut text) {
380            Err(err) => {
381                assert_eq!(err.kind(), io::ErrorKind::InvalidData);
382            }
383            Ok(_) => {
384                panic!("Expected failure");
385            }
386        }
387    }
388
389    #[test]
390    fn test_decode_rmi() {
391        fn decode(rmi_str: &str) -> Vec<usize> {
392            let mut out = bitvec::bitvec![u8, Lsb0; 0; 0];
393            decode_rmi(rmi_str, &mut out).expect("failed to decode");
394
395            let mut res = vec![];
396            for (idx, bit) in out.iter().enumerate() {
397                if *bit {
398                    res.push(idx);
399                }
400            }
401            res
402        }
403
404        // This is 0-based index of the bits
405        assert_eq!(decode("AAB"), vec![12]);
406        assert_eq!(decode("g"), vec![5]);
407        assert_eq!(decode("Bg"), vec![0, 11]);
408    }
409
410    #[test]
411    fn test_decode_sourcemap_index_no_debug_id() {
412        let raw = RawSourceMap {
413            version: Some(3),
414            file: Some("test.js".into()),
415            sources: None,
416            source_root: None,
417            sources_content: None,
418            sections: Some(vec![]),
419            names: None,
420            scopes: None,
421            range_mappings: None,
422            mappings: None,
423            ignore_list: None,
424            x_facebook_offsets: None,
425            x_metro_module_paths: None,
426            x_facebook_sources: None,
427            debug_id: None,
428            _debug_id_new: None,
429        };
430
431        let decoded = decode_common(raw).expect("should decoded");
432        assert_eq!(
433            decoded,
434            DecodedMap::Index(SourceMapIndex::new(Some("test.js".into()), vec![]))
435        );
436    }
437
438    #[test]
439    fn test_decode_sourcemap_index_debug_id() {
440        const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";
441
442        let raw = RawSourceMap {
443            version: Some(3),
444            file: Some("test.js".into()),
445            sources: None,
446            source_root: None,
447            sources_content: None,
448            sections: Some(vec![]),
449            names: None,
450            scopes: None,
451            range_mappings: None,
452            mappings: None,
453            ignore_list: None,
454            x_facebook_offsets: None,
455            x_metro_module_paths: None,
456            x_facebook_sources: None,
457            debug_id: None,
458            _debug_id_new: Some(DEBUG_ID.parse().expect("valid debug id")),
459        };
460
461        let decoded = decode_common(raw).expect("should decode");
462        assert_eq!(
463            decoded,
464            DecodedMap::Index(
465                SourceMapIndex::new(Some("test.js".into()), vec![])
466                    .with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")))
467            )
468        );
469    }
470
471    #[test]
472    fn test_decode_sourcemap_index_debug_id_from_legacy_key() {
473        const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";
474
475        let raw = RawSourceMap {
476            version: Some(3),
477            file: Some("test.js".into()),
478            sources: None,
479            source_root: None,
480            sources_content: None,
481            sections: Some(vec![]),
482            names: None,
483            scopes: None,
484            range_mappings: None,
485            mappings: None,
486            ignore_list: None,
487            x_facebook_offsets: None,
488            x_metro_module_paths: None,
489            x_facebook_sources: None,
490            debug_id: Some(DEBUG_ID.parse().expect("valid debug id")),
491            _debug_id_new: None,
492        };
493
494        let decoded = decode_common(raw).expect("should decode");
495        assert_eq!(
496            decoded,
497            DecodedMap::Index(
498                SourceMapIndex::new(Some("test.js".into()), vec![])
499                    .with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")))
500            )
501        );
502    }
503}