use std::rc::Rc;
use bitflags::bitflags;
#[must_use]
pub fn is_python_runtime_basename(name: &str) -> bool {
crate::is_python_module(name)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FrameKind {
Python,
Native,
Kernel,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SymbolOrigin {
Elf,
PerfMap,
KernelSymbols,
AddressOnly,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FrameFlags: u32 {
const PYTHON_RUNTIME = 1 << 0;
const HIDDEN_DEFAULT = 1 << 2;
const JIT = 1 << 3;
const TRUNCATED_STACK = 1 << 4;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LocationInfo {
pub lineno: i32,
pub end_lineno: i32,
pub column: i32,
pub end_column: i32,
}
impl Default for LocationInfo {
fn default() -> Self {
const UNKNOWN: i32 = -1;
Self {
lineno: UNKNOWN,
end_lineno: UNKNOWN,
column: UNKNOWN,
end_column: UNKNOWN,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PythonFrame {
file_name: Rc<str>,
pub location: LocationInfo,
pub func_name: Rc<str>,
pub opcode: Option<u8>,
pub is_entry: bool,
pub flags: FrameFlags,
basename_start: usize,
}
impl PythonFrame {
#[must_use]
pub fn new(
file_name: impl Into<Rc<str>>,
location: LocationInfo,
func_name: impl Into<Rc<str>>,
opcode: Option<u8>,
is_entry: bool,
) -> Self {
let file_name = file_name.into();
let basename_start = basename_start(&file_name);
Self {
file_name,
location,
func_name: func_name.into(),
opcode,
is_entry,
flags: FrameFlags::empty(),
basename_start,
}
}
#[must_use]
pub fn file_name(&self) -> &str {
&self.file_name
}
#[must_use]
pub fn file_name_rc(&self) -> &Rc<str> {
&self.file_name
}
#[must_use]
pub fn with_flags(mut self, flags: FrameFlags) -> Self {
self.flags = flags;
self
}
#[inline]
#[must_use]
pub fn basename(&self) -> &str {
&self.file_name[self.basename_start..]
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct SourceLocation {
pub file: Option<Rc<str>>,
pub line: Option<u32>,
pub column: Option<u32>,
pub function_start_line: Option<u32>,
pub function_start_column: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NativeSymbol {
name: Rc<str>,
pub source: SourceLocation,
pub module: Rc<str>,
pub offset: u64,
inline_depth: u32,
is_eval_frame: bool,
should_ignore: bool,
}
impl NativeSymbol {
#[must_use]
pub fn new(
name: impl Into<Rc<str>>,
source: SourceLocation,
module: impl Into<Rc<str>>,
offset: u64,
) -> Self {
let name = name.into();
let module = module.into();
Self {
is_eval_frame: crate::symbols::is_eval_frame(&name),
name,
source,
module,
offset,
inline_depth: 0,
should_ignore: false,
}
}
#[must_use]
pub fn hidden_by_default(mut self) -> Self {
self.should_ignore = true;
self
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn name_rc(&self) -> &Rc<str> {
&self.name
}
#[must_use]
pub const fn inline_depth(&self) -> u32 {
self.inline_depth
}
#[must_use]
pub const fn is_eval_frame(&self) -> bool {
self.is_eval_frame
}
#[must_use]
pub const fn should_ignore(&self) -> bool {
self.should_ignore
}
pub(crate) fn set_inline_depth(&mut self, depth: usize) {
self.inline_depth = u32::try_from(depth).unwrap_or(u32::MAX);
}
#[inline]
#[must_use]
pub fn module_basename(&self) -> &str {
&self.module[basename_start(&self.module)..]
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NativeFrame {
pub pc: u64,
pub symbol: Option<NativeSymbol>,
pub kind: FrameKind,
pub origin: SymbolOrigin,
pub flags: FrameFlags,
}
impl NativeFrame {
#[must_use]
pub fn from_address(pc: u64) -> Self {
Self {
pc,
symbol: None,
kind: FrameKind::Unknown,
origin: SymbolOrigin::AddressOnly,
flags: FrameFlags::empty(),
}
}
#[must_use]
pub fn truncated_stack_marker() -> Self {
Self {
pc: 0,
symbol: Some(NativeSymbol::new(
"<stack truncated>",
SourceLocation::default(),
"",
0,
)),
kind: FrameKind::Unknown,
origin: SymbolOrigin::AddressOnly,
flags: FrameFlags::TRUNCATED_STACK,
}
}
#[must_use]
pub fn name(&self) -> Option<&str> {
self.symbol.as_ref().map(NativeSymbol::name)
}
#[must_use]
pub fn is_python_runtime(&self) -> bool {
self.flags.contains(FrameFlags::PYTHON_RUNTIME)
}
#[must_use]
pub fn display_name(&self) -> String {
self.symbol.as_ref().map_or_else(
|| format!("<0x{:x}>", self.pc),
|symbol| symbol.name().to_owned(),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ResolvedFrame {
Python(PythonFrame),
Native(NativeFrame),
}
impl ResolvedFrame {
#[must_use]
pub fn name(&self) -> Option<&str> {
match self {
Self::Python(frame) => Some(&frame.func_name),
Self::Native(frame) => frame.name(),
}
}
#[must_use]
pub fn display_name(&self) -> String {
match self {
Self::Python(frame) => frame.func_name.to_string(),
Self::Native(frame) => frame.display_name(),
}
}
}
#[inline]
#[must_use]
pub(crate) fn basename_start(path: &str) -> usize {
memchr::memrchr(b'/', path.as_bytes()).map_or(0, |i| i + 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unknown_python_location_uses_documented_sentinel() {
assert_eq!(
LocationInfo::default(),
LocationInfo {
lineno: -1,
end_lineno: -1,
column: -1,
end_column: -1,
}
);
}
#[test]
fn python_frame_basename_handles_long_utf8_path() {
let path = format!("{}é/leaf.py", "a".repeat(65_534));
let frame = PythonFrame::new(path.as_str(), LocationInfo::default(), "f", None, false);
assert_eq!(frame.basename(), "leaf.py");
}
#[test]
fn native_symbol_basename_follows_mutated_module_path() {
let mut symbol = NativeSymbol::new("f", SourceLocation::default(), "/old/f.so", 0);
symbol.module = "new.so".into();
assert_eq!(symbol.module_basename(), "new.so");
}
}