use alloc::borrow::Cow;
use alloc::string::String;
use core::iter;
use crate::{maybe_small, Error, Function, InlinedFunction, ResUnit};
pub struct Location<'a> {
pub file: Option<&'a str>,
pub line: Option<u32>,
pub column: Option<u32>,
}
pub struct Frame<'ctx, R: gimli::Reader> {
pub dw_die_offset: Option<gimli::UnitOffset<R::Offset>>,
pub function: Option<FunctionName<R>>,
pub location: Option<Location<'ctx>>,
}
pub struct FrameIter<'ctx, R>(FrameIterState<'ctx, R>)
where
R: gimli::Reader;
enum FrameIterState<'ctx, R>
where
R: gimli::Reader,
{
Empty,
Location(Option<Location<'ctx>>),
Frames(FrameIterFrames<'ctx, R>),
}
struct FrameIterFrames<'ctx, R>
where
R: gimli::Reader,
{
unit: &'ctx ResUnit<R>,
sections: &'ctx gimli::Dwarf<R>,
function: &'ctx Function<R>,
inlined_functions: iter::Rev<maybe_small::IntoIter<&'ctx InlinedFunction<R>>>,
next: Option<Location<'ctx>>,
}
impl<'ctx, R> FrameIter<'ctx, R>
where
R: gimli::Reader + 'ctx,
{
pub(crate) fn new_empty() -> Self {
FrameIter(FrameIterState::Empty)
}
pub(crate) fn new_location(location: Location<'ctx>) -> Self {
FrameIter(FrameIterState::Location(Some(location)))
}
pub(crate) fn new_frames(
unit: &'ctx ResUnit<R>,
sections: &'ctx gimli::Dwarf<R>,
function: &'ctx Function<R>,
inlined_functions: maybe_small::Vec<&'ctx InlinedFunction<R>>,
location: Option<Location<'ctx>>,
) -> Self {
FrameIter(FrameIterState::Frames(FrameIterFrames {
unit,
sections,
function,
inlined_functions: inlined_functions.into_iter().rev(),
next: location,
}))
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<Frame<'ctx, R>>, Error> {
let frames = match &mut self.0 {
FrameIterState::Empty => return Ok(None),
FrameIterState::Location(location) => {
let location = location.take();
self.0 = FrameIterState::Empty;
return Ok(Some(Frame {
dw_die_offset: None,
function: None,
location,
}));
}
FrameIterState::Frames(frames) => frames,
};
let loc = frames.next.take();
let func = match frames.inlined_functions.next() {
Some(func) => func,
None => {
let frame = Frame {
dw_die_offset: Some(frames.function.dw_die_offset),
function: frames.function.name.clone().map(|name| FunctionName {
name,
language: frames.unit.lang,
}),
location: loc,
};
self.0 = FrameIterState::Empty;
return Ok(Some(frame));
}
};
let mut next = Location {
file: None,
line: if func.call_line != 0 {
Some(func.call_line)
} else {
None
},
column: if func.call_column != 0 {
Some(func.call_column)
} else {
None
},
};
if let Some(call_file) = func.call_file {
if let Some(lines) = frames.unit.parse_lines(frames.sections)? {
next.file = lines.file(call_file);
}
}
frames.next = Some(next);
Ok(Some(Frame {
dw_die_offset: Some(func.dw_die_offset),
function: func.name.clone().map(|name| FunctionName {
name,
language: frames.unit.lang,
}),
location: loc,
}))
}
}
#[cfg(feature = "fallible-iterator")]
impl<'ctx, R> fallible_iterator::FallibleIterator for FrameIter<'ctx, R>
where
R: gimli::Reader + 'ctx,
{
type Item = Frame<'ctx, R>;
type Error = Error;
#[inline]
fn next(&mut self) -> Result<Option<Frame<'ctx, R>>, Error> {
self.next()
}
}
pub struct FunctionName<R: gimli::Reader> {
pub name: R,
pub language: Option<gimli::DwLang>,
}
impl<R: gimli::Reader> FunctionName<R> {
pub fn raw_name(&self) -> Result<Cow<'_, str>, Error> {
self.name.to_string_lossy()
}
pub fn demangle(&self) -> Result<Cow<'_, str>, Error> {
self.raw_name().map(|x| demangle_auto(x, self.language))
}
}
#[allow(unused_variables)]
pub fn demangle(name: &str, language: gimli::DwLang) -> Option<String> {
match language {
#[cfg(feature = "rustc-demangle")]
gimli::DW_LANG_Rust => rustc_demangle::try_demangle(name)
.ok()
.as_ref()
.map(|x| format!("{:#}", x)),
#[cfg(feature = "cpp_demangle")]
gimli::DW_LANG_C_plus_plus
| gimli::DW_LANG_C_plus_plus_03
| gimli::DW_LANG_C_plus_plus_11
| gimli::DW_LANG_C_plus_plus_14 => cpp_demangle::Symbol::new(name)
.ok()
.and_then(|x| x.demangle(&Default::default()).ok()),
_ => None,
}
}
pub fn demangle_auto(name: Cow<'_, str>, language: Option<gimli::DwLang>) -> Cow<'_, str> {
match language {
Some(language) => demangle(name.as_ref(), language),
None => demangle(name.as_ref(), gimli::DW_LANG_Rust)
.or_else(|| demangle(name.as_ref(), gimli::DW_LANG_C_plus_plus)),
}
.map(Cow::from)
.unwrap_or(name)
}