Skip to content

Commit

Permalink
feat(proguard): Implement exception remapping (#1378)
Browse files Browse the repository at this point in the history
  • Loading branch information
loewenheim authored Feb 27, 2024
1 parent b2ed43e commit c5d7ecc
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 15 deletions.
20 changes: 14 additions & 6 deletions crates/symbolicator-proguard/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub struct SymbolicateJvmStacktraces {
pub sources: Arc<[SourceConfig]>,
/// The exceptions to symbolicate/remap.
pub exceptions: Vec<JvmException>,
/// The list of stacktraces to symbolicate/remap.
pub stacktraces: Vec<JvmStacktrace>,
/// A list of proguard files to use for remapping.
pub modules: Vec<JvmModule>,
/// Whether to apply source context for the stack frames.
Expand Down Expand Up @@ -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<JvmFrame>,
pub frames: Vec<JvmFrame>,
}

/// A JVM module (proguard file).
Expand All @@ -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<JvmException>,
}
1 change: 1 addition & 0 deletions crates/symbolicator-proguard/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod interface;
mod service;
mod symbolication;

pub use service::ProguardService;
28 changes: 19 additions & 9 deletions crates/symbolicator-proguard/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ pub struct ProguardMapper {
inner: Arc<SelfCell<ByteView<'static>, 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,
Expand Down Expand Up @@ -133,15 +151,7 @@ impl CacheItemRequest for FetchProguard {
}

fn load(&self, byteview: ByteView<'static>) -> CacheEntry<Self::Item> {
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 {
Expand Down
100 changes: 100 additions & 0 deletions crates/symbolicator-proguard/src/symbolication.rs
Original file line number Diff line number Diff line change
@@ -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<JvmException> {
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 <init>() -> <init>
67:67:java.lang.Class[] getClassContext() -> a
69:69:java.lang.Class[] getExtraClassContext() -> a
68:68:java.lang.Class[] getContext() -> a
65:65:void <init>(org.slf4j.helpers.Util$1) -> <init>
org.slf4j.helpers.Util$ClassContext -> org.a.b.g$b:
65:65:void <init>() -> <init>
";

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");
}
}
56 changes: 56 additions & 0 deletions crates/symbolicator-proguard/tests/integration/proguard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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]);
}
8 changes: 8 additions & 0 deletions tests/fixtures/proguard/02/proguard.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
org.slf4j.helpers.Util$ClassContextSecurityManager -> org.a.b.g$a:
65:65:void <init>() -> <init>
67:67:java.lang.Class[] getClassContext() -> a
69:69:java.lang.Class[] getExtraClassContext() -> a
68:68:java.lang.Class[] getContext() -> a
65:65:void <init>(org.slf4j.helpers.Util$1) -> <init>
org.slf4j.helpers.Util$ClassContext -> org.a.b.g$b:
65:65:void <init>() -> <init>

0 comments on commit c5d7ecc

Please sign in to comment.