Skip to content

Commit ea97ee4

Browse files
fix: reject indexed maps in unsupported parsers
1 parent 40264c8 commit ea97ee4

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

crates/sourcemap/src/lib.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,13 +810,18 @@ impl SourceMap {
810810
/// Parse a source map from JSON, skipping sourcesContent allocation.
811811
/// Useful for WASM bindings where sourcesContent is kept on the JS side.
812812
/// The resulting SourceMap has an empty `sources_content` vec.
813+
/// Indexed source maps are not supported; use [`SourceMap::from_json`] instead.
813814
pub fn from_json_no_content(json: &str) -> Result<Self, ParseError> {
814815
let raw: RawSourceMapLite<'_> = serde_json::from_str(json)?;
815816

816817
if raw.version != 3 {
817818
return Err(ParseError::InvalidVersion(raw.version));
818819
}
819820

821+
if raw.sections.is_some() {
822+
return Err(ParseError::NestedIndexMap);
823+
}
824+
820825
let source_root = raw.source_root.as_deref().unwrap_or("");
821826
let sources = resolve_sources(&raw.sources, source_root);
822827
let source_map = build_source_map(&sources);
@@ -1662,13 +1667,18 @@ impl SourceMap {
16621667
/// This is useful for large source maps where only a subset of lines is needed.
16631668
/// VLQ state is maintained through skipped lines (required for correct delta decoding),
16641669
/// but `Mapping` structs are only allocated for lines in the requested range.
1670+
/// Indexed source maps are not supported; use [`SourceMap::from_json`] instead.
16651671
pub fn from_json_lines(json: &str, start_line: u32, end_line: u32) -> Result<Self, ParseError> {
16661672
let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
16671673

16681674
if raw.version != 3 {
16691675
return Err(ParseError::InvalidVersion(raw.version));
16701676
}
16711677

1678+
if raw.sections.is_some() {
1679+
return Err(ParseError::NestedIndexMap);
1680+
}
1681+
16721682
let source_root = raw.source_root.as_deref().unwrap_or("");
16731683
let sources = resolve_sources(&raw.sources, source_root);
16741684
let sources_content = raw.sources_content.unwrap_or_default();
@@ -2027,13 +2037,18 @@ impl LazySourceMap {
20272037
///
20282038
/// Parses all JSON metadata eagerly but stores the raw mappings string.
20292039
/// VLQ mappings are decoded per-line on demand.
2040+
/// Indexed source maps are not supported; use [`SourceMap::from_json`] instead.
20302041
pub fn from_json(json: &str) -> Result<Self, ParseError> {
20312042
let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
20322043

20332044
if raw.version != 3 {
20342045
return Err(ParseError::InvalidVersion(raw.version));
20352046
}
20362047

2048+
if raw.sections.is_some() {
2049+
return Err(ParseError::NestedIndexMap);
2050+
}
2051+
20372052
let source_root = raw.source_root.as_deref().unwrap_or("");
20382053
let sources = resolve_sources(&raw.sources, source_root);
20392054
let sources_content = raw.sources_content.unwrap_or_default();
@@ -3547,6 +3562,10 @@ mod tests {
35473562
r#"{"version":3,"sources":["input.js"],"names":["hello"],"mappings":"AAAA;AACA,EAAA;AACA"}"#
35483563
}
35493564

3565+
fn indexed_map() -> &'static str {
3566+
r#"{"version":3,"sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}}]}"#
3567+
}
3568+
35503569
#[test]
35513570
fn parse_basic() {
35523571
let sm = SourceMap::from_json(simple_map()).unwrap();
@@ -7191,7 +7210,7 @@ mod tests {
71917210

71927211
#[test]
71937212
fn lazy_sourcemap_rejects_indexed_maps() {
7194-
let json = r#"{"version":3,"sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}}]}"#;
7213+
let json = indexed_map();
71957214
let result = LazySourceMap::from_json_fast(json);
71967215
assert!(result.is_err());
71977216
assert!(matches!(result.unwrap_err(), ParseError::NestedIndexMap));
@@ -7201,6 +7220,33 @@ mod tests {
72017220
assert!(matches!(result.unwrap_err(), ParseError::NestedIndexMap));
72027221
}
72037222

7223+
#[test]
7224+
fn source_map_no_content_rejects_indexed_maps() {
7225+
let json = indexed_map();
7226+
let err = SourceMap::from_json_no_content(json)
7227+
.expect_err("indexed maps must not be parsed as empty regular maps");
7228+
7229+
assert!(matches!(err, ParseError::NestedIndexMap));
7230+
}
7231+
7232+
#[test]
7233+
fn source_map_lines_rejects_indexed_maps() {
7234+
let json = indexed_map();
7235+
let err = SourceMap::from_json_lines(json, 0, 1)
7236+
.expect_err("indexed maps must not be parsed as empty regular maps");
7237+
7238+
assert!(matches!(err, ParseError::NestedIndexMap));
7239+
}
7240+
7241+
#[test]
7242+
fn lazy_source_map_rejects_indexed_maps() {
7243+
let json = indexed_map();
7244+
let err = LazySourceMap::from_json(json)
7245+
.expect_err("indexed maps must not be parsed as empty regular maps");
7246+
7247+
assert!(matches!(err, ParseError::NestedIndexMap));
7248+
}
7249+
72047250
#[test]
72057251
fn lazy_sourcemap_regular_map_still_works() {
72067252
let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;

packages/sourcemap-wasm/__tests__/sourcemap-wasm.test.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,28 @@ describe("originalPositionBuf (zero-alloc)", () => {
365365
});
366366

367367
describe("indexed source maps", () => {
368+
it("rejects indexed maps in the no-content constructor", () => {
369+
const indexedMap = JSON.stringify({
370+
version: 3,
371+
sections: [
372+
{
373+
offset: { line: 0, column: 0 },
374+
map: {
375+
version: 3,
376+
sources: ["a.js"],
377+
names: [],
378+
mappings: "AAAA",
379+
},
380+
},
381+
],
382+
});
383+
384+
assert.throws(
385+
() => SourceMap.fromJsonNoContent(indexedMap),
386+
/section map must not be an indexed source map/,
387+
);
388+
});
389+
368390
it("parses an indexed (sectioned) source map", () => {
369391
const indexedMap = JSON.stringify({
370392
version: 3,

0 commit comments

Comments
 (0)