Skip to content

Commit 6394b02

Browse files
vaindloewenheim
andauthored
feat: change DebugSession::source_by_path() to return SourceCode enum (#758)
Co-authored-by: Sebastian Zivota <loewenheim@users.noreply.github.com>
1 parent 9f7ceef commit 6394b02

File tree

17 files changed

+397
-61
lines changed

17 files changed

+397
-61
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
**Breaking changes**:
6+
7+
- Change `DebugSession::source_by_path()` to return `SourceCode` enum with either file content or a URL to fetch it from. ([#758](https://github.com/getsentry/symbolic/pull/758))
8+
59
**Fixes**:
610

711
- Make sure to parse `PortablePdb` streams in the correct order. ([#760](https://github.com/getsentry/symbolic/pull/760))

symbolic-debuginfo/src/base.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,17 @@ impl fmt::Debug for Function<'_> {
670670
/// A dynamically dispatched iterator over items with the given lifetime.
671671
pub type DynIterator<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
672672

673+
/// Represents a source file referenced by a debug information object file.
674+
#[non_exhaustive]
675+
#[derive(Debug, Clone)]
676+
pub enum SourceCode<'a> {
677+
/// Verbatim source code/file contents.
678+
Content(Cow<'a, str>),
679+
680+
/// Url (usually https) where the content can be fetched from.
681+
Url(Cow<'a, str>),
682+
}
683+
673684
/// A stateful session for interfacing with debug information.
674685
///
675686
/// Debug sessions can be obtained via [`ObjectLike::debug_session`]. Since computing a session may
@@ -716,10 +727,9 @@ pub trait DebugSession<'session> {
716727
/// Returns an iterator over all source files referenced by this debug file.
717728
fn files(&'session self) -> Self::FileIterator;
718729

719-
/// Looks up a file's source contents by its full canonicalized path.
720-
///
721-
/// The given path must be canonicalized.
722-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error>;
730+
/// Looks up a file's source by its full canonicalized path.
731+
/// Returns either source contents, if it was embedded, or a source link.
732+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error>;
723733
}
724734

725735
/// An object containing debug information.

symbolic-debuginfo/src/breakpad.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,10 +1276,8 @@ impl<'data> BreakpadDebugSession<'data> {
12761276
}
12771277
}
12781278

1279-
/// Looks up a file's source contents by its full canonicalized path.
1280-
///
1281-
/// The given path must be canonicalized.
1282-
pub fn source_by_path(&self, _path: &str) -> Result<Option<Cow<'_, str>>, BreakpadError> {
1279+
/// See [DebugSession::source_by_path] for more information.
1280+
pub fn source_by_path(&self, _path: &str) -> Result<Option<SourceCode<'_>>, BreakpadError> {
12831281
Ok(None)
12841282
}
12851283
}
@@ -1297,7 +1295,7 @@ impl<'data, 'session> DebugSession<'session> for BreakpadDebugSession<'data> {
12971295
self.files()
12981296
}
12991297

1300-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
1298+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error> {
13011299
self.source_by_path(path)
13021300
}
13031301
}

symbolic-debuginfo/src/dwarf.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,10 +1331,8 @@ impl<'data> DwarfDebugSession<'data> {
13311331
}
13321332
}
13331333

1334-
/// Looks up a file's source contents by its full canonicalized path.
1335-
///
1336-
/// The given path must be canonicalized.
1337-
pub fn source_by_path(&self, _path: &str) -> Result<Option<Cow<'_, str>>, DwarfError> {
1334+
/// See [DebugSession::source_by_path] for more information.
1335+
pub fn source_by_path(&self, _path: &str) -> Result<Option<SourceCode<'_>>, DwarfError> {
13381336
Ok(None)
13391337
}
13401338
}
@@ -1352,7 +1350,7 @@ impl<'data, 'session> DebugSession<'session> for DwarfDebugSession<'data> {
13521350
self.files()
13531351
}
13541352

1355-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
1353+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error> {
13561354
self.source_by_path(path)
13571355
}
13581356
}

symbolic-debuginfo/src/object.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Generic wrappers over various object file formats.
22
3-
use std::borrow::Cow;
43
use std::error::Error;
54
use std::fmt;
65

@@ -483,10 +482,9 @@ impl<'d> ObjectDebugSession<'d> {
483482
}
484483
}
485484

486-
/// Looks up a file's source contents by its full canonicalized path.
487-
///
488-
/// The given path must be canonicalized.
489-
pub fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, ObjectError> {
485+
/// Looks up a file's source by its full canonicalized path.
486+
/// Returns either source contents, if it was embedded, or a source link.
487+
pub fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, ObjectError> {
490488
match *self {
491489
ObjectDebugSession::Breakpad(ref s) => {
492490
s.source_by_path(path).map_err(ObjectError::transparent)
@@ -520,7 +518,7 @@ impl<'session> DebugSession<'session> for ObjectDebugSession<'_> {
520518
self.files()
521519
}
522520

523-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
521+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error> {
524522
self.source_by_path(path)
525523
}
526524
}

symbolic-debuginfo/src/pdb.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -637,10 +637,8 @@ impl<'d> PdbDebugSession<'d> {
637637
}
638638
}
639639

640-
/// Looks up a file's source contents by its full canonicalized path.
641-
///
642-
/// The given path must be canonicalized.
643-
pub fn source_by_path(&self, _path: &str) -> Result<Option<Cow<'_, str>>, PdbError> {
640+
/// See [DebugSession::source_by_path] for more information.
641+
pub fn source_by_path(&self, _path: &str) -> Result<Option<SourceCode<'_>>, PdbError> {
644642
Ok(None)
645643
}
646644
}
@@ -658,7 +656,7 @@ impl<'session> DebugSession<'session> for PdbDebugSession<'_> {
658656
self.files()
659657
}
660658

661-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
659+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error> {
662660
self.source_by_path(path)
663661
}
664662
}

symbolic-debuginfo/src/ppdb.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use std::collections::HashMap;
44
use std::fmt;
55
use std::iter;
66

7+
use lazycell::LazyCell;
78
use symbolic_common::{Arch, CodeId, DebugId};
89
use symbolic_ppdb::EmbeddedSource;
9-
use symbolic_ppdb::{FormatError, PortablePdb};
10+
use symbolic_ppdb::{Document, FormatError, PortablePdb};
1011

1112
use crate::base::*;
1213

@@ -141,24 +142,44 @@ impl fmt::Debug for PortablePdbObject<'_> {
141142
/// A debug session for a Portable PDB object.
142143
pub struct PortablePdbDebugSession<'data> {
143144
ppdb: PortablePdb<'data>,
144-
sources: HashMap<String, EmbeddedSource<'data>>,
145+
sources: LazyCell<HashMap<String, PPDBSource<'data>>>,
146+
}
147+
148+
#[derive(Debug, Clone)]
149+
enum PPDBSource<'data> {
150+
Embedded(EmbeddedSource<'data>),
151+
Link(Document),
145152
}
146153

147154
impl<'data> PortablePdbDebugSession<'data> {
148155
fn new(ppdb: &'_ PortablePdb<'data>) -> Result<Self, FormatError> {
149-
let mut sources: HashMap<String, EmbeddedSource<'data>> = HashMap::new();
150-
for source in ppdb.get_embedded_sources()? {
151-
match source {
152-
Ok(source) => sources.insert(source.get_path().into(), source),
153-
Err(e) => return Err(e),
154-
};
155-
}
156156
Ok(PortablePdbDebugSession {
157157
ppdb: ppdb.clone(),
158-
sources,
158+
sources: LazyCell::new(),
159159
})
160160
}
161161

162+
fn init_sources(&self) -> HashMap<String, PPDBSource<'data>> {
163+
let count = self.ppdb.get_documents_count().unwrap_or(0);
164+
let mut result = HashMap::with_capacity(count);
165+
166+
if let Ok(iter) = self.ppdb.get_embedded_sources() {
167+
for source in iter.flatten() {
168+
result.insert(source.get_path().to_string(), PPDBSource::Embedded(source));
169+
}
170+
};
171+
172+
for i in 1..count + 1 {
173+
if let Ok(doc) = self.ppdb.get_document(i) {
174+
if !result.contains_key(&doc.name) {
175+
result.insert(doc.name.clone(), PPDBSource::Link(doc));
176+
}
177+
}
178+
}
179+
180+
result
181+
}
182+
162183
/// Returns an iterator over all functions in this debug file.
163184
pub fn functions(&self) -> PortablePdbFunctionIterator<'_> {
164185
iter::empty()
@@ -169,15 +190,17 @@ impl<'data> PortablePdbDebugSession<'data> {
169190
PortablePdbFileIterator::new(&self.ppdb)
170191
}
171192

172-
/// Looks up a file's source contents by its full canonicalized path.
173-
///
174-
/// The given path must be canonicalized.
175-
pub fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, FormatError> {
176-
match self.sources.get(path) {
193+
/// See [DebugSession::source_by_path] for more information.
194+
pub fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, FormatError> {
195+
let sources = self.sources.borrow_with(|| self.init_sources());
196+
match sources.get(path) {
177197
None => Ok(None),
178-
Some(source) => source
198+
Some(PPDBSource::Embedded(source)) => source
179199
.get_contents()
180-
.map(|bytes| Some(from_utf8_cow_lossy(&bytes))),
200+
.map(|bytes| Some(SourceCode::Content(from_utf8_cow_lossy(&bytes)))),
201+
Some(PPDBSource::Link(document)) => {
202+
Ok(self.ppdb.get_source_link(document).map(SourceCode::Url))
203+
}
181204
}
182205
}
183206
}
@@ -195,7 +218,7 @@ impl<'data, 'session> DebugSession<'session> for PortablePdbDebugSession<'data>
195218
self.files()
196219
}
197220

198-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
221+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error> {
199222
self.source_by_path(path)
200223
}
201224
}

symbolic-debuginfo/src/sourcebundle.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -650,17 +650,15 @@ impl<'data> SourceBundleDebugSession<'data> {
650650
Ok(Some(source_content))
651651
}
652652

653-
/// Looks up a file's source contents by its full canonicalized path.
654-
///
655-
/// The given path must be canonicalized.
656-
pub fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, SourceBundleError> {
653+
/// See [DebugSession::source_by_path] for more information.
654+
pub fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, SourceBundleError> {
657655
let zip_path = match self.zip_path_by_source_path(path) {
658656
Some(zip_path) => zip_path,
659657
None => return Ok(None),
660658
};
661659

662-
self.source_by_zip_path(zip_path)
663-
.map(|opt| opt.map(Cow::Owned))
660+
let content = self.source_by_zip_path(zip_path)?;
661+
Ok(content.map(|opt| SourceCode::Content(Cow::Owned(opt))))
664662
}
665663
}
666664

@@ -677,7 +675,7 @@ impl<'data, 'session> DebugSession<'session> for SourceBundleDebugSession<'data>
677675
self.files()
678676
}
679677

680-
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
678+
fn source_by_path(&self, path: &str) -> Result<Option<SourceCode<'_>>, Self::Error> {
681679
self.source_by_path(path)
682680
}
683681
}
@@ -1152,11 +1150,12 @@ mod tests {
11521150
.flatten()
11531151
.flat_map(|f| {
11541152
let path = f.abs_path_str();
1155-
session
1156-
.source_by_path(&path)
1157-
.ok()
1158-
.flatten()
1159-
.map(|c| (path, c.into_owned()))
1153+
session.source_by_path(&path).ok().flatten().map(|source| {
1154+
let SourceCode::Content(text) = source else {
1155+
unreachable!();
1156+
};
1157+
(path, text.into_owned())
1158+
})
11601159
})
11611160
.collect();
11621161

symbolic-debuginfo/tests/test_objects.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{env, ffi::CString, fmt, io::BufWriter};
22

33
use symbolic_common::ByteView;
44
use symbolic_debuginfo::{
5-
elf::ElfObject, pe::PeObject, FileEntry, Function, LineInfo, Object, SymbolMap,
5+
elf::ElfObject, pe::PeObject, FileEntry, Function, LineInfo, Object, SourceCode, SymbolMap,
66
};
77
use symbolic_testutils::fixture;
88

@@ -776,10 +776,51 @@ fn test_ppdb_source_by_path() -> Result<(), Error> {
776776
"C:\\dev\\sentry-dotnet\\samples\\Sentry.Samples.Console.Basic\\Program.cs",
777777
)
778778
.unwrap();
779-
let source_text = source.unwrap();
780-
assert_eq!(source_text.len(), 204);
779+
match source.unwrap() {
780+
SourceCode::Content(text) => assert_eq!(text.len(), 204),
781+
_ => panic!(),
782+
}
783+
}
784+
785+
Ok(())
786+
}
787+
788+
#[test]
789+
fn test_ppdb_source_links() -> Result<(), Error> {
790+
let view = ByteView::open(fixture("ppdb-sourcelink-sample/ppdb-sourcelink-sample.pdb"))?;
791+
let object = Object::parse(&view)?;
792+
let session = object.debug_session()?;
793+
794+
let known_embedded_sources = vec![
795+
".NETStandard,Version=v2.0.AssemblyAttributes.cs",
796+
"ppdb-sourcelink-sample.AssemblyInfo.cs",
797+
];
798+
799+
// Testing this is simple because there's just one prefix rule in this PPDB.
800+
let src_prefix = "C:\\dev\\symbolic\\";
801+
let url_prefix = "https://raw.githubusercontent.com/getsentry/symbolic/9f7ceefc29da4c45bc802751916dbb3ea72bf08f/";
802+
803+
for file in session.files() {
804+
let file = file.unwrap();
805+
806+
match session.source_by_path(&file.path_str()).unwrap().unwrap() {
807+
SourceCode::Content(text) => {
808+
assert!(known_embedded_sources.contains(&file.name_str().as_ref()));
809+
assert!(!text.is_empty());
810+
}
811+
SourceCode::Url(url) => {
812+
// testing this is simple because there's just one prefix rule in this PPDB.
813+
let expected = file
814+
.path_str()
815+
.replace(src_prefix, url_prefix)
816+
.replace('\\', "/");
817+
assert_eq!(url, expected);
818+
}
819+
_ => panic!(),
820+
}
781821
}
782822

823+
assert!(session.source_by_path("c:/non/existent/path.cs")?.is_none());
783824
Ok(())
784825
}
785826

symbolic-ppdb/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ watto = { version = "0.1.0", features = ["writer", "strings"] }
2626
thiserror = "1.0.31"
2727
uuid = "1.0.0"
2828
flate2 = { version ="1.0.13", default-features = false, features = [ "rust_backend" ] }
29+
serde_json = { version = "1.0.40" }
2930

3031
[dev-dependencies]
3132
symbolic-debuginfo = { path = "../symbolic-debuginfo" }

0 commit comments

Comments
 (0)