Skip to content

Commit 4f8317b

Browse files
romtsncodex
andauthored
fix(mapping): Parse Compose mappings with flexible indentation (#93)
Parse Compose mappings with flexible indentation `ComposeStackTrace -> $$compose:` sections generated by recent Android builds use two-space member indentation. The Compose compiler emits those entries with two leading spaces in https://github.com/JetBrains/kotlin/blob/effb244358aec74b7fa752d70a1055b4ea2f3e57/plugins/compose/group-mapping/src/main/kotlin/androidx/compose/compiler/mapping/ComposeMapping.kt#L54, which does not follow the ProGuard spec for member lines. As a result, those entries were dropped during cache generation, leaving symbolication able to remap `$$compose` to `ComposeStackTrace` but not to the original methods. I already filed https://issuetracker.google.com/issues/504284805, but we still need to fix this on our side because there are existing broken mappings in the wild that we need to support. This changes the parser to accept indented member lines and adds focused mapper and cache regressions for the raw Compose stacktrace shape. Refs getsentry/sentry-android-gradle-plugin#1065 --------- Co-authored-by: Codex <noreply@openai.com>
1 parent 9922a41 commit 4f8317b

2 files changed

Lines changed: 66 additions & 18 deletions

File tree

src/mapping.rs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,7 @@ impl<'s> ProguardMapping<'s> {
161161
/// let valid = ProguardMapping::new(b"a -> b:\n void method() -> b");
162162
/// assert_eq!(valid.is_valid(), true);
163163
///
164-
/// let invalid = ProguardMapping::new(
165-
/// br#"
166-
/// # looks: like
167-
/// a -> proguard:
168-
/// mapping but(is) -> not
169-
/// "#,
170-
/// );
164+
/// let invalid = ProguardMapping::new(b"a -> proguard:\n not a valid proguard member line");
171165
/// assert_eq!(invalid.is_valid(), false);
172166
/// ```
173167
pub fn is_valid(&self) -> bool {
@@ -505,7 +499,7 @@ fn parse_proguard_record(bytes: &[u8]) -> (Result<ProguardRecord<'_>, ParseError
505499
} else {
506500
parse_proguard_header(bytes)
507501
}
508-
} else if bytes.starts_with(b" ") {
502+
} else if matches!(bytes.first(), Some(b' ' | b'\t')) {
509503
parse_proguard_field_or_method(bytes)
510504
} else {
511505
parse_proguard_class(bytes)
@@ -568,7 +562,7 @@ fn parse_proguard_field_or_method(
568562
// field line or method line:
569563
// `originalfieldtype originalfieldname -> obfuscatedfieldname`
570564
// `[startline:endline:]originalreturntype [originalclassname.]originalmethodname(originalargumenttype,...)[:originalstartline[:originalendline]] -> obfuscatedmethodname`
571-
let bytes = parse_prefix(bytes, b" ")?;
565+
let bytes = bytes.trim_ascii_start();
572566

573567
let (startline, bytes) = match parse_usize(bytes) {
574568
Ok((startline, bytes)) => (Some(startline), bytes),
@@ -1063,15 +1057,15 @@ mod tests {
10631057
}
10641058

10651059
#[test]
1066-
fn try_parse_field_insufficient_leading_spaces() {
1067-
// only 2 leading spaces instead of 4
1060+
fn try_parse_field_with_two_space_indentation() {
10681061
let bytes = b" android.app.Activity mActivity -> a";
10691062
let parsed = ProguardRecord::try_parse(bytes);
10701063
assert_eq!(
10711064
parsed,
1072-
Err(ParseError {
1073-
line: bytes,
1074-
kind: ParseErrorKind::ParseError("line is not a valid proguard record"),
1065+
Ok(ProguardRecord::Field {
1066+
ty: "android.app.Activity",
1067+
original: "mActivity",
1068+
obfuscated: "a",
10751069
}),
10761070
);
10771071
}
@@ -1145,9 +1139,10 @@ androidx.activity.OnBackPressedCallback
11451139
original: "mEnabled",
11461140
obfuscated: "a",
11471141
}),
1148-
Err(ParseError {
1149-
line: b" boolean mEnabled -> a\n",
1150-
kind: ParseErrorKind::ParseError("line is not a valid proguard record"),
1142+
Ok(ProguardRecord::Field {
1143+
ty: "boolean",
1144+
original: "mEnabled",
1145+
obfuscated: "a",
11511146
}),
11521147
Ok(ProguardRecord::Field {
11531148
ty: "java.util.ArrayDeque",

tests/retrace.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use proguard::{ProguardMapper, StackFrame};
1+
use proguard::{ProguardCache, ProguardMapper, ProguardMapping, StackFrame};
22

33
#[test]
44
fn test_remap() {
@@ -130,3 +130,56 @@ fn test_remap_just_method() {
130130
let ambiguous = mapper.remap_method("a.b.c.d", "buttonClicked");
131131
assert_eq!(ambiguous, None);
132132
}
133+
134+
#[test]
135+
fn test_remap_compose_stacktrace_group_keys() {
136+
let mapping = r#"ComposeStackTrace -> $$compose:
137+
1:1:androidx.compose.runtime.State androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(float,androidx.compose.animation.core.AnimationSpec,float,java.lang.String,kotlin.jvm.functions.Function1,androidx.compose.runtime.Composer,int,int):71:71 -> m$1125598679
138+
1:1:androidx.compose.runtime.State androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(float,androidx.compose.animation.core.AnimationSpec,float,java.lang.String,kotlin.jvm.functions.Function1,androidx.compose.runtime.Composer,int,int):73:73 -> m$1125708605"#;
139+
let mapper = ProguardMapper::from(mapping);
140+
141+
let mapped = mapper
142+
.remap_stacktrace(
143+
r#"androidx.compose.runtime.ComposeTraceException:
144+
at $$compose.m$1125598679(SourceFile:1)
145+
at $$compose.m$1125708605(SourceFile:1)"#,
146+
)
147+
.unwrap();
148+
149+
assert_eq!(
150+
mapped.trim(),
151+
r#"androidx.compose.runtime.ComposeTraceException:
152+
at androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(AnimateAsState.kt:71)
153+
at androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(AnimateAsState.kt:73)"#
154+
.trim()
155+
);
156+
}
157+
158+
#[test]
159+
fn test_remap_compose_stacktrace_group_keys_cache() {
160+
let mapping = ProguardMapping::new(
161+
br#"ComposeStackTrace -> $$compose:
162+
1:1:androidx.compose.runtime.State androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(float,androidx.compose.animation.core.AnimationSpec,float,java.lang.String,kotlin.jvm.functions.Function1,androidx.compose.runtime.Composer,int,int):71:71 -> m$1125598679
163+
1:1:androidx.compose.runtime.State androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(float,androidx.compose.animation.core.AnimationSpec,float,java.lang.String,kotlin.jvm.functions.Function1,androidx.compose.runtime.Composer,int,int):73:73 -> m$1125708605"#,
164+
);
165+
166+
let mut buf = Vec::new();
167+
ProguardCache::write(&mapping, &mut buf).unwrap();
168+
let cache = ProguardCache::parse(&buf).unwrap();
169+
170+
let mapped = cache
171+
.remap_stacktrace(
172+
r#"androidx.compose.runtime.ComposeTraceException:
173+
at $$compose.m$1125598679(SourceFile:1)
174+
at $$compose.m$1125708605(SourceFile:1)"#,
175+
)
176+
.unwrap();
177+
178+
assert_eq!(
179+
mapped.trim(),
180+
r#"androidx.compose.runtime.ComposeTraceException:
181+
at androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(AnimateAsState.kt:71)
182+
at androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState(AnimateAsState.kt:73)"#
183+
.trim()
184+
);
185+
}

0 commit comments

Comments
 (0)