Skip to content

Commit 00b21ab

Browse files
authored
feat: Add support for embedded debug IDs in minified files (#765)
1 parent 68147ad commit 00b21ab

File tree

5 files changed

+139
-27
lines changed

5 files changed

+139
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**Features**:
66

77
- Added debug IDs to source bundle JavaScript files and source maps. ([#762](https://github.com/getsentry/symbolic/pull/762))
8+
- Add support for embedded debug IDs in minified files ([#765](https://github.com/getsentry/symbolic/pull/765))
89

910
**Breaking changes**:
1011

symbolic-debuginfo/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ exclude = ["tests/**/*"]
2121
all-features = true
2222

2323
[features]
24-
default = ["breakpad", "elf", "macho", "ms", "ppdb", "sourcebundle", "wasm"]
24+
default = ["breakpad", "elf", "macho", "ms", "ppdb", "sourcebundle", "js", "wasm"]
2525
# Breakpad text format parsing and processing
2626
breakpad = ["nom", "nom-supreme", "regex"]
2727
# DWARF processing.
@@ -70,8 +70,11 @@ sourcebundle = [
7070
"regex",
7171
"serde_json",
7272
"zip",
73-
"debugid/serde"
73+
"js",
74+
"debugid/serde",
7475
]
76+
# JavaScript stuff
77+
js = []
7578
# WASM processing
7679
wasm = ["bitvec", "dwarf", "wasmparser"]
7780

symbolic-debuginfo/src/js.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! Utilities specifically for working with JavaScript specific debug info.
2+
//!
3+
//! This for the most part only contains utility functions to parse references
4+
//! out of minified JavaScript files and source maps. For actually working
5+
//! with source maps this module is insufficient.
6+
7+
use debugid::DebugId;
8+
use serde::Deserialize;
9+
10+
/// Parses a sourceMappingURL comment in a file to discover a sourcemap reference.
11+
pub fn discover_sourcemaps_location(contents: &str) -> Option<&str> {
12+
for line in contents.lines().rev() {
13+
if line.starts_with("//# sourceMappingURL=") || line.starts_with("//@ sourceMappingURL=") {
14+
return Some(line[21..].trim());
15+
}
16+
}
17+
None
18+
}
19+
20+
/// Quickly reads the embedded `debug_id` key from a source map.
21+
pub fn discover_sourcemap_embedded_debug_id(contents: &str) -> Option<DebugId> {
22+
#[derive(Deserialize)]
23+
struct DebugIdInSourceMap {
24+
debug_id: Option<DebugId>,
25+
}
26+
27+
serde_json::from_str(contents)
28+
.ok()
29+
.and_then(|x: DebugIdInSourceMap| x.debug_id)
30+
}
31+
32+
/// Parses a `debugId` comment in a file to discover a sourcemap's debug ID.
33+
pub fn discover_debug_id(contents: &str) -> Option<DebugId> {
34+
for line in contents.lines().rev() {
35+
if let Some(rest) = line.strip_prefix("//# debugId=") {
36+
return rest.trim().parse().ok();
37+
}
38+
}
39+
None
40+
}

symbolic-debuginfo/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ pub mod elf;
5757
pub mod function_builder;
5858
#[cfg(feature = "ms")]
5959
pub(crate) mod function_stack;
60+
#[cfg(feature = "js")]
61+
pub mod js;
6062
#[cfg(feature = "macho")]
6163
pub mod macho;
6264
#[cfg(feature = "ms")]

symbolic-debuginfo/src/sourcebundle.rs

Lines changed: 91 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ use zip::{write::FileOptions, ZipWriter};
6161
use symbolic_common::{Arch, AsSelf, CodeId, DebugId};
6262

6363
use crate::base::*;
64+
use crate::js::{
65+
discover_debug_id, discover_sourcemap_embedded_debug_id, discover_sourcemaps_location,
66+
};
6467
use crate::{DebugSession, ObjectKind, ObjectLike};
6568

6669
/// Magic bytes of a source bundle. They are prepended to the ZIP file.
@@ -400,38 +403,37 @@ impl<'a> SourceFileDescriptor<'a> {
400403
/// The debug ID of the file if available.
401404
///
402405
/// For source maps or minified source files symbolic supports embedded debug IDs. If they
403-
/// are in use, the debug ID is returned from here.
406+
/// are in use, the debug ID is returned from here. The debug ID is discovered from the
407+
/// file's `debug-id` header or the embedded `debugId` reference in the file body.
404408
pub fn debug_id(&self) -> Option<DebugId> {
405-
self.file_info.and_then(|x| x.debug_id())
409+
self.file_info.and_then(|x| x.debug_id()).or_else(|| {
410+
if matches!(self.ty(), SourceFileType::MinifiedSource) {
411+
self.contents().and_then(discover_debug_id)
412+
} else if matches!(self.ty(), SourceFileType::SourceMap) {
413+
self.contents()
414+
.and_then(discover_sourcemap_embedded_debug_id)
415+
} else {
416+
None
417+
}
418+
})
406419
}
407420

408421
/// The source mapping URL reference of the file.
409422
///
410423
/// This is used to refer to a source map from a minified file. Only minified source files
411-
/// will have a relationship to a source map.
424+
/// will have a relationship to a source map. The source mapping is discovered either from
425+
/// a `sourcemap` header in the source manifest, or the `sourceMappingURL` reference in the body.
412426
pub fn source_mapping_url(&self) -> Option<&str> {
413-
if let Some(file_info) = self.file_info {
414-
if let Some(url) = file_info.source_mapping_url() {
415-
return Some(url);
416-
}
417-
}
418-
if let Some(ref contents) = self.contents {
419-
if let Some(url) = discover_sourcemaps_location(contents) {
420-
return Some(url);
421-
}
422-
}
423-
None
424-
}
425-
}
426-
427-
/// Parses a sourceMappingURL comment in a file to discover a sourcemap reference.
428-
fn discover_sourcemaps_location(contents: &str) -> Option<&str> {
429-
for line in contents.lines().rev() {
430-
if line.starts_with("//# sourceMappingURL=") || line.starts_with("//@ sourceMappingURL=") {
431-
return Some(line[21..].trim());
432-
}
427+
self.file_info
428+
.and_then(|x| x.source_mapping_url())
429+
.or_else(|| {
430+
if matches!(self.ty(), SourceFileType::MinifiedSource) {
431+
self.contents().and_then(discover_sourcemaps_location)
432+
} else {
433+
None
434+
}
435+
})
433436
}
434-
None
435437
}
436438

437439
/// Version number of a [`SourceBundle`](struct.SourceBundle.html).
@@ -1416,7 +1418,7 @@ mod tests {
14161418
info.set_ty(SourceFileType::MinifiedSource);
14171419
bundle.add_file(
14181420
"bar.js",
1419-
&b"filecontents\n//@ sourceMappingURL=bar.js.map"[..],
1421+
&b"filecontents\n//# sourceMappingURL=bar.js.map"[..],
14201422
info,
14211423
)?;
14221424

@@ -1436,6 +1438,70 @@ mod tests {
14361438
Ok(())
14371439
}
14381440

1441+
#[test]
1442+
fn test_source_embedded_debug_id() -> Result<(), SourceBundleError> {
1443+
let mut writer = Cursor::new(Vec::new());
1444+
let mut bundle = SourceBundleWriter::start(&mut writer)?;
1445+
1446+
let mut info = SourceFileInfo::default();
1447+
info.set_url("https://example.com/bar.min.js".into());
1448+
info.set_ty(SourceFileType::MinifiedSource);
1449+
bundle.add_file(
1450+
"bar.js",
1451+
&b"filecontents\n//# debugId=5b65abfb23384f0bb3b964c8f734d43f"[..],
1452+
info,
1453+
)?;
1454+
1455+
bundle.finish()?;
1456+
let bundle_bytes = writer.into_inner();
1457+
let bundle = SourceBundle::parse(&bundle_bytes)?;
1458+
1459+
let sess = bundle.debug_session().unwrap();
1460+
let f = sess
1461+
.source_by_url("https://example.com/bar.min.js")
1462+
.unwrap()
1463+
.expect("should exist");
1464+
assert_eq!(f.ty(), SourceFileType::MinifiedSource);
1465+
assert_eq!(
1466+
f.debug_id(),
1467+
Some("5b65abfb-2338-4f0b-b3b9-64c8f734d43f".parse().unwrap())
1468+
);
1469+
1470+
Ok(())
1471+
}
1472+
1473+
#[test]
1474+
fn test_sourcemap_embedded_debug_id() -> Result<(), SourceBundleError> {
1475+
let mut writer = Cursor::new(Vec::new());
1476+
let mut bundle = SourceBundleWriter::start(&mut writer)?;
1477+
1478+
let mut info = SourceFileInfo::default();
1479+
info.set_url("https://example.com/bar.js.map".into());
1480+
info.set_ty(SourceFileType::SourceMap);
1481+
bundle.add_file(
1482+
"bar.js.map",
1483+
&br#"{"debug_id": "5b65abfb-2338-4f0b-b3b9-64c8f734d43f"}"#[..],
1484+
info,
1485+
)?;
1486+
1487+
bundle.finish()?;
1488+
let bundle_bytes = writer.into_inner();
1489+
let bundle = SourceBundle::parse(&bundle_bytes)?;
1490+
1491+
let sess = bundle.debug_session().unwrap();
1492+
let f = sess
1493+
.source_by_url("https://example.com/bar.js.map")
1494+
.unwrap()
1495+
.expect("should exist");
1496+
assert_eq!(f.ty(), SourceFileType::SourceMap);
1497+
assert_eq!(
1498+
f.debug_id(),
1499+
Some("5b65abfb-2338-4f0b-b3b9-64c8f734d43f".parse().unwrap())
1500+
);
1501+
1502+
Ok(())
1503+
}
1504+
14391505
#[test]
14401506
fn test_il2cpp_reference() -> Result<(), Box<dyn std::error::Error>> {
14411507
let mut cpp_file = NamedTempFile::new()?;

0 commit comments

Comments
 (0)