use std::path::Path;
use super::images::{find_module, LoadedModule};
use super::payload::Frame;
pub fn build_frames(bt: &backtrace::Backtrace, modules: &[LoadedModule]) -> Vec<Frame> {
let mut frames = Vec::new();
for bt_frame in bt.frames() {
let ip = bt_frame.ip() as usize as u64;
let module = find_module(modules, ip);
let symbols = bt_frame.symbols();
for symbol in symbols {
frames.push(frame_from_symbol(ip, module, symbol));
}
if symbols.is_empty() {
let mut frame = Frame::address_only(
Some(format!("0x{ip:x}")),
module.map(|m| m.image.image_addr.clone()),
);
frame.module = module_name(module);
frames.push(frame);
}
}
frames
}
fn module_name(module: Option<&LoadedModule>) -> Option<String> {
module
.and_then(|m| m.image.code_file.as_deref())
.map(Path::new)
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned())
}
fn is_in_app(filename: Option<&Path>) -> bool {
let Some(path) = filename else {
return false;
};
let normalized = path.to_string_lossy().replace('\\', "/");
const NOT_OURS: &[&str] = &[
"/rustc/",
"cargo/registry/",
"cargo/git/",
"/library/std/",
"/library/core/",
"/library/alloc/",
];
if NOT_OURS.iter().any(|marker| normalized.contains(marker)) {
return false;
}
normalized.starts_with("src/") || normalized.contains("/src/")
}
fn frame_from_symbol(
ip: u64,
module: Option<&LoadedModule>,
symbol: &backtrace::BacktraceSymbol,
) -> Frame {
let filename = symbol.filename();
Frame {
platform: "native",
instruction_addr: Some(format!("0x{ip:x}")),
image_addr: module.map(|m| m.image.image_addr.clone()),
lang: "rust",
module: module_name(module),
function: symbol
.name()
.map(|n| n.to_string())
.filter(|n| !n.is_empty()),
filename: filename.map(|p| p.to_string_lossy().into_owned()),
lineno: symbol.lineno(),
colno: symbol.colno(),
client_resolved: true,
in_app: is_in_app(filename),
synthetic: false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn in_app(p: &str) -> bool {
is_in_app(Some(&PathBuf::from(p)))
}
#[test]
fn our_own_source_paths_are_in_app() {
assert!(in_app("src/core/telemetry/crash/mod.rs"));
assert!(in_app("src\\core\\telemetry\\crash\\mod.rs"));
assert!(in_app("/home/dev/openlatch-client/src/daemon/mod.rs"));
assert!(in_app("D:/GITOSIS/openlatch-client/src/daemon/mod.rs"));
}
#[test]
fn the_standard_library_is_never_in_app() {
assert!(!in_app(
"/rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/panicking.rs"
));
assert!(!in_app("/library/std/src/thread/mod.rs"));
}
#[test]
fn dependencies_are_never_in_app() {
assert!(!in_app(
"/home/dev/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.0/src/lib.rs"
));
assert!(!in_app(
"/home/dev/.cargo/git/checkouts/axum-abc/src/lib.rs"
));
assert!(!in_app(
"C:\\Users\\dev\\.cargo\\registry\\src\\index\\serde-1\\src\\lib.rs"
));
}
#[test]
fn a_frame_with_no_filename_is_never_in_app() {
assert!(!is_in_app(None));
}
#[test]
fn an_address_only_frame_is_not_in_app_and_not_client_resolved() {
let frame = Frame::address_only(Some("0x1000".into()), None);
assert!(!frame.in_app);
assert!(!frame.client_resolved);
}
#[test]
fn every_built_frame_serializes_in_app() {
let bt = backtrace::Backtrace::new();
let modules = super::super::images::collect_loaded_modules();
let frames = build_frames(&bt, &modules);
assert!(!frames.is_empty(), "a live backtrace has frames");
for frame in &frames {
let v = serde_json::to_value(frame).expect("frame serializes");
let obj = v.as_object().expect("frame is an object");
assert!(obj.contains_key("in_app"), "in_app missing: {v}");
assert_eq!(obj["platform"], "native");
assert_eq!(obj["lang"], "rust");
}
}
#[test]
fn module_name_is_the_base_name_not_the_path() {
let module = LoadedModule {
base: 0x1000,
end: 0x2000,
image: super::super::payload::DebugImage {
debug_id: "aaa".into(),
image_addr: "0x1000".into(),
image_size: Some(0x1000),
image_vmaddr: Some("0x0".into()),
code_file: Some("/home/alice/.openlatch/bin/openlatch".into()),
image_type: Some("elf".into()),
arch: Some("x86_64".into()),
},
reportable: true,
};
assert_eq!(module_name(Some(&module)).as_deref(), Some("openlatch"));
assert_eq!(module_name(None), None);
}
}