diff --git a/crates/symbolicator-proguard/src/interface.rs b/crates/symbolicator-proguard/src/interface.rs index 490540e06..a74de4645 100644 --- a/crates/symbolicator-proguard/src/interface.rs +++ b/crates/symbolicator-proguard/src/interface.rs @@ -14,6 +14,8 @@ pub struct SymbolicateJvmStacktraces { pub sources: Arc<[SourceConfig]>, /// The exceptions to symbolicate/remap. pub exceptions: Vec, + /// The list of stacktraces to symbolicate/remap. + pub stacktraces: Vec, /// A list of proguard files to use for remapping. pub modules: Vec, /// Whether to apply source context for the stack frames. @@ -63,18 +65,16 @@ pub struct JvmFrame { pub struct JvmException { /// The type (class name) of the exception. #[serde(rename = "type")] - ty: String, + pub ty: String, /// The module in which the exception is defined. - module: String, - /// The stacktrace associated with the exception. - stacktrace: JvmStacktrace, + pub module: String, } /// A JVM stacktrace. #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct JvmStacktrace { /// The stacktrace's frames. - frames: Vec, + pub frames: Vec, } /// A JVM module (proguard file). @@ -83,5 +83,13 @@ pub struct JvmModule { /// The file's UUID. /// /// This is used to download the file from symbol sources. - uuid: DebugId, + pub uuid: DebugId, +} + +// TODO: Expand this +/// The symbolicated/remapped event data. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct CompletedJvmSymbolicationResponse { + /// The exceptions after remapping. + pub exceptions: Vec, } diff --git a/crates/symbolicator-proguard/src/lib.rs b/crates/symbolicator-proguard/src/lib.rs index 4e12db88c..e166a8893 100644 --- a/crates/symbolicator-proguard/src/lib.rs +++ b/crates/symbolicator-proguard/src/lib.rs @@ -1,4 +1,5 @@ pub mod interface; mod service; +mod symbolication; pub use service::ProguardService; diff --git a/crates/symbolicator-proguard/src/service.rs b/crates/symbolicator-proguard/src/service.rs index 3413bd147..970377a6e 100644 --- a/crates/symbolicator-proguard/src/service.rs +++ b/crates/symbolicator-proguard/src/service.rs @@ -103,6 +103,24 @@ pub struct ProguardMapper { inner: Arc, ProguardInner<'static>>>, } +impl ProguardMapper { + pub fn new(byteview: ByteView<'static>) -> Self { + let inner = SelfCell::new(byteview, |data| { + let mapping = proguard::ProguardMapping::new(unsafe { &*data }); + let mapper = proguard::ProguardMapper::new(mapping.clone()); + ProguardInner { mapping, mapper } + }); + + Self { + inner: Arc::new(inner), + } + } + + pub fn get(&self) -> &proguard::ProguardMapper { + &self.inner.get().mapper + } +} + #[derive(Clone, Debug)] pub struct FetchProguard { file: RemoteFile, @@ -133,15 +151,7 @@ impl CacheItemRequest for FetchProguard { } fn load(&self, byteview: ByteView<'static>) -> CacheEntry { - let inner = SelfCell::new(byteview, |data| { - let mapping = proguard::ProguardMapping::new(unsafe { &*data }); - let mapper = proguard::ProguardMapper::new(mapping.clone()); - ProguardInner { mapping, mapper } - }); - - Ok(ProguardMapper { - inner: Arc::new(inner), - }) + Ok(Self::Item::new(byteview)) } fn use_shared_cache(&self) -> bool { diff --git a/crates/symbolicator-proguard/src/symbolication.rs b/crates/symbolicator-proguard/src/symbolication.rs new file mode 100644 index 000000000..05c985370 --- /dev/null +++ b/crates/symbolicator-proguard/src/symbolication.rs @@ -0,0 +1,100 @@ +use crate::interface::{ + CompletedJvmSymbolicationResponse, JvmException, SymbolicateJvmStacktraces, +}; +use crate::ProguardService; + +use futures::future; + +impl ProguardService { + pub async fn symbolicate_jvm( + &self, + request: SymbolicateJvmStacktraces, + ) -> CompletedJvmSymbolicationResponse { + let SymbolicateJvmStacktraces { + scope, + sources, + exceptions, + modules, + .. + } = request; + let mappers = future::join_all( + modules + .iter() + .map(|module| self.download_proguard_file(&sources, &scope, module.uuid)), + ) + .await; + + // TODO: error handling/reporting + let mappers: Vec<_> = mappers + .iter() + .filter_map(|res| match res { + Ok(mapper) => Some(mapper.get()), + Err(_e) => None, + }) + .collect(); + + let mut remapped_exceptions = Vec::with_capacity(exceptions.len()); + + for raw_exception in exceptions { + remapped_exceptions + .push(Self::map_exception(&mappers, &raw_exception).unwrap_or(raw_exception)); + } + + CompletedJvmSymbolicationResponse { + exceptions: remapped_exceptions, + } + } + + fn map_exception( + mappers: &[&proguard::ProguardMapper], + exception: &JvmException, + ) -> Option { + if mappers.is_empty() { + return None; + } + + let key = format!("{}.{}", exception.module, exception.ty); + + let mapped = mappers.iter().find_map(|mapper| mapper.remap_class(&key))?; + + // TOOD: Capture/log error + let (new_module, new_ty) = mapped.rsplit_once('.')?; + + Some(JvmException { + ty: new_ty.into(), + module: new_module.into(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proguard::{ProguardMapper, ProguardMapping}; + + #[test] + fn remap_exception_simple() { + let proguard_source = b"org.slf4j.helpers.Util$ClassContextSecurityManager -> org.a.b.g$a: + 65:65:void () -> + 67:67:java.lang.Class[] getClassContext() -> a + 69:69:java.lang.Class[] getExtraClassContext() -> a + 68:68:java.lang.Class[] getContext() -> a + 65:65:void (org.slf4j.helpers.Util$1) -> +org.slf4j.helpers.Util$ClassContext -> org.a.b.g$b: + 65:65:void () -> +"; + + let exception = JvmException { + ty: "g$a".into(), + module: "org.a.b".into(), + }; + + let mapping = ProguardMapping::new(proguard_source); + let mapper = ProguardMapper::new(mapping); + + let exception = ProguardService::map_exception(&[&mapper], &exception).unwrap(); + + assert_eq!(exception.ty, "Util$ClassContextSecurityManager"); + assert_eq!(exception.module, "org.slf4j.helpers"); + } +} diff --git a/crates/symbolicator-proguard/tests/integration/proguard.rs b/crates/symbolicator-proguard/tests/integration/proguard.rs index b2aceeac3..eb9f73f97 100644 --- a/crates/symbolicator-proguard/tests/integration/proguard.rs +++ b/crates/symbolicator-proguard/tests/integration/proguard.rs @@ -3,6 +3,9 @@ use std::{collections::HashMap, str::FromStr}; use serde_json::json; use symbolic::common::DebugId; +use symbolicator_proguard::interface::{ + CompletedJvmSymbolicationResponse, JvmException, JvmModule, SymbolicateJvmStacktraces, +}; use symbolicator_service::types::Scope; use symbolicator_sources::{SentrySourceConfig, SourceConfig}; @@ -52,3 +55,56 @@ async fn test_download_proguard_file() { .await .is_ok()); } + +#[tokio::test] +async fn test_remap_exception() { + symbolicator_test::setup(); + let (symbolication, _cache_dir) = setup_service(|_| ()); + let (_srv, source) = proguard_server("02", |_url, _query| { + json!([{ + "id":"proguard.txt", + "uuid":"246fb328-fc4e-406a-87ff-fc35f6149d8f", + "debugId":"246fb328-fc4e-406a-87ff-fc35f6149d8f", + "codeId":null, + "cpuName":"any", + "objectName":"proguard-mapping", + "symbolType":"proguard", + "headers": { + "Content-Type":"text/x-proguard+plain" + }, + "size":3619, + "sha1":"deba83e73fd18210a830db372a0e0a2f2293a989", + "dateCreated":"2024-02-14T10:49:38.770116Z", + "data":{ + "features":["mapping"] + } + }]) + }); + + let source = SourceConfig::Sentry(Arc::new(source)); + let debug_id = DebugId::from_str("246fb328-fc4e-406a-87ff-fc35f6149d8f").unwrap(); + + let exception = JvmException { + ty: "g$a".into(), + module: "org.a.b".into(), + }; + + let request = SymbolicateJvmStacktraces { + scope: Scope::Global, + sources: Arc::new([source]), + exceptions: vec![exception.clone()], + stacktraces: vec![], + modules: vec![JvmModule { uuid: debug_id }], + apply_source_context: false, + }; + + let CompletedJvmSymbolicationResponse { exceptions } = + symbolication.symbolicate_jvm(request).await; + + let remapped_exception = JvmException { + ty: "Util$ClassContextSecurityManager".into(), + module: "org.slf4j.helpers".into(), + }; + + assert_eq!(exceptions, [remapped_exception]); +} diff --git a/tests/fixtures/proguard/02/proguard.txt b/tests/fixtures/proguard/02/proguard.txt new file mode 100644 index 000000000..564abbf65 --- /dev/null +++ b/tests/fixtures/proguard/02/proguard.txt @@ -0,0 +1,8 @@ +org.slf4j.helpers.Util$ClassContextSecurityManager -> org.a.b.g$a: + 65:65:void () -> + 67:67:java.lang.Class[] getClassContext() -> a + 69:69:java.lang.Class[] getExtraClassContext() -> a + 68:68:java.lang.Class[] getContext() -> a + 65:65:void (org.slf4j.helpers.Util$1) -> +org.slf4j.helpers.Util$ClassContext -> org.a.b.g$b: + 65:65:void () ->