From cdb3ab128235e47a192fafecaa7d4ee80b848304 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Wed, 17 Apr 2024 12:47:57 +0200 Subject: [PATCH 1/3] feat: Add cleanup_mapping_file function --- src/lib.rs | 4 +-- src/mapping.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 925e479..bdb3208 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ mod stacktrace; pub use mapper::{DeobfuscatedSignature, ProguardMapper, RemappedFrameIter}; pub use mapping::{ - LineMapping, MappingSummary, ParseError, ParseErrorKind, ProguardMapping, ProguardRecord, - ProguardRecordIter, + cleanup_mapping_file, LineMapping, MappingSummary, ParseError, ParseErrorKind, ProguardMapping, + ProguardRecord, ProguardRecordIter, }; pub use stacktrace::{StackFrame, StackTrace, Throwable}; diff --git a/src/mapping.rs b/src/mapping.rs index 46c5635..dd0972a 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -4,6 +4,7 @@ //! [here](https://www.guardsquare.com/en/products/proguard/manual/retrace). use std::fmt; +use std::io::Write; use std::str; #[cfg(feature = "uuid")] @@ -206,16 +207,15 @@ impl<'s> ProguardMapping<'s> { /// assert_eq!(without.has_line_info(), false); /// ``` pub fn has_line_info(&self) -> bool { - // We are matching on the inner `ProguardRecord` anyway - #[allow(clippy::manual_flatten)] - for record in self.iter() { - if let Ok(ProguardRecord::Method { line_mapping, .. }) = record { - if line_mapping.is_some() { - return true; - } - } - } - false + self.iter().any(|record| { + matches!( + record, + Ok(ProguardRecord::Method { + line_mapping: Some(_), + .. + }) + ) + }) } /// Calculates the UUID of the mapping file. @@ -690,8 +690,63 @@ fn is_newline(byte: &u8) -> bool { *byte == b'\r' || *byte == b'\n' } +/// An error that occured while cleaning up a mapping file. +#[derive(Debug)] +pub enum CleanupFileError<'s> { + /// There was an error writing to the output buffer. + Io(std::io::Error), + /// There was an error reading a record. + Parse(ParseError<'s>), +} + +impl<'s> From> for CleanupFileError<'s> { + fn from(value: ParseError<'s>) -> Self { + Self::Parse(value) + } +} + +impl From for CleanupFileError<'_> { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +/// Cleans up a Proguard mapping file by removing all records +/// that can't be used by a [`ProguardMapper`]. +/// +/// This preserves class, method, and "sourceFile" header records. +/// It removes field and all other header records. +pub fn cleanup_mapping_file(source: &[u8], mut output: W) -> Result<(), CleanupFileError> +where + W: Write, +{ + for line in source.split(is_newline) { + if line.is_empty() { + continue; + } + + let (record, _rest) = parse_proguard_record(line); + if matches!( + record?, + ProguardRecord::Class { .. } + | ProguardRecord::Method { .. } + | ProguardRecord::Header { + key: "sourceFile", + .. + } + ) { + output.write_all(line)?; + output.write_all(&[b'\n'])?; + } + } + + Ok(()) +} + #[cfg(test)] mod tests { + use crate::cleanup_mapping_file; + use super::*; #[test] @@ -1060,4 +1115,27 @@ androidx.activity.OnBackPressedCallback ], ); } + + #[test] + fn cleanup() { + let bytes = br#"# compiler: R8 +# common_typos_disable + +androidx.activity.OnBackPressedCallback -> c.a.b: +# {"id":"sourceFile","fileName":"Test.kt"} + boolean mEnabled -> a + java.util.ArrayDeque mOnBackPressedCallbacks -> b + 1:4:void onBackPressed():184:187 -> c +"#; + + let mut out = Vec::new(); + + cleanup_mapping_file(bytes, &mut out).unwrap(); + + let expected = br#"androidx.activity.OnBackPressedCallback -> c.a.b: +# {"id":"sourceFile","fileName":"Test.kt"} + 1:4:void onBackPressed():184:187 -> c +"#; + assert_eq!(&out, expected); + } } From 4ebd53ad396f461da9ece97b3ee5bf82ecd8bd88 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Wed, 17 Apr 2024 13:47:16 +0200 Subject: [PATCH 2/3] Skip invalid records --- src/mapping.rs | 67 ++++++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/src/mapping.rs b/src/mapping.rs index dd0972a..094dd44 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -4,7 +4,7 @@ //! [here](https://www.guardsquare.com/en/products/proguard/manual/retrace). use std::fmt; -use std::io::Write; +use std::io::{BufRead, Write}; use std::str; #[cfg(feature = "uuid")] @@ -690,62 +690,44 @@ fn is_newline(byte: &u8) -> bool { *byte == b'\r' || *byte == b'\n' } -/// An error that occured while cleaning up a mapping file. -#[derive(Debug)] -pub enum CleanupFileError<'s> { - /// There was an error writing to the output buffer. - Io(std::io::Error), - /// There was an error reading a record. - Parse(ParseError<'s>), -} - -impl<'s> From> for CleanupFileError<'s> { - fn from(value: ParseError<'s>) -> Self { - Self::Parse(value) - } -} - -impl From for CleanupFileError<'_> { - fn from(value: std::io::Error) -> Self { - Self::Io(value) - } -} - /// Cleans up a Proguard mapping file by removing all records /// that can't be used by a [`ProguardMapper`]. /// /// This preserves class, method, and "sourceFile" header records. -/// It removes field and all other header records. -pub fn cleanup_mapping_file(source: &[u8], mut output: W) -> Result<(), CleanupFileError> +/// It removes field and all other header records, as well as all invalid lines. +pub fn cleanup_mapping_file(mut source: R, mut output: W) -> Result<(), std::io::Error> where + R: BufRead, W: Write, { - for line in source.split(is_newline) { - if line.is_empty() { - continue; + let mut line_buf = String::new(); + loop { + line_buf.clear(); + let read = source.read_line(&mut line_buf)?; + + if read == 0 { + break; } - let (record, _rest) = parse_proguard_record(line); + let (record, _rest) = parse_proguard_record(line_buf.as_bytes()); if matches!( - record?, - ProguardRecord::Class { .. } + record, + Ok(ProguardRecord::Class { .. } | ProguardRecord::Method { .. } | ProguardRecord::Header { key: "sourceFile", .. - } + }) ) { - output.write_all(line)?; - output.write_all(&[b'\n'])?; + output.write_all(line_buf.as_bytes())?; } } - Ok(()) } #[cfg(test)] mod tests { - use crate::cleanup_mapping_file; + use std::io::Cursor; use super::*; @@ -1118,24 +1100,29 @@ androidx.activity.OnBackPressedCallback #[test] fn cleanup() { - let bytes = br#"# compiler: R8 + let bytes = br#" +# compiler: R8 # common_typos_disable +androidx.activity.OnBackPressedCallback->c.a.b: androidx.activity.OnBackPressedCallback -> c.a.b: # {"id":"sourceFile","fileName":"Test.kt"} boolean mEnabled -> a + boolean mEnabled -> a java.util.ArrayDeque mOnBackPressedCallbacks -> b 1:4:void onBackPressed():184:187 -> c -"#; +androidx.activity.OnBackPressedCallback +-> c.a.b: + "#; let mut out = Vec::new(); - cleanup_mapping_file(bytes, &mut out).unwrap(); + cleanup_mapping_file(&mut Cursor::new(bytes), &mut out).unwrap(); - let expected = br#"androidx.activity.OnBackPressedCallback -> c.a.b: + let expected = r#"androidx.activity.OnBackPressedCallback -> c.a.b: # {"id":"sourceFile","fileName":"Test.kt"} 1:4:void onBackPressed():184:187 -> c "#; - assert_eq!(&out, expected); + assert_eq!(std::str::from_utf8(&out).unwrap(), expected); } } From 545e85b40bdb2153c41b825220454cb6bc77db04 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Wed, 17 Apr 2024 16:17:54 +0200 Subject: [PATCH 3/3] Review --- src/mapping.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mapping.rs b/src/mapping.rs index 094dd44..e75ba3d 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -1119,10 +1119,10 @@ androidx.activity.OnBackPressedCallback cleanup_mapping_file(&mut Cursor::new(bytes), &mut out).unwrap(); - let expected = r#"androidx.activity.OnBackPressedCallback -> c.a.b: + let expected = br#"androidx.activity.OnBackPressedCallback -> c.a.b: # {"id":"sourceFile","fileName":"Test.kt"} 1:4:void onBackPressed():184:187 -> c "#; - assert_eq!(std::str::from_utf8(&out).unwrap(), expected); + assert_eq!(out, expected); } }