Skip to content

Commit f048b2c

Browse files
committed
feat(r8-tests): Add R8 exception handling tests
1 parent 20a5bee commit f048b2c

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# R8 Exception Handling fixtures: failures & required behavior changes
2+
3+
Ported from upstream R8 retrace fixtures under:
4+
- `src/test/java/com/android/tools/r8/retrace/stacktraces/`
5+
6+
This doc lists **only the failing tests** and explains, one-by-one, what would need to change in `rust-proguard` to match upstream R8 retrace behavior. (We keep expectations as-is; no behavior fixes here.)
7+
8+
## `test_suppressed_stacktrace`
9+
10+
- **Upstream behavior**: Throwable lines prefixed with `Suppressed:` still have their exception class retraced (e.g. `Suppressed: a.b.c: ...``Suppressed: foo.bar.baz: ...`).
11+
- **Current crate behavior**: Leaves the suppressed exception class as `a.b.c`.
12+
- **Why it fails**: `stacktrace::parse_throwable` recognizes normal throwables and `Caused by:`, but the `Suppressed:` prefix requires special-case parsing/stripping and then re-emitting with the same prefix.
13+
- **What needs fixing**:
14+
- **Throwable parsing**: treat `Suppressed:` lines as throwables (similar to `Caused by:`) and remap their class name.
15+
16+
## `test_circular_reference_stacktrace`
17+
18+
- **Upstream behavior**: Retrace rewrites `[CIRCULAR REFERENCE: X]` tokens by remapping `X` as a class name (when it looks like an obfuscated class).
19+
- **Current crate behavior**: Leaves the input unchanged.
20+
- **Why it fails**: These lines are neither parsed as throwables nor stack frames, so they currently fall through to “print as-is”.
21+
- **What needs fixing**:
22+
- **Extra line kinds**: add a parser/rewriter for circular-reference marker lines that extracts the referenced class name and applies `remap_class`.
23+
- **Robustness**: keep the upstream behavior of only rewriting valid markers and leaving invalid marker formats unchanged.
24+
25+
## `test_exception_message_with_class_name_in_message`
26+
27+
- **Upstream behavior**: Retrace can replace obfuscated class names appearing inside arbitrary log/exception message text (here it replaces `net::ERR_CONNECTION_CLOSED``foo.bar.baz::ERR_CONNECTION_CLOSED`).
28+
- **Current crate behavior**: Does not rewrite inside plain text lines.
29+
- **Why it fails**: `remap_stacktrace` currently only remaps:
30+
- throwable headers (`X: message`, `Caused by: ...`, etc.)
31+
- parsed stack frames (`at ...`)
32+
Everything else is emitted unchanged.
33+
- **What needs fixing**:
34+
- **Text rewriting pass** (R8-like): implement optional “message rewriting” for known patterns where an obfuscated class appears in text (in this fixture: a token that looks like `<class>::<rest>`).
35+
- **Scoping**: upstream uses context; we likely need a conservative implementation to avoid over-replacing.
36+
37+
## `test_unknown_source_stacktrace`
38+
39+
- **Expected in test**: deterministic ordering for ambiguous alternatives: `bar` then `foo`, repeated for each frame.
40+
- **Current crate behavior**: emits the same set of alternatives but in the opposite order (`foo` then `bar`).
41+
- **Why it fails**: ambiguous member ordering is currently determined by internal iteration order (mapping parse order / sorting) which does not match upstream’s ordering rules for this fixture.
42+
- **What needs fixing**:
43+
- **Stable ordering rule** for ambiguous alternatives (e.g., preserve mapping file order, or sort by original method name/signature in a defined way matching R8).
44+

tests/r8-exception-handling.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
//! Tests for R8 retrace "Exception Handling" fixtures.
2+
//!
3+
//! Ported from the upstream R8 retrace fixtures in:
4+
//! `src/test/java/com/android/tools/r8/retrace/stacktraces/`.
5+
//!
6+
//! Notes:
7+
//! - Fixture mapping indentation is normalized to 4-space member indentation so it is parsed by this
8+
//! crate's Proguard mapping parser.
9+
//! - Expected stacktrace indentation is normalized to this crate's output (`" at ..."`).
10+
//! - These tests intentionally do **not** assert on R8 warning counts; this crate currently does not
11+
//! surface equivalent diagnostics.
12+
#![allow(clippy::unwrap_used)]
13+
14+
use proguard::{ProguardCache, ProguardMapper, ProguardMapping};
15+
16+
fn assert_remap_stacktrace(mapping: &str, input: &str, expected: &str) {
17+
let mapper = ProguardMapper::from(mapping);
18+
let actual = mapper.remap_stacktrace(input).unwrap();
19+
assert_eq!(actual.trim_end(), expected.trim_end());
20+
21+
let mapping = ProguardMapping::new(mapping.as_bytes());
22+
let mut buf = Vec::new();
23+
ProguardCache::write(&mapping, &mut buf).unwrap();
24+
let cache = ProguardCache::parse(&buf).unwrap();
25+
cache.test();
26+
27+
let actual = cache.remap_stacktrace(input).unwrap();
28+
assert_eq!(actual.trim_end(), expected.trim_end());
29+
}
30+
31+
// =============================================================================
32+
// ObfucatedExceptionClassStackTrace
33+
// =============================================================================
34+
35+
const OBFUSCATED_EXCEPTION_CLASS_MAPPING: &str = r#"foo.bar.baz -> a.b.c:
36+
"#;
37+
38+
#[test]
39+
fn test_obfuscated_exception_class_stacktrace() {
40+
let input = r#"a.b.c: Problem when compiling program
41+
at r8.main(App:800)
42+
Caused by: a.b.c: You have to write the program first
43+
at r8.retrace(App:184)
44+
... 7 more
45+
"#;
46+
47+
let expected = r#"foo.bar.baz: Problem when compiling program
48+
at r8.main(App:800)
49+
Caused by: foo.bar.baz: You have to write the program first
50+
at r8.retrace(App:184)
51+
... 7 more
52+
"#;
53+
54+
assert_remap_stacktrace(OBFUSCATED_EXCEPTION_CLASS_MAPPING, input, expected);
55+
}
56+
57+
// =============================================================================
58+
// SuppressedStackTrace
59+
// =============================================================================
60+
61+
const SUPPRESSED_STACKTRACE_MAPPING: &str = r#"foo.bar.baz -> a.b.c:
62+
"#;
63+
64+
#[test]
65+
fn test_suppressed_stacktrace() {
66+
let input = r#"a.b.c: Problem when compiling program
67+
at r8.main(App:800)
68+
Suppressed: a.b.c: You have to write the program first
69+
at r8.retrace(App:184)
70+
... 7 more
71+
"#;
72+
73+
let expected = r#"foo.bar.baz: Problem when compiling program
74+
at r8.main(App:800)
75+
Suppressed: foo.bar.baz: You have to write the program first
76+
at r8.retrace(App:184)
77+
... 7 more
78+
"#;
79+
80+
assert_remap_stacktrace(SUPPRESSED_STACKTRACE_MAPPING, input, expected);
81+
}
82+
83+
// =============================================================================
84+
// CircularReferenceStackTrace
85+
// =============================================================================
86+
87+
const CIRCULAR_REFERENCE_STACKTRACE_MAPPING: &str = r#"foo.bar.Baz -> A.A:
88+
foo.bar.Qux -> A.B:
89+
"#;
90+
91+
#[test]
92+
fn test_circular_reference_stacktrace() {
93+
let input = r#" [CIRCULAR REFERENCE: A.A]
94+
[CIRCULAR REFERENCE: A.B]
95+
[CIRCULAR REFERENCE: None.existing.class]
96+
[CIRCULAR REFERENCE: A.A]
97+
[CIRCU:AA]
98+
[CIRCULAR REFERENCE: A.A
99+
[CIRCULAR REFERENCE: ]
100+
[CIRCULAR REFERENCE: None existing class]
101+
"#;
102+
103+
let expected = r#" [CIRCULAR REFERENCE: foo.bar.Baz]
104+
[CIRCULAR REFERENCE: foo.bar.Qux]
105+
[CIRCULAR REFERENCE: None.existing.class]
106+
[CIRCULAR REFERENCE: foo.bar.Baz]
107+
[CIRCU:AA]
108+
[CIRCULAR REFERENCE: foo.bar.Baz
109+
[CIRCULAR REFERENCE: ]
110+
[CIRCULAR REFERENCE: None existing class]
111+
"#;
112+
113+
assert_remap_stacktrace(CIRCULAR_REFERENCE_STACKTRACE_MAPPING, input, expected);
114+
}
115+
116+
// =============================================================================
117+
// ExceptionMessageWithClassNameInMessage
118+
// =============================================================================
119+
120+
const EXCEPTION_MESSAGE_WITH_CLASSNAME_IN_MESSAGE_MAPPING: &str = r#"foo.bar.baz -> net:
121+
"#;
122+
123+
#[test]
124+
fn test_exception_message_with_class_name_in_message() {
125+
let input = r#"10-26 19:26:24.749 10159 26250 26363 E Tycho.crl: Exception
126+
10-26 19:26:24.749 10159 26250 26363 E Tycho.crl: java.util.concurrent.ExecutionException: ary: eu: Exception in CronetUrlRequest: net::ERR_CONNECTION_CLOSED, ErrorCode=5, InternalErrorCode=-100, Retryable=true
127+
"#;
128+
129+
let expected = r#"10-26 19:26:24.749 10159 26250 26363 E Tycho.crl: Exception
130+
10-26 19:26:24.749 10159 26250 26363 E Tycho.crl: java.util.concurrent.ExecutionException: ary: eu: Exception in CronetUrlRequest: foo.bar.baz::ERR_CONNECTION_CLOSED, ErrorCode=5, InternalErrorCode=-100, Retryable=true
131+
"#;
132+
133+
assert_remap_stacktrace(
134+
EXCEPTION_MESSAGE_WITH_CLASSNAME_IN_MESSAGE_MAPPING,
135+
input,
136+
expected,
137+
);
138+
}
139+
140+
// =============================================================================
141+
// RetraceAssertionErrorStackTrace
142+
// =============================================================================
143+
144+
const RETRACE_ASSERTION_ERROR_STACKTRACE_MAPPING: &str = r#"com.android.tools.r8.retrace.Retrace -> com.android.tools.r8.retrace.Retrace:
145+
boolean $assertionsDisabled -> a
146+
1:5:void <clinit>():34:38 -> <clinit>
147+
1:1:void <init>():35:35 -> <init>
148+
com.android.tools.r8.retrace.RetraceCore$StackTraceNode -> com.android.tools.r8.retrace.h:
149+
java.util.List lines -> a
150+
boolean $assertionsDisabled -> b
151+
1:1:void <clinit>():24:24 -> <clinit>
152+
1:4:void <init>(java.util.List):28:31 -> <init>
153+
com.android.tools.r8.retrace.RetraceCore -> com.android.tools.r8.retrace.f:
154+
1:3:com.android.tools.r8.retrace.RetraceCore$RetraceResult retrace():106:108 -> a
155+
4:7:void retraceLine(java.util.List,int,java.util.List):112:115 -> a
156+
8:8:void retraceLine(java.util.List,int,java.util.List):115 -> a
157+
47:50:void retraceLine(java.util.List,int,java.util.List):116:119 -> a
158+
com.android.tools.r8.retrace.Retrace -> com.android.tools.r8.retrace.Retrace:
159+
1:9:void run(com.android.tools.r8.retrace.RetraceCommand):112:120 -> run
160+
"#;
161+
162+
#[test]
163+
fn test_retrace_assertion_error_stacktrace() {
164+
let input = r#"java.lang.AssertionError
165+
at com.android.tools.r8.retrace.h.<init>(:4)
166+
at com.android.tools.r8.retrace.f.a(:48)
167+
at com.android.tools.r8.retrace.f.a(:2)
168+
at com.android.tools.r8.retrace.Retrace.run(:5)
169+
at com.android.tools.r8.retrace.RetraceTests.testNullLineTrace(RetraceTests.java:73)
170+
"#;
171+
172+
let expected = r#"java.lang.AssertionError
173+
at com.android.tools.r8.retrace.RetraceCore$StackTraceNode.<init>(RetraceCore.java:31)
174+
at com.android.tools.r8.retrace.RetraceCore.retraceLine(RetraceCore.java:117)
175+
at com.android.tools.r8.retrace.RetraceCore.retrace(RetraceCore.java:107)
176+
at com.android.tools.r8.retrace.Retrace.run(Retrace.java:116)
177+
at com.android.tools.r8.retrace.RetraceTests.testNullLineTrace(RetraceTests.java:73)
178+
"#;
179+
180+
assert_remap_stacktrace(RETRACE_ASSERTION_ERROR_STACKTRACE_MAPPING, input, expected);
181+
}
182+
183+
// =============================================================================
184+
// UnknownSourceStackTrace
185+
// =============================================================================
186+
187+
const UNKNOWN_SOURCE_STACKTRACE_MAPPING: &str = r#"com.android.tools.r8.R8 -> a.a:
188+
void foo(int) -> a
189+
void bar(int, int) -> a
190+
"#;
191+
192+
#[test]
193+
fn test_unknown_source_stacktrace() {
194+
let input = r#"com.android.tools.r8.CompilationException: foo[parens](Source:3)
195+
at a.a.a(Unknown Source)
196+
at a.a.a(Unknown Source)
197+
at com.android.tools.r8.R8.main(Unknown Source)
198+
Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)
199+
at a.a.a(Unknown Source)
200+
... 42 more
201+
"#;
202+
203+
// This crate does not format `<OR>` groups; alternatives are emitted as duplicate frames.
204+
let expected = r#"com.android.tools.r8.CompilationException: foo[parens](Source:3)
205+
at com.android.tools.r8.R8.bar(R8.java:0)
206+
at com.android.tools.r8.R8.foo(R8.java:0)
207+
at com.android.tools.r8.R8.bar(R8.java:0)
208+
at com.android.tools.r8.R8.foo(R8.java:0)
209+
at com.android.tools.r8.R8.main(Unknown Source)
210+
Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)
211+
at com.android.tools.r8.R8.bar(R8.java:0)
212+
at com.android.tools.r8.R8.foo(R8.java:0)
213+
... 42 more
214+
"#;
215+
216+
assert_remap_stacktrace(UNKNOWN_SOURCE_STACKTRACE_MAPPING, input, expected);
217+
}

0 commit comments

Comments
 (0)