Skip to content

Commit 24af606

Browse files
committed
ref: Robustly parse R8 headers
R8 headers have a different format from "normal" ProGuard headers. This splits the parsing of the two header formats apart and uses `serde` for R8 headers (they are specified to be in JSON format).
1 parent ebe9503 commit 24af606

File tree

5 files changed

+74
-51
lines changed

5 files changed

+74
-51
lines changed

Cargo.lock

Lines changed: 12 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ uuid = ["dep:uuid", "lazy_static"]
1515

1616
[dependencies]
1717
lazy_static = { version = "1.4.0", optional = true }
18+
serde = "1.0.219"
19+
serde_json = "1.0.140"
1820
thiserror = "1.0.61"
1921
uuid = { version = "1.0.0", features = ["v5"], optional = true }
2022
watto = { version = "0.1.0", features = ["writer", "strings"] }

src/cache/raw.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::io::Write;
33

44
use watto::{Pod, StringTable};
55

6+
use crate::mapping::R8Header;
67
use crate::{ProguardMapping, ProguardRecord};
78

89
use super::{CacheError, CacheErrorKind};
@@ -194,15 +195,11 @@ impl<'data> ProguardCache<'data> {
194195
let mut records = mapping.iter().filter_map(Result::ok).peekable();
195196
while let Some(record) = records.next() {
196197
match record {
197-
ProguardRecord::Header {
198-
key,
199-
value: Some(file_name),
200-
} => {
201-
if key == "sourceFile" {
202-
current_class.class.file_name_offset =
203-
string_table.insert(file_name) as u32;
204-
}
198+
ProguardRecord::R8Header(R8Header::SourceFile { ref file_name }) => {
199+
current_class.class.file_name_offset = string_table.insert(file_name) as u32;
205200
}
201+
ProguardRecord::Header { .. } => {}
202+
ProguardRecord::R8Header(R8Header::Other) => {}
206203
ProguardRecord::Class {
207204
original,
208205
obfuscated,

src/mapper.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::fmt::{Error as FmtError, Write};
55
use std::iter::FusedIterator;
66

77
use crate::java;
8+
use crate::mapping::R8Header;
89
use crate::mapping::{ProguardMapping, ProguardRecord};
910
use crate::stacktrace::{self, StackFrame, StackTrace, Throwable};
1011

@@ -236,11 +237,11 @@ impl<'s> ProguardMapper<'s> {
236237
let mut records = mapping.iter().filter_map(Result::ok).peekable();
237238
while let Some(record) = records.next() {
238239
match record {
239-
ProguardRecord::Header { key, value } => {
240-
if key == "sourceFile" {
241-
class.file_name = value;
242-
}
240+
ProguardRecord::R8Header(R8Header::SourceFile { file_name }) => {
241+
class.file_name = Some(file_name);
243242
}
243+
ProguardRecord::Header { .. } => {}
244+
ProguardRecord::R8Header(R8Header::Other) => {}
244245
ProguardRecord::Class {
245246
original,
246247
obfuscated,

src/mapping.rs

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use std::fmt;
77
use std::ops::Range;
88
use std::str;
99

10+
use serde::Deserialize;
11+
1012
#[cfg(feature = "uuid")]
1113
use uuid::Uuid;
1214

@@ -282,7 +284,7 @@ impl<'s> Iterator for ProguardRecordIter<'s> {
282284
/// Maps start/end lines of a minified file to original start/end lines.
283285
///
284286
/// All line mappings are 1-based and inclusive.
285-
#[derive(Clone, Copy, Debug, PartialEq)]
287+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
286288
pub struct LineMapping {
287289
/// Start Line, 1-based.
288290
pub startline: usize,
@@ -294,8 +296,26 @@ pub struct LineMapping {
294296
pub original_endline: Option<usize>,
295297
}
296298

299+
/// An R8 header, as described in
300+
/// <https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#additional-information-appended-as-comments-to-the-file>.
301+
///
302+
/// The format is a line starting with `#` and followed by a JSON object.
303+
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
304+
#[serde(tag = "id", rename_all = "camelCase")]
305+
pub enum R8Header<'s> {
306+
/// A source file header, stating what source file a class originated from.
307+
///
308+
/// See <https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#source-file>.
309+
#[serde(rename_all = "camelCase")]
310+
SourceFile { file_name: &'s str },
311+
312+
/// Catchall variant for headers we don't support.
313+
#[serde(other)]
314+
Other,
315+
}
316+
297317
/// A Proguard Mapping Record.
298-
#[derive(Clone, Debug, PartialEq)]
318+
#[derive(Clone, Debug, PartialEq, Eq)]
299319
pub enum ProguardRecord<'s> {
300320
/// A Proguard Header.
301321
Header {
@@ -304,6 +324,8 @@ pub enum ProguardRecord<'s> {
304324
/// Optional value if the Header is a KV pair.
305325
value: Option<&'s str>,
306326
},
327+
/// An R8 Header.
328+
R8Header(R8Header<'s>),
307329
/// A Class Mapping.
308330
Class {
309331
/// Original name of the class.
@@ -436,7 +458,9 @@ impl<'s> ProguardRecord<'s> {
436458
fn parse_proguard_record(bytes: &[u8]) -> (Result<ProguardRecord, ParseError>, &[u8]) {
437459
let bytes = consume_leading_newlines(bytes);
438460

439-
let result = if bytes.starts_with(b"#") {
461+
let result = if bytes.starts_with(b"# {") {
462+
parse_r8_header(bytes)
463+
} else if bytes.starts_with(b"#") {
440464
parse_proguard_header(bytes)
441465
} else if bytes.starts_with(b" ") {
442466
parse_proguard_field_or_method(bytes)
@@ -459,38 +483,35 @@ fn parse_proguard_record(bytes: &[u8]) -> (Result<ProguardRecord, ParseError>, &
459483
}
460484
}
461485

462-
const SOURCE_FILE_PREFIX: &[u8; 32] = br#" {"id":"sourceFile","fileName":""#;
463-
464486
/// Parses a single Proguard Header from a Proguard File.
465487
fn parse_proguard_header(bytes: &[u8]) -> Result<(ProguardRecord, &[u8]), ParseError> {
466488
let bytes = parse_prefix(bytes, b"#")?;
467489

468-
if let Ok(bytes) = parse_prefix(bytes, SOURCE_FILE_PREFIX) {
469-
let (value, bytes) = parse_until(bytes, |c| *c == b'"')?;
470-
let bytes = parse_prefix(bytes, br#""}"#)?;
490+
// Existing logic for `key: value` format
491+
let (key, bytes) = parse_until(bytes, |c| *c == b':' || is_newline(c))?;
471492

472-
let record = ProguardRecord::Header {
473-
key: "sourceFile",
474-
value: Some(value),
475-
};
493+
let (value, bytes) = match parse_prefix(bytes, b":") {
494+
Ok(bytes) => parse_until(bytes, is_newline).map(|(v, bytes)| (Some(v), bytes)),
495+
Err(_) => Ok((None, bytes)),
496+
}?;
476497

477-
Ok((record, consume_leading_newlines(bytes)))
478-
} else {
479-
// Existing logic for `key: value` format
480-
let (key, bytes) = parse_until(bytes, |c| *c == b':' || is_newline(c))?;
498+
let record = ProguardRecord::Header {
499+
key: key.trim(),
500+
value: value.map(|v| v.trim()),
501+
};
481502

482-
let (value, bytes) = match parse_prefix(bytes, b":") {
483-
Ok(bytes) => parse_until(bytes, is_newline).map(|(v, bytes)| (Some(v), bytes)),
484-
Err(_) => Ok((None, bytes)),
485-
}?;
503+
Ok((record, consume_leading_newlines(bytes)))
504+
}
486505

487-
let record = ProguardRecord::Header {
488-
key: key.trim(),
489-
value: value.map(|v| v.trim()),
490-
};
506+
fn parse_r8_header(bytes: &[u8]) -> Result<(ProguardRecord, &[u8]), ParseError> {
507+
let bytes = parse_prefix(bytes, b"#")?;
508+
let (header, rest) = parse_until(bytes, is_newline)?;
491509

492-
Ok((record, consume_leading_newlines(bytes)))
493-
}
510+
let header = serde_json::from_str(header).unwrap();
511+
Ok((
512+
ProguardRecord::R8Header(header),
513+
consume_leading_newlines(rest),
514+
))
494515
}
495516

496517
/// Parses a single Proguard Field or Method from a Proguard File.
@@ -763,10 +784,9 @@ mod tests {
763784
let parsed = ProguardRecord::try_parse(bytes);
764785
assert_eq!(
765786
parsed,
766-
Ok(ProguardRecord::Header {
767-
key: "sourceFile",
768-
value: Some("Foobar.kt")
769-
})
787+
Ok(ProguardRecord::R8Header(R8Header::SourceFile {
788+
file_name: "Foobar.kt",
789+
}))
770790
);
771791
}
772792

0 commit comments

Comments
 (0)