@@ -7,6 +7,8 @@ use std::fmt;
77use std:: ops:: Range ;
88use std:: str;
99
10+ use serde:: Deserialize ;
11+
1012#[ cfg( feature = "uuid" ) ]
1113use uuid:: Uuid ;
1214
@@ -283,7 +285,7 @@ impl<'s> Iterator for ProguardRecordIter<'s> {
283285/// Maps start/end lines of a minified file to original start/end lines.
284286///
285287/// All line mappings are 1-based and inclusive.
286- #[ derive( Clone , Copy , Debug , PartialEq ) ]
288+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
287289pub struct LineMapping {
288290 /// Start Line, 1-based.
289291 pub startline : usize ,
@@ -295,8 +297,26 @@ pub struct LineMapping {
295297 pub original_endline : Option < usize > ,
296298}
297299
300+ /// An R8 header, as described in
301+ /// <https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#additional-information-appended-as-comments-to-the-file>.
302+ ///
303+ /// The format is a line starting with `#` and followed by a JSON object.
304+ #[ derive( Debug , Clone , Deserialize , PartialEq , Eq ) ]
305+ #[ serde( tag = "id" , rename_all = "camelCase" ) ]
306+ pub enum R8Header < ' s > {
307+ /// A source file header, stating what source file a class originated from.
308+ ///
309+ /// See <https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md#source-file>.
310+ #[ serde( rename_all = "camelCase" ) ]
311+ SourceFile { file_name : & ' s str } ,
312+
313+ /// Catchall variant for headers we don't support.
314+ #[ serde( other) ]
315+ Other ,
316+ }
317+
298318/// A Proguard Mapping Record.
299- #[ derive( Clone , Debug , PartialEq ) ]
319+ #[ derive( Clone , Debug , PartialEq , Eq ) ]
300320pub enum ProguardRecord < ' s > {
301321 /// A Proguard Header.
302322 Header {
@@ -305,6 +325,8 @@ pub enum ProguardRecord<'s> {
305325 /// Optional value if the Header is a KV pair.
306326 value : Option < & ' s str > ,
307327 } ,
328+ /// An R8 Header.
329+ R8Header ( R8Header < ' s > ) ,
308330 /// A Class Mapping.
309331 Class {
310332 /// Original name of the class.
@@ -437,8 +459,15 @@ impl<'s> ProguardRecord<'s> {
437459fn parse_proguard_record ( bytes : & [ u8 ] ) -> ( Result < ProguardRecord , ParseError > , & [ u8 ] ) {
438460 let bytes = consume_leading_newlines ( bytes) ;
439461
440- let result = if bytes. starts_with ( b"#" ) {
441- parse_proguard_header ( bytes)
462+ let result = if let Some ( bytes) = bytes. trim_ascii_start ( ) . strip_prefix ( b"#" ) {
463+ // ProGuard / R8 headers
464+
465+ let bytes = bytes. trim_ascii_start ( ) ;
466+ if bytes. starts_with ( b"{" ) {
467+ parse_r8_header ( bytes)
468+ } else {
469+ parse_proguard_header ( bytes)
470+ }
442471 } else if bytes. starts_with ( b" " ) {
443472 parse_proguard_field_or_method ( bytes)
444473 } else {
@@ -460,38 +489,36 @@ fn parse_proguard_record(bytes: &[u8]) -> (Result<ProguardRecord, ParseError>, &
460489 }
461490}
462491
463- const SOURCE_FILE_PREFIX : & [ u8 ; 32 ] = br#" {"id":"sourceFile","fileName":""# ;
464-
465492/// Parses a single Proguard Header from a Proguard File.
466493fn parse_proguard_header ( bytes : & [ u8 ] ) -> Result < ( ProguardRecord , & [ u8 ] ) , ParseError > {
467- let bytes = parse_prefix ( bytes , b"#" ) ? ;
494+ // Note: the leading `#` has already been parsed.
468495
469- if let Ok ( bytes) = parse_prefix ( bytes, SOURCE_FILE_PREFIX ) {
470- let ( value, bytes) = parse_until ( bytes, |c| * c == b'"' ) ?;
471- let bytes = parse_prefix ( bytes, br#""}"# ) ?;
496+ // Existing logic for `key: value` format
497+ let ( key, bytes) = parse_until ( bytes, |c| * c == b':' || is_newline ( c) ) ?;
472498
473- let record = ProguardRecord :: Header {
474- key : "sourceFile" ,
475- value : Some ( value ) ,
476- } ;
499+ let ( value , bytes ) = match parse_prefix ( bytes , b":" ) {
500+ Ok ( bytes ) => parse_until ( bytes , is_newline ) . map ( | ( v , bytes ) | ( Some ( v ) , bytes ) ) ,
501+ Err ( _ ) => Ok ( ( None , bytes ) ) ,
502+ } ? ;
477503
478- Ok ( ( record , consume_leading_newlines ( bytes ) ) )
479- } else {
480- // Existing logic for `key : value` format
481- let ( key , bytes ) = parse_until ( bytes , |c| * c == b':' || is_newline ( c ) ) ? ;
504+ let record = ProguardRecord :: Header {
505+ key : key . trim ( ) ,
506+ value : value. map ( |v| v . trim ( ) ) ,
507+ } ;
482508
483- let ( value, bytes) = match parse_prefix ( bytes, b":" ) {
484- Ok ( bytes) => parse_until ( bytes, is_newline) . map ( |( v, bytes) | ( Some ( v) , bytes) ) ,
485- Err ( _) => Ok ( ( None , bytes) ) ,
486- } ?;
509+ Ok ( ( record, consume_leading_newlines ( bytes) ) )
510+ }
487511
488- let record = ProguardRecord :: Header {
489- key : key. trim ( ) ,
490- value : value. map ( |v| v. trim ( ) ) ,
491- } ;
512+ fn parse_r8_header ( bytes : & [ u8 ] ) -> Result < ( ProguardRecord , & [ u8 ] ) , ParseError > {
513+ // Note: the leading `#` has already been parsed.
492514
493- Ok ( ( record, consume_leading_newlines ( bytes) ) )
494- }
515+ let ( header, rest) = parse_until ( bytes, is_newline) ?;
516+
517+ let header = serde_json:: from_str ( header) . unwrap ( ) ;
518+ Ok ( (
519+ ProguardRecord :: R8Header ( header) ,
520+ consume_leading_newlines ( rest) ,
521+ ) )
495522}
496523
497524/// Parses a single Proguard Field or Method from a Proguard File.
@@ -764,10 +791,29 @@ mod tests {
764791 let parsed = ProguardRecord :: try_parse ( bytes) ;
765792 assert_eq ! (
766793 parsed,
767- Ok ( ProguardRecord :: Header {
768- key: "sourceFile" ,
769- value: Some ( "Foobar.kt" )
770- } )
794+ Ok ( ProguardRecord :: R8Header ( R8Header :: SourceFile {
795+ file_name: "Foobar.kt" ,
796+ } ) )
797+ ) ;
798+ }
799+
800+ #[ test]
801+ fn try_parse_r8_headers ( ) {
802+ let bytes = br#"# {"id":"foobar"}"# ;
803+ assert_eq ! (
804+ ProguardRecord :: try_parse( bytes) . unwrap( ) ,
805+ ProguardRecord :: R8Header ( R8Header :: Other ) ,
806+ ) ;
807+
808+ let bytes = br#" #{"id":"foobar"}"# ;
809+ assert_eq ! (
810+ ProguardRecord :: try_parse( bytes) . unwrap( ) ,
811+ ProguardRecord :: R8Header ( R8Header :: Other ) ,
812+ ) ;
813+ let bytes = br#"# {"id":"foobar"}"# ;
814+ assert_eq ! (
815+ ProguardRecord :: try_parse( bytes) . unwrap( ) ,
816+ ProguardRecord :: R8Header ( R8Header :: Other ) ,
771817 ) ;
772818 }
773819
@@ -1005,6 +1051,7 @@ androidx.activity.OnBackPressedCallback -> c.a.b:
10051051 boolean mEnabled -> a
10061052 java.util.ArrayDeque mOnBackPressedCallbacks -> b
10071053 1:4:void onBackPressed():184:187 -> c
1054+ # {\" id\" :\" com.android.tools.r8.synthesized\" }
10081055androidx.activity.OnBackPressedCallback
10091056-> c.a.b:
10101057 " ;
@@ -1057,6 +1104,7 @@ androidx.activity.OnBackPressedCallback
10571104 original_endline: Some ( 187 ) ,
10581105 } ) ,
10591106 } ) ,
1107+ Ok ( ProguardRecord :: R8Header ( R8Header :: Other ) ) ,
10601108 Err ( ParseError {
10611109 line: b"androidx.activity.OnBackPressedCallback \n " ,
10621110 kind: ParseErrorKind :: ParseError ( "line is not a valid proguard record" ) ,
0 commit comments