From 35c987885e7b923745c5d47151bc4b1ff700e233 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 12 Dec 2025 12:45:02 +0100 Subject: [PATCH 01/11] feat(proguard): Support rewriteFrame annotation --- crates/symbolicator-proguard/src/interface.rs | 2 + .../src/symbolication.rs | 116 +++++++++++++----- .../src/caches/versions.rs | 2 +- 3 files changed, 91 insertions(+), 29 deletions(-) diff --git a/crates/symbolicator-proguard/src/interface.rs b/crates/symbolicator-proguard/src/interface.rs index f9f1b0309..28945dfa9 100644 --- a/crates/symbolicator-proguard/src/interface.rs +++ b/crates/symbolicator-proguard/src/interface.rs @@ -128,6 +128,8 @@ pub struct JvmException { /// A JVM stacktrace. #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct JvmStacktrace { + /// The exception that caused the stacktrace. + pub exception: Option, /// The stacktrace's frames. pub frames: Vec, } diff --git a/crates/symbolicator-proguard/src/symbolication.rs b/crates/symbolicator-proguard/src/symbolication.rs index 3c45b2074..0d7e6a3a9 100644 --- a/crates/symbolicator-proguard/src/symbolication.rs +++ b/crates/symbolicator-proguard/src/symbolication.rs @@ -14,6 +14,16 @@ use symbolicator_service::caching::CacheError; use symbolicator_service::source_context::get_context_lines; use symbolicator_service::types::FrameOrder; +/// Result of attempting a full frame remap with rewrite rules. +enum FullRemapResult { + /// Outline frame or rewrite rules cleared all frames - skip this frame entirely. + Skip, + /// Successfully remapped to these frames. + Frames(Vec), + /// No mappings found - try fallback methods. + NoFrames, +} + impl ProguardService { /// Symbolicates a JVM event. /// @@ -145,10 +155,16 @@ impl ProguardService { let mut remapped_stacktraces: Vec<_> = stacktraces .into_iter() .map(|raw_stacktrace| { + let exception = raw_stacktrace + .exception + .as_ref() + .map(|exc| Self::map_exception(&mappers, exc).unwrap_or_else(|| exc.clone())); + let mut frames = Self::map_stacktrace( &mappers, &raw_stacktrace.frames, release_package.as_deref(), + exception, &mut stats, ); @@ -157,7 +173,7 @@ impl ProguardService { frames.reverse(); } - JvmStacktrace { frames } + JvmStacktrace { exception, frames } }) .collect(); @@ -202,11 +218,22 @@ impl ProguardService { mappers: &[&proguard::ProguardCache], stacktrace: &[JvmFrame], release_package: Option<&str>, + exception: Option<&JvmException>, stats: &mut SymbolicationStats, ) -> Vec { let mut carried_outline_pos = vec![None; mappers.len()]; let mut remapped_frames = Vec::new(); + // Compute exception descriptor for rewrite rules + let exception_descriptor = exception.map(|exc| { + let full_class = format!("{}.{}", exc.module, exc.ty); + proguard::class_name_to_descriptor(&full_class) + }); + + // Track whether the next frame can have rewrite rules applied + // (only the first non-outline frame after an exception) + let mut next_frame_can_rewrite = exception_descriptor.is_some(); + 'frames: for frame in stacktrace { let deobfuscated_signature = frame.signature.as_ref().and_then(|signature| { mappers @@ -253,22 +280,28 @@ impl ProguardService { let mut remap_buffer = Vec::new(); for (mapper_idx, mapper) in mappers.iter().enumerate() { - if mapper.is_outline_frame(proguard_frame.class(), proguard_frame.method()) { - carried_outline_pos[mapper_idx] = Some(proguard_frame.line()); - continue 'frames; - } - - let effective = mapper.prepare_frame_for_mapping( + // Try full remap with rewrite rules + match Self::map_full_frame( + mapper, &proguard_frame, + frame, + exception_descriptor.as_deref(), + next_frame_can_rewrite, &mut carried_outline_pos[mapper_idx], - ); - - // First, try to remap the whole frame. - if let Some(frames_out) = - Self::map_full_frame(mapper, frame, &effective, &mut remap_buffer) - { - mapped_result = Some(frames_out); - break; + &mut remap_buffer, + ) { + FullRemapResult::Skip => { + // Outline frame or rewrite rules cleared all frames + next_frame_can_rewrite = false; + continue 'frames; + } + FullRemapResult::Frames(frames) => { + mapped_result = Some(frames); + break; + } + FullRemapResult::NoFrames => { + // Try fallbacks + } } // Second, try to remap the frame's method. @@ -284,6 +317,9 @@ impl ProguardService { } } + // After processing the first non-outline frame, disable rewrite rules for subsequent frames + next_frame_can_rewrite = false; + // Fix up the frames' in-app fields only if they were actually mapped if let Some(frames) = mapped_result.as_mut() { for mapped_frame in frames { @@ -361,26 +397,39 @@ impl ProguardService { }) } - /// Tries to remap a `JvmFrame` using a `proguard::StackFrame` - /// constructed from it. - /// - /// The `buf` parameter is used as a buffer for the frames returned - /// by `remap_frame`. + /// Tries to fully remap a frame using the proguard cache, including rewrite rules. /// - /// This function returns a list of frames because one frame may be expanded into - /// a series of inlined frames. The returned list is sorted so that inlinees come before their callers. - #[tracing::instrument(skip_all)] + /// Uses `buf` as scratch space for intermediate proguard frames. fn map_full_frame<'a>( mapper: &'a proguard::ProguardCache<'a>, - original_frame: &JvmFrame, proguard_frame: &proguard::StackFrame<'a>, + original_frame: &JvmFrame, + exception_descriptor: Option<&str>, + apply_rewrite: bool, + carried_outline_pos: &mut Option, buf: &mut Vec>, - ) -> Option> { + ) -> FullRemapResult { buf.clear(); - buf.extend(mapper.remap_frame(proguard_frame)); + + let Some(iter) = mapper.remap_frame( + proguard_frame, + exception_descriptor, + apply_rewrite, + carried_outline_pos, + ) else { + // Outline frame + return FullRemapResult::Skip; + }; + + let had_mappings = iter.had_mappings(); + buf.extend(iter); + if had_mappings && buf.is_empty() { + // Rewrite rules cleared all frames + return FullRemapResult::Skip; + } if buf.is_empty() { - return None; + return FullRemapResult::NoFrames; } let res = buf @@ -401,7 +450,8 @@ impl ProguardService { ..original_frame.clone() }) .collect(); - Some(res) + + FullRemapResult::Frames(res) } /// Tries to remap a frame's class and method. @@ -511,6 +561,15 @@ mod tests { proguard_source: &[u8], release_package: Option<&str>, frames: &mut [JvmFrame], + ) -> Vec { + remap_stacktrace_caller_first_with_exception(proguard_source, release_package, None, frames) + } + + fn remap_stacktrace_caller_first_with_exception( + proguard_source: &[u8], + release_package: Option<&str>, + exception: Option<&JvmException>, + frames: &mut [JvmFrame], ) -> Vec { frames.reverse(); let mapping = ProguardMapping::new(proguard_source); @@ -523,6 +582,7 @@ mod tests { &[&cache], frames, release_package, + exception, &mut SymbolicationStats::default(), ); diff --git a/crates/symbolicator-service/src/caches/versions.rs b/crates/symbolicator-service/src/caches/versions.rs index 48dca4374..5d8bddb87 100644 --- a/crates/symbolicator-service/src/caches/versions.rs +++ b/crates/symbolicator-service/src/caches/versions.rs @@ -294,7 +294,7 @@ pub const PROGUARD_CACHE_VERSIONS: CacheVersions = CacheVersions { CacheVersion::new(4, CachePathFormat::V2), ], }; -static_assert!(proguard::PRGCACHE_VERSION == 3); +static_assert!(proguard::PRGCACHE_VERSION == 4); /// Symstore index cache, with the following versions: /// From b8d2361c1d5456c6adaa0aeaea7d57e46fef50f8 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 12 Dec 2025 12:49:00 +0100 Subject: [PATCH 02/11] doc --- .../symbolicator-proguard/src/symbolication.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/symbolicator-proguard/src/symbolication.rs b/crates/symbolicator-proguard/src/symbolication.rs index 0d7e6a3a9..2271d50d3 100644 --- a/crates/symbolicator-proguard/src/symbolication.rs +++ b/crates/symbolicator-proguard/src/symbolication.rs @@ -397,9 +397,22 @@ impl ProguardService { }) } - /// Tries to fully remap a frame using the proguard cache, including rewrite rules. + /// Tries to remap a `JvmFrame` using a `proguard::StackFrame` + /// constructed from it. /// - /// Uses `buf` as scratch space for intermediate proguard frames. + /// The `buf` parameter is used as a buffer for the frames returned + /// by `remap_frame`. + /// + /// The `exception_descriptor` parameter is used to apply rewrite rules + /// to the frame. + /// + /// The `apply_rewrite` parameter is used to determine whether to apply rewrite rules. + /// + /// The `carried_outline_pos` parameter is used to track the position of the + /// next frame in the outline. + /// + /// This function returns a list of frames because one frame may be expanded into + /// a series of inlined frames. The returned list is sorted so that inlinees come before their callers. fn map_full_frame<'a>( mapper: &'a proguard::ProguardCache<'a>, proguard_frame: &proguard::StackFrame<'a>, From 279e066ae914ad2e851cc785abf492c7ce1c3893 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 12 Dec 2025 12:59:29 +0100 Subject: [PATCH 03/11] Fix lint --- crates/symbolicator-proguard/src/symbolication.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/symbolicator-proguard/src/symbolication.rs b/crates/symbolicator-proguard/src/symbolication.rs index 2271d50d3..aca65a88a 100644 --- a/crates/symbolicator-proguard/src/symbolication.rs +++ b/crates/symbolicator-proguard/src/symbolication.rs @@ -164,7 +164,7 @@ impl ProguardService { &mappers, &raw_stacktrace.frames, release_package.as_deref(), - exception, + exception.as_ref(), &mut stats, ); From f59f60dafd3b75c4b2e3b25a1f98bf7279d6e240 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 18:31:55 +0100 Subject: [PATCH 04/11] Bump CacheVersion --- crates/symbolicator-service/src/caches/versions.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/symbolicator-service/src/caches/versions.rs b/crates/symbolicator-service/src/caches/versions.rs index 5d8bddb87..f6fa8eda9 100644 --- a/crates/symbolicator-service/src/caches/versions.rs +++ b/crates/symbolicator-service/src/caches/versions.rs @@ -273,6 +273,8 @@ pub const BUNDLE_INDEX_CACHE_VERSIONS: CacheVersions = CacheVersions { /// Proguard Cache, with the following versions: /// +/// - `6`: Add support for remapping with context. +/// /// - `5`: Information about whether a method is an outline/outlineCallsite is now part /// of the cache format. /// @@ -292,6 +294,7 @@ pub const PROGUARD_CACHE_VERSIONS: CacheVersions = CacheVersions { CacheVersion::new(2, CachePathFormat::V1), CacheVersion::new(3, CachePathFormat::V2), CacheVersion::new(4, CachePathFormat::V2), + CacheVersion::new(5, CachePathFormat::V2), ], }; static_assert!(proguard::PRGCACHE_VERSION == 4); From 2cab1cab9fadd826d09e31add0aab90646fbaed6 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 18:58:08 +0100 Subject: [PATCH 05/11] Bump CacheVersion --- crates/symbolicator-service/src/caches/versions.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/symbolicator-service/src/caches/versions.rs b/crates/symbolicator-service/src/caches/versions.rs index f6fa8eda9..df581158a 100644 --- a/crates/symbolicator-service/src/caches/versions.rs +++ b/crates/symbolicator-service/src/caches/versions.rs @@ -273,7 +273,8 @@ pub const BUNDLE_INDEX_CACHE_VERSIONS: CacheVersions = CacheVersions { /// Proguard Cache, with the following versions: /// -/// - `6`: Add support for remapping with context. +/// - `6`: Information about whether a method has rewrite rules is now part +/// of the cache format. /// /// - `5`: Information about whether a method is an outline/outlineCallsite is now part /// of the cache format. @@ -287,7 +288,7 @@ pub const BUNDLE_INDEX_CACHE_VERSIONS: CacheVersions = CacheVersions { /// /// - `1`: Initial version. pub const PROGUARD_CACHE_VERSIONS: CacheVersions = CacheVersions { - current: CacheVersion::new(5, CachePathFormat::V2), + current: CacheVersion::new(6, CachePathFormat::V2), fallbacks: &[], previous: &[ CacheVersion::new(1, CachePathFormat::V1), From cd7c8b40cc538928634e2a51c517b98998367556 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 18:59:21 +0100 Subject: [PATCH 06/11] Fix tests --- crates/symbolicator-proguard/tests/integration/proguard.rs | 5 ++++- .../integration__proguard__basic_source_lookup.snap | 5 ++++- .../snapshots/integration__proguard__remap_exception.snap | 5 ++++- .../snapshots/integration__proguard__resolving_inline.snap | 5 ++++- .../integration__proguard__source_lookup_with_proguard.snap | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/symbolicator-proguard/tests/integration/proguard.rs b/crates/symbolicator-proguard/tests/integration/proguard.rs index 266ab9f1b..bad13c6e6 100644 --- a/crates/symbolicator-proguard/tests/integration/proguard.rs +++ b/crates/symbolicator-proguard/tests/integration/proguard.rs @@ -22,7 +22,10 @@ fn make_jvm_request( let exceptions = vec![serde_json::from_str(exception).unwrap()]; let frames: Vec = serde_json::from_str(frames).unwrap(); let modules: Vec = serde_json::from_str(modules).unwrap(); - let stacktraces = vec![JvmStacktrace { frames }]; + let stacktraces = vec![JvmStacktrace { + frames, + exception: serde_json::from_str(exception).unwrap(), + }]; SymbolicateJvmStacktraces { platform: None, diff --git a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__basic_source_lookup.snap b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__basic_source_lookup.snap index 8cfd0b992..db152e893 100644 --- a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__basic_source_lookup.snap +++ b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__basic_source_lookup.snap @@ -6,7 +6,10 @@ exceptions: - type: RuntimeException module: io.sentry.samples stacktraces: - - frames: + - exception: + type: RuntimeException + module: io.sentry.samples + frames: - function: otherMethod filename: OtherActivity.java module: OtherActivity diff --git a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__remap_exception.snap b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__remap_exception.snap index 111e153dc..8d7de9b70 100644 --- a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__remap_exception.snap +++ b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__remap_exception.snap @@ -6,6 +6,9 @@ exceptions: - type: Util$ClassContextSecurityManager module: org.slf4j.helpers stacktraces: - - frames: [] + - exception: + type: Util$ClassContextSecurityManager + module: org.slf4j.helpers + frames: [] classes: {} errors: [] diff --git a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__resolving_inline.snap b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__resolving_inline.snap index 6d76ef6a2..ebd84f46c 100644 --- a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__resolving_inline.snap +++ b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__resolving_inline.snap @@ -6,7 +6,10 @@ exceptions: - type: g$a module: org.a.b stacktraces: - - frames: + - exception: + type: g$a + module: org.a.b + frames: - function: onClick module: io.sentry.sample.-$$Lambda$r3Avcbztes2hicEObh02jjhQqd4 lineno: 2 diff --git a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__source_lookup_with_proguard.snap b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__source_lookup_with_proguard.snap index b9a41e562..f1e908701 100644 --- a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__source_lookup_with_proguard.snap +++ b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__source_lookup_with_proguard.snap @@ -6,7 +6,10 @@ exceptions: - type: RuntimeException module: java.lang stacktraces: - - frames: + - exception: + type: RuntimeException + module: java.lang + frames: - function: main filename: ZygoteInit.java module: com.android.internal.os.ZygoteInit From d2b74d80a8566ecdecd35fd32918f50b175b8c9b Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 19:07:24 +0100 Subject: [PATCH 07/11] Bump rust-proguard version --- Cargo.lock | 65 +++++++++++-------- Cargo.toml | 2 +- .../src/symbolication.rs | 8 +-- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f8847eda..34c85b962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -988,7 +988,7 @@ dependencies = [ "minidump-common", "nom", "range-map", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", ] @@ -2241,7 +2241,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.16", + "thiserror 2.0.17", "tinyvec", "tokio", "tracing", @@ -2264,7 +2264,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -2856,7 +2856,7 @@ dependencies = [ "swc_common", "swc_ecma_parser", "swc_ecma_visit", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", ] @@ -3095,7 +3095,7 @@ dependencies = [ "prost", "range-map", "scroll 0.12.0", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", "tracing", "uuid", @@ -3132,7 +3132,7 @@ dependencies = [ "scroll 0.12.0", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "yaxpeax-x86", ] @@ -3529,7 +3529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.16", + "thiserror 2.0.17", "ucd-trie", ] @@ -3768,14 +3768,14 @@ dependencies = [ [[package]] name = "proguard" -version = "5.8.0" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260b8aaeeb4bc5175bb90a599f51d74a560f19de428c2d058c67eac92b5085d3" +checksum = "485ce6a0eaff8ca5566dde882ee2ef65dc25ba9f3ef1dba1129a8dd78a181952" dependencies = [ "serde", "serde_json", - "thiserror 1.0.69", - "watto", + "thiserror 2.0.17", + "watto 0.2.0", ] [[package]] @@ -3955,7 +3955,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -4472,7 +4472,7 @@ dependencies = [ "rand 0.9.2", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", "url", "uuid", @@ -4671,7 +4671,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", ] @@ -4788,7 +4788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cd3e3828fb4dd5ba0e7091777edb6c3db3cd2d6fc10547b29b40f6949a29be" dependencies = [ "memchr", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -5103,7 +5103,7 @@ dependencies = [ "symbolic-common", "thiserror 1.0.69", "uuid", - "watto", + "watto 0.1.0", ] [[package]] @@ -5118,7 +5118,7 @@ dependencies = [ "symbolic-common", "thiserror 1.0.69", "tracing", - "watto", + "watto 0.1.0", ] [[package]] @@ -5133,7 +5133,7 @@ dependencies = [ "symbolic-il2cpp", "thiserror 1.0.69", "tracing", - "watto", + "watto 0.1.0", ] [[package]] @@ -5164,7 +5164,7 @@ dependencies = [ "symbolicator-sources", "symbolicator-test", "tempfile", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-metrics", "tokio-util", @@ -5235,7 +5235,7 @@ dependencies = [ "symbolicator-test", "tempfile", "test-assembler", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5301,7 +5301,7 @@ dependencies = [ "symbolicator-sources", "symbolicator-test", "tempfile", - "thiserror 2.0.16", + "thiserror 2.0.17", "thread_local", "tokio", "tokio-util", @@ -5521,11 +5521,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -5541,9 +5541,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -6274,6 +6274,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "watto" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfbc1480663d640f8c9f7a1ac70922eea60ac16fea79df883177df3bc7bb8b49" +dependencies = [ + "hashbrown 0.15.5", + "leb128", + "thiserror 2.0.17", +] + [[package]] name = "web-sys" version = "0.3.78" @@ -6874,7 +6885,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 2.0.16", + "thiserror 2.0.17", "zopfli", ] diff --git a/Cargo.toml b/Cargo.toml index e61839a78..4226095d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ minidump-processor = "0.26.1" minidump-unwind = "0.26.1" moka = { version = "0.12.8", features = ["future", "sync"] } prettytable-rs = "0.10.0" -proguard = "5.8.0" +proguard = "5.9.0" rand = "0.9.0" rayon = "1.10.0" regex = "1.5.5" diff --git a/crates/symbolicator-proguard/src/symbolication.rs b/crates/symbolicator-proguard/src/symbolication.rs index aca65a88a..404634f24 100644 --- a/crates/symbolicator-proguard/src/symbolication.rs +++ b/crates/symbolicator-proguard/src/symbolication.rs @@ -401,7 +401,7 @@ impl ProguardService { /// constructed from it. /// /// The `buf` parameter is used as a buffer for the frames returned - /// by `remap_frame`. + /// by `remap_frame_with_context`. /// /// The `exception_descriptor` parameter is used to apply rewrite rules /// to the frame. @@ -424,7 +424,7 @@ impl ProguardService { ) -> FullRemapResult { buf.clear(); - let Some(iter) = mapper.remap_frame( + let Some(iter) = mapper.remap_frame_with_context( proguard_frame, exception_descriptor, apply_rewrite, @@ -863,13 +863,13 @@ org.slf4j.helpers.Util$ClassContext -> org.a.b.g$b: filename: App.java module: com.example.App abs_path: App.java - lineno: 0 + lineno: 42 index: 0 - function: barInternalInject filename: App.java module: com.example.App abs_path: App.java - lineno: 0 + lineno: 47 index: 0 "###); } From bde0a0f70d28107457769c1018760b3dc2b3ccbd Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 19:27:03 +0100 Subject: [PATCH 08/11] Update docs --- docs/api/response.md | 4 ++++ docs/api/symbolicate-jvm.md | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/docs/api/response.md b/docs/api/response.md index 1212af5ce..30041fbc4 100644 --- a/docs/api/response.md +++ b/docs/api/response.md @@ -201,6 +201,10 @@ a list of processed stack traces, exceptions and classes as well as an optional ], "stacktraces": [ { + "exception": { + "type": "RuntimeException", + "module": "java.lang" + }, "frames": [ { "function": "onMenuItemClick", diff --git a/docs/api/symbolicate-jvm.md b/docs/api/symbolicate-jvm.md index 83401508a..c1134dddd 100644 --- a/docs/api/symbolicate-jvm.md +++ b/docs/api/symbolicate-jvm.md @@ -27,6 +27,10 @@ Content-Type: application/json ], "stacktraces": [ { + "exception": { + "type": "RuntimeException", + "module": "io.sentry.samples" + }, "frames": [ { "function": "otherMethod", @@ -75,6 +79,7 @@ Content-Type: application/json - `modules`: A list of source code files with a corresponding debug id that were loaded during JVM code execution. The list is handled by the Sentry source. - `stacktrace`: A list of stacktraces to symbolicate. + - `exception`: (_optional_) The stacktrace exception which will have its module and type fields remapped. Necessary for applying [rewrite rules](https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#rewriteframe-introduced-at-version-2_0). - `frames`: A list of frames with corresponding `abs_path`, `lineno`, and other optional fields like `colno` or minified `function` name. This list is assumed to be ordered according to the `frame_order` option (see below). - `release_package`: Name of Sentry `release` for the processed request. From 2ed3785bbe65c73d5b5f8c21546548220156c7f6 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 23:42:57 +0100 Subject: [PATCH 09/11] Integration test --- .../tests/integration/proguard.rs | 64 +++++++++++++++++++ .../proguard/rewrite_frames/proguard.txt | 12 ++++ 2 files changed, 76 insertions(+) create mode 100644 tests/fixtures/proguard/rewrite_frames/proguard.txt diff --git a/crates/symbolicator-proguard/tests/integration/proguard.rs b/crates/symbolicator-proguard/tests/integration/proguard.rs index bad13c6e6..b57f137f9 100644 --- a/crates/symbolicator-proguard/tests/integration/proguard.rs +++ b/crates/symbolicator-proguard/tests/integration/proguard.rs @@ -481,3 +481,67 @@ async fn test_source_lookup_with_proguard() { assert_snapshot!(response); } + +#[tokio::test] +async fn test_rewrite_frames() { + symbolicator_test::setup(); + let (symbolication, _cache_dir) = setup_service(|_| ()); + let (_srv, source) = proguard_server("rewrite_frames", |_url, _query| { + json!([{ + "id":"proguard.txt", + "uuid":"550e8400-e29b-41d4-a716-446655440000", + "debugId":"550e8400-e29b-41d4-a716-446655440000", + "codeId":null, + "cpuName":"any", + "objectName":"proguard-mapping", + "symbolType":"proguard", + "headers": { + "Content-Type":"text/x-proguard+plain" + }, + "size":1000, + "sha1":"0000000000000000000000000000000000000000", + "dateCreated":"2024-02-14T10:49:38.770116Z", + "data":{ + "features":["mapping"] + } + }]) + }); + + let source = SourceConfig::Sentry(Arc::new(source)); + + // Test with NullPointerException thrown at a.start + // In CallerFirst order: [outermost_caller, ..., exception_location] + // So we have: draw (outermost) -> dispatch -> start (where exception thrown) + // + // After rewrite: + // - c.draw(20) -> UiBridge.render(200) + // - b.dispatch(5) -> StreamRouter.dispatch(12) + StreamRouter$Inline.internalDispatch(30) + // - a.start(10) -> Initializer.start(42) only (2 inlined frames removed by rewrite rule) + let frames_npe = r#"[{ + "function": "draw", + "module": "c", + "lineno": 20, + "index": 0 + }, { + "function": "dispatch", + "module": "b", + "lineno": 5, + "index": 1 + }, { + "function": "start", + "module": "a", + "lineno": 10, + "index": 2 + }]"#; + + let request_npe = make_jvm_request( + source.clone(), + r#"{"type": "NullPointerException", "module": "java.lang"}"#, + frames_npe, + r#"[{"uuid": "550e8400-e29b-41d4-a716-446655440000", "type": "proguard"}]"#, + None, + ); + + let response_npe = symbolication.symbolicate_jvm(request_npe).await; + assert_snapshot!(response_npe); +} diff --git a/tests/fixtures/proguard/rewrite_frames/proguard.txt b/tests/fixtures/proguard/rewrite_frames/proguard.txt new file mode 100644 index 000000000..43b45351c --- /dev/null +++ b/tests/fixtures/proguard/rewrite_frames/proguard.txt @@ -0,0 +1,12 @@ +com.example.flow.Initializer -> a: + 10:10:void com.example.flow.Inliner.firstStep(com.example.flow.Step):60:60 -> start + 10:10:void com.example.flow.Inliner.secondStep(com.example.flow.Step):61:61 -> start + 10:10:void start(com.example.flow.Step):42 -> start + # {"id":"com.android.tools.r8.rewriteFrame","conditions":["throws(Ljava/lang/NullPointerException;)"],"actions":["removeInnerFrames(2)"]} + 15:15:void resume():55 -> start +com.example.flow.StreamRouter -> b: + 5:5:void com.example.flow.StreamRouter$Inline.internalDispatch(java.lang.String):30:30 -> dispatch + 5:5:void dispatch(java.lang.String):12 -> dispatch + # {"id":"com.android.tools.r8.rewriteFrame","conditions":["throws(Ljava/lang/IllegalStateException;)"],"actions":["removeInnerFrames(1)"]} +com.example.flow.UiBridge -> c: + 20:20:void render():200 -> draw From 81120fdf0f383f42450f00c79b0de3bb2f932de7 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 18 Dec 2025 23:46:23 +0100 Subject: [PATCH 10/11] Add snapshot --- ...integration__proguard__rewrite_frames.snap | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__rewrite_frames.snap diff --git a/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__rewrite_frames.snap b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__rewrite_frames.snap new file mode 100644 index 000000000..e136676e7 --- /dev/null +++ b/crates/symbolicator-proguard/tests/integration/snapshots/integration__proguard__rewrite_frames.snap @@ -0,0 +1,31 @@ +--- +source: crates/symbolicator-proguard/tests/integration/proguard.rs +assertion_line: 546 +expression: response_npe +--- +exceptions: + - type: NullPointerException + module: java.lang +stacktraces: + - exception: + type: NullPointerException + module: java.lang + frames: + - function: render + module: com.example.flow.UiBridge + lineno: 200 + index: 0 + - function: dispatch + module: com.example.flow.StreamRouter + lineno: 12 + index: 1 + - function: internalDispatch + module: com.example.flow.StreamRouter$Inline + lineno: 30 + index: 1 + - function: start + module: com.example.flow.Initializer + lineno: 42 + index: 2 +classes: {} +errors: [] From 9ad0b310fc8f6b9e1bdd670cd64de8c25e5e67ae Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 19 Dec 2025 09:29:12 +0100 Subject: [PATCH 11/11] Preserve rewrite frames flag if encountered outline frame --- .../src/symbolication.rs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/symbolicator-proguard/src/symbolication.rs b/crates/symbolicator-proguard/src/symbolication.rs index 404634f24..e3dd77efb 100644 --- a/crates/symbolicator-proguard/src/symbolication.rs +++ b/crates/symbolicator-proguard/src/symbolication.rs @@ -16,8 +16,10 @@ use symbolicator_service::types::FrameOrder; /// Result of attempting a full frame remap with rewrite rules. enum FullRemapResult { - /// Outline frame or rewrite rules cleared all frames - skip this frame entirely. - Skip, + /// Outline frame - skip this frame but preserve rewrite eligibility for next frame. + OutlineFrame, + /// Rewrite rules cleared all frames - skip this frame and disable rewrite for subsequent frames. + RewriteCleared, /// Successfully remapped to these frames. Frames(Vec), /// No mappings found - try fallback methods. @@ -290,8 +292,12 @@ impl ProguardService { &mut carried_outline_pos[mapper_idx], &mut remap_buffer, ) { - FullRemapResult::Skip => { - // Outline frame or rewrite rules cleared all frames + FullRemapResult::OutlineFrame => { + // Outline frame - skip but preserve rewrite eligibility for next frame + continue 'frames; + } + FullRemapResult::RewriteCleared => { + // Rewrite rules cleared all frames - skip and disable rewrite next_frame_can_rewrite = false; continue 'frames; } @@ -430,15 +436,15 @@ impl ProguardService { apply_rewrite, carried_outline_pos, ) else { - // Outline frame - return FullRemapResult::Skip; + // Outline frame - preserve rewrite eligibility for next frame + return FullRemapResult::OutlineFrame; }; let had_mappings = iter.had_mappings(); buf.extend(iter); if had_mappings && buf.is_empty() { // Rewrite rules cleared all frames - return FullRemapResult::Skip; + return FullRemapResult::RewriteCleared; } if buf.is_empty() {