diff --git a/crates/symbolicator-native/src/interface.rs b/crates/symbolicator-native/src/interface/mod.rs similarity index 99% rename from crates/symbolicator-native/src/interface.rs rename to crates/symbolicator-native/src/interface/mod.rs index 62efe6e4f..7b5585712 100644 --- a/crates/symbolicator-native/src/interface.rs +++ b/crates/symbolicator-native/src/interface/mod.rs @@ -18,9 +18,13 @@ use symbolicator_sources::SourceConfig; use thiserror::Error; use url::Url; +pub(crate) use self::variables::Value as VariableValue; +pub use self::variables::{Variable, VariableKind, Variables}; use crate::memory::MemoryAccess; pub use crate::metrics::StacktraceOrigin; +mod variables; + #[derive(Debug, Clone)] /// A request for symbolication of multiple stack traces. pub struct SymbolicateStacktraces { @@ -356,8 +360,8 @@ pub struct RawFrame { /// Mapping of local variables and expression names that were available in this frame. /// /// Current format is heavily work in progress. - #[serde(skip_serializing_if = "Option::is_none")] - pub vars: Option>, + #[serde(default, skip_serializing_if = "Variables::is_empty")] + pub vars: Variables, /// Information about how the raw frame was created. #[serde(default, skip_serializing_if = "is_default_value")] diff --git a/crates/symbolicator-native/src/interface/variables.rs b/crates/symbolicator-native/src/interface/variables.rs new file mode 100644 index 000000000..f3e7ca698 --- /dev/null +++ b/crates/symbolicator-native/src/interface/variables.rs @@ -0,0 +1,94 @@ +//! Contains types for representing variable values. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use symbolic::debuginfo::VariableKind as SymbolicVariableKind; + +/// Variables keyed by their names. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Variables(BTreeMap); + +impl Variables { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl FromIterator<(S, Variable)> for Variables +where + S: Into, +{ + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Self(iter.into_iter().map(|(k, v)| (k.into(), v)).collect()) + } +} + +/// A variable extracted from a memory dump. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Variable { + kind: VariableKind, + #[serde(rename = "type")] + ty: String, + #[serde(flatten)] + value: Value, +} + +/// The variable's kind (e.g. local or parameter). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum VariableKind { + Local, + Parameter, +} + +/// A value of a variable or field within a variable (e.g. list item, pointee, struct field). +/// +/// This struct allows for multiple co-existing representations. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Value { + /// A fully-formatted, source-specific representation of a variable value. + /// + /// This should typically be used for primitive types like integers, booleans, chars. + value: Option, +} + +impl Value { + /// Create a new empty [`Value`]. + pub fn new() -> Self { + Self::default() + } + + /// Provide a fully-formatted, source-specific representation of the [`Value`]. + /// + /// This should typically be used for primitive types like integers, booleans, and chars. + pub fn value(self, value: Option) -> Self { + Self { value } + } + + /// Convert this [`Value`] into a [`Variable`] with the given type and kind. + pub fn into_variable(self, ty: T, kind: K) -> Variable + where + T: Into, + K: Into, + { + Variable { + kind: kind.into(), + ty: ty.into(), + value: self, + } + } +} + +impl From for VariableKind { + fn from(value: SymbolicVariableKind) -> Self { + match value { + SymbolicVariableKind::Parameter => VariableKind::Parameter, + SymbolicVariableKind::Local => VariableKind::Local, + } + } +} diff --git a/crates/symbolicator-native/src/symbolication/native.rs b/crates/symbolicator-native/src/symbolication/native.rs index d180dabb0..4d180b172 100644 --- a/crates/symbolicator-native/src/symbolication/native.rs +++ b/crates/symbolicator-native/src/symbolication/native.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use minidump::CpuContext; use symbolic::common::{CpuFamily, InstructionInfo, Language, split_path}; use symbolic::symcache::{ @@ -10,6 +8,7 @@ use symbolicator_service::utils::hex::HexValue; use crate::interface::{ AdjustInstructionAddr, FrameStatus, RawFrame, Registers, Signal, SymbolicatedFrame, + VariableValue, Variables, }; use crate::memory::{MemoryAccess, MemoryAccessExt}; @@ -69,10 +68,9 @@ pub fn symbolicate_native_frame( frame.filename.clone() }; - let mut vars = None; - if let Some(memory) = memory { - vars = do_extract_variables(&source_location, symcache, &frame.registers, memory); - } + let vars = memory.map_or_default(|memory| { + do_extract_variables(&source_location, symcache, &frame.registers, memory) + }); rv.push(SymbolicatedFrame { status: FrameStatus::Symbolicated, @@ -185,35 +183,26 @@ fn do_extract_variables<'data, 'cache>( cache: &SymCache<'cache>, registers: &Registers, memory: &dyn MemoryAccess, -) -> Option> { - let mut result = BTreeMap::new(); - - for variable in source_location.variables() { - let Some(name) = variable.name() else { - continue; - }; - - let mut ty = String::new(); - resolve_type_name(&mut ty, cache, variable.ty(), 0); - - let value = variable - .locations() - .find_map(|loc| resolve_variable_value(cache, registers, memory, loc, variable.ty())); - - // This doesn't handle name collisions currently. - result.insert( - name.to_owned(), - match value { - Some(value) => format!("{value} ({ty})").into(), - None => ty.into(), - }, - ); - } - - match result.is_empty() { - false => Some(result), - true => None, - } +) -> Variables { + source_location + .variables() + .flat_map(|variable| { + let name = variable.name()?; + + let mut ty = String::new(); + resolve_type_name(&mut ty, cache, variable.ty(), 0); + + let value = variable.locations().find_map(|loc| { + resolve_variable_value(cache, registers, memory, loc, variable.ty()) + }); + + let interface_variable = VariableValue::new() + .value(value) + .into_variable(ty, variable.kind()); + + Some((name, interface_variable)) + }) + .collect() } fn resolve_variable_value( diff --git a/crates/symbolicli/src/event.rs b/crates/symbolicli/src/event.rs index 5f4cd00c6..84479c865 100644 --- a/crates/symbolicli/src/event.rs +++ b/crates/symbolicli/src/event.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::sync::Arc; use anyhow::bail; @@ -9,7 +8,7 @@ use symbolicator_js::interface::{ }; use symbolicator_native::interface::{ AddrMode, CompleteObjectInfo, FrameTrust, RawFrame, RawStacktrace, Signal, StacktraceOrigin, - SymbolicateStacktraces, + SymbolicateStacktraces, Variables, }; use symbolicator_service::types::{FrameOrder, Platform, RawObjectInfo, Scope, ScrapingConfig}; use symbolicator_service::utils::hex::HexValue; @@ -268,7 +267,8 @@ struct Frame { post_context: Vec, module: Option, source_link: Option, - vars: Option>, + #[serde(default)] + vars: Variables, #[serde(default)] trust: FrameTrust, #[serde(default)]