From 43b85fbed98ce5cb7463477e98ea6eb6211369c9 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Jul 2025 14:17:05 +0200 Subject: [PATCH 1/7] WIPw --- src/mapper.rs | 223 +++++++++++++++++++++++++++++++++++++--------- tests/callback.rs | 26 ++---- 2 files changed, 190 insertions(+), 59 deletions(-) diff --git a/src/mapper.rs b/src/mapper.rs index c085501..6cb8cdd 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -52,6 +52,59 @@ impl fmt::Display for DeobfuscatedSignature { } } +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +struct ObfuscatedName<'s>(&'s str); + +impl std::ops::Deref for ObfuscatedName<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +struct OriginalName<'s>(&'s str); + +impl std::ops::Deref for OriginalName<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +#[derive(Clone, Debug, Default)] +struct ClassInfo<'s> { + source_file: Option<&'s str>, + members: HashMap, Members<'s>>, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +struct MethodKey<'s> { + class: OriginalName<'s>, + name: OriginalName<'s>, + arguments: &'s str, +} + +#[derive(Clone, Debug, Default)] +struct MethodInfo {} + +#[derive(Clone, Debug)] +struct Member<'s> { + method: MethodKey<'s>, + startline: usize, + endline: usize, + original_startline: usize, + original_endline: Option, +} + +#[derive(Clone, Debug, Default)] +struct Members<'s> { + all: Vec>, + by_params: HashMap<&'s str, Vec>>, +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] struct MemberMapping<'s> { startline: usize, @@ -63,18 +116,16 @@ struct MemberMapping<'s> { original_endline: Option, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] struct ClassMembers<'s> { all_mappings: Vec>, // method_params -> Vec[MemberMapping] mappings_by_params: HashMap<&'s str, Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] struct ClassMapping<'s> { original: &'s str, - obfuscated: &'s str, - file_name: Option<&'s str>, members: HashMap<&'s str, ClassMembers<'s>>, } @@ -226,37 +277,45 @@ impl<'s> ProguardMapper<'s> { initialize_param_mapping: bool, ) -> Self { let mut classes = HashMap::new(); - let mut class = ClassMapping { - original: "", - obfuscated: "", - file_name: None, - members: HashMap::new(), - }; + let mut methods = HashMap::new(); + let mut class_name_mapping = HashMap::new(); + // let mut method_name_mapping: HashMap::new(); + let mut current_class_name = None; + let mut current_class = ClassInfo::default(); let mut unique_methods: HashSet<(&str, &str, &str)> = HashSet::new(); let mut records = mapping.iter().filter_map(Result::ok).peekable(); while let Some(record) = records.next() { match record { - ProguardRecord::R8Header(R8Header::SourceFile { file_name }) => { - class.file_name = Some(file_name); - } ProguardRecord::Header { .. } => {} - ProguardRecord::R8Header(R8Header::Other) => {} + ProguardRecord::R8Header(_) => {} ProguardRecord::Class { original, obfuscated, } => { - if !class.original.is_empty() { - classes.insert(class.obfuscated, class); + // Flush the previous class if there is one. + if let Some(name) = current_class_name { + classes.insert(name, current_class); } - class = ClassMapping { - original, - obfuscated, - file_name: None, - members: HashMap::new(), - }; + let key = OriginalName(original); + current_class_name = Some(key); + current_class = ClassInfo::default(); + class_name_mapping.insert(ObfuscatedName(obfuscated), key); unique_methods.clear(); + + // consume R8 headers attached to this class + while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() { + match r8_header { + R8Header::SourceFile { file_name } => { + current_class.source_file = Some(file_name); + } + R8Header::Other => {} + } + + records.next(); + } } + ProguardRecord::Method { original, obfuscated, @@ -285,24 +344,30 @@ impl<'s> ProguardMapper<'s> { } }); - let members = class + let members = current_class .members - .entry(obfuscated) - .or_insert_with(|| ClassMembers { - all_mappings: Vec::with_capacity(1), - mappings_by_params: Default::default(), - }); + .entry(ObfuscatedName(obfuscated)) + .or_default(); + + let method = MethodKey { + class: original_class + .map(OriginalName) + .or(current_class_name) + .unwrap(), + name: OriginalName(original), + arguments, + }; - let member_mapping = MemberMapping { + let _method_info: &mut MethodInfo = methods.entry(method).or_default(); + let member = Member { + method, startline, endline, - original_class, - original_file: class.file_name, - original, original_startline, original_endline, }; - members.all_mappings.push(member_mapping.clone()); + + members.all.push(member.clone()); if !initialize_param_mapping { continue; @@ -327,20 +392,94 @@ impl<'s> ProguardMapper<'s> { let key = (obfuscated, arguments, original); if unique_methods.insert(key) { members - .mappings_by_params + .by_params .entry(arguments) .or_insert_with(|| Vec::with_capacity(1)) - .push(member_mapping); + .push(member); } } // end ProguardRecord::Method - _ => {} + ProguardRecord::Field { .. } => {} } } - if !class.original.is_empty() { - classes.insert(class.obfuscated, class); + + if let Some(name) = current_class_name { + classes.insert(name, current_class); + } + + let mut class_mappings = HashMap::new(); + + for (obfuscated, original) in class_name_mapping { + let Some(class) = classes.get_mut(&original) else { + continue; + }; + + let class_mapping: &mut ClassMapping = class_mappings.entry(obfuscated.0).or_default(); + + class_mapping.original = original.0; + + let class_members = std::mem::take(&mut class.members); + + for (obfuscated_method, members) in class_members { + let method_mappings = class_mapping + .members + .entry(obfuscated_method.0) + .or_default(); + for Member { + method, + startline, + endline, + original_startline, + original_endline, + } in members.all + { + let original_class = classes.get(&method.class); + let original_class_name = + Some(&method.class).filter(|cn| **cn != original).copied(); + let member_mapping = MemberMapping { + startline, + endline, + original_class: original_class_name.map(|name| name.0), + original_file: original_class.and_then(|class| class.source_file), + original: method.name.0, + original_startline, + original_endline, + }; + + method_mappings.all_mappings.push(member_mapping); + } + + for (args, arg_members) in members.by_params { + let arg_mappings = method_mappings.mappings_by_params.entry(args).or_default(); + for Member { + method, + startline, + endline, + original_startline, + original_endline, + } in arg_members + { + let original_class = classes.get(&method.class); + let original_class_name = + Some(&method.class).filter(|cn| **cn != original).copied(); + let member_mapping = MemberMapping { + startline, + endline, + original_class: original_class_name.map(|name| name.0), + original_file: original_class.and_then(|class| class.source_file), + original: method.name.0, + original_startline, + original_endline, + }; + + arg_mappings.push(member_mapping); + } + } + } } - Self { classes } + Self { + classes: class_mappings, + } } /// Remaps an obfuscated Class. @@ -617,8 +756,8 @@ Caused by: com.example.MainFragment$EngineFailureException: Engines overheating let mapper = ProguardMapper::from(mapping); assert_eq!( - expect, - mapper.remap_stacktrace_typed(&stacktrace).to_string() + mapper.remap_stacktrace_typed(&stacktrace).to_string(), + expect ); } @@ -654,6 +793,6 @@ Caused by: com.example.MainFragment$EngineFailureException: Engines overheating let mapper = ProguardMapper::from(mapping); - assert_eq!(expect, mapper.remap_stacktrace(stacktrace).unwrap()); + assert_eq!(mapper.remap_stacktrace(stacktrace).unwrap(), expect); } } diff --git a/tests/callback.rs b/tests/callback.rs index 31c3fd0..ef349a2 100644 --- a/tests/callback.rs +++ b/tests/callback.rs @@ -18,20 +18,18 @@ fn test_method_matches_callback() { assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.EditActivity", "onCreate$lambda$1", 37, - "EditActivity", ) ); assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.EditActivity$$InternalSyntheticLambda$1$ebaa538726b99bb77e0f5e7c86443911af17d6e5be2b8771952ae0caa4ff2ac7$0", "onMenuItemClick", 0, - "EditActivity", ) ); assert_eq!(mapped.next(), None); @@ -52,38 +50,34 @@ fn test_method_matches_callback_extra_class() { assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.TestSourceContext", "test2", 10, - "TestSourceContext", ) ); assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.TestSourceContext", "test", 6, - "TestSourceContext", ) ); assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.EditActivity", "onCreate$lambda$1", 38, - "EditActivity", ) ); assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.EditActivity$$InternalSyntheticLambda$1$ebaa538726b99bb77e0f5e7c86443911af17d6e5be2b8771952ae0caa4ff2ac7$0", "onMenuItemClick", 0, - "EditActivity", ) ); assert_eq!(mapped.next(), None); @@ -107,25 +101,23 @@ fn test_method_matches_callback_inner_class() { "io.sentry.samples.instrumentation.ui.EditActivity$InnerEditActivityClass", "testInner", 19, - "EditActivity", + "EditActivity.kt", ) ); assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.EditActivity", "onCreate$lambda$1", 45, - "EditActivity", ) ); assert_eq!( mapped.next().unwrap(), - StackFrame::with_file( + StackFrame::new( "io.sentry.samples.instrumentation.ui.EditActivity$$InternalSyntheticLambda$1$ebaa538726b99bb77e0f5e7c86443911af17d6e5be2b8771952ae0caa4ff2ac7$0", "onMenuItemClick", 0, - "EditActivity", ) ); assert_eq!(mapped.next(), None); From 45f2aba9383f633db74a1efccdfef9a03658b86d Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Jul 2025 14:40:25 +0200 Subject: [PATCH 2/7] Add builder module --- src/builder.rs | 214 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/mapper.rs | 208 ++++------------------------------------------- 3 files changed, 230 insertions(+), 193 deletions(-) create mode 100644 src/builder.rs diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..1a4fae9 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,214 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{mapping::R8Header, ProguardMapping, ProguardRecord}; + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub(crate) struct ObfuscatedName<'s>(&'s str); + +impl<'s> ObfuscatedName<'s> { + pub(crate) fn as_str(&self) -> &'s str { + self.0 + } +} + +impl std::ops::Deref for ObfuscatedName<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub(crate) struct OriginalName<'s>(&'s str); + +impl<'s> OriginalName<'s> { + pub(crate) fn as_str(&self) -> &'s str { + self.0 + } +} + +impl std::ops::Deref for OriginalName<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct ClassInfo<'s> { + pub(crate) source_file: Option<&'s str>, + pub(crate) members: HashMap, Members<'s>>, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub(crate) struct MethodKey<'s> { + pub(crate) class: OriginalName<'s>, + pub(crate) name: OriginalName<'s>, + pub(crate) arguments: &'s str, +} + +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct MethodInfo {} + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Member<'s> { + pub(crate) method: MethodKey<'s>, + pub(crate) startline: usize, + pub(crate) endline: usize, + pub(crate) original_startline: usize, + pub(crate) original_endline: Option, +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct Members<'s> { + pub(crate) all: Vec>, + pub(crate) by_params: HashMap<&'s str, Vec>>, +} + +#[derive(Clone, Debug)] +pub(crate) struct ParsedProguardMapping<'s> { + pub(crate) class_names: HashMap, OriginalName<'s>>, + pub(crate) classes: HashMap, ClassInfo<'s>>, +} + +impl<'s> ParsedProguardMapping<'s> { + pub(crate) fn parse(mapping: ProguardMapping<'s>, initialize_param_mapping: bool) -> Self { + let mut classes = HashMap::new(); + let mut methods = HashMap::new(); + let mut class_names = HashMap::new(); + // let mut method_name_mapping: HashMap::new(); + let mut current_class_name = None; + let mut current_class = ClassInfo::default(); + let mut unique_methods: HashSet<(&str, &str, &str)> = HashSet::new(); + + let mut records = mapping.iter().filter_map(Result::ok).peekable(); + + while let Some(record) = records.next() { + match record { + ProguardRecord::Field { .. } => {} + ProguardRecord::Header { .. } => {} + ProguardRecord::R8Header(_) => {} + ProguardRecord::Class { + original, + obfuscated, + } => { + // Flush the previous class if there is one. + if let Some(name) = current_class_name { + classes.insert(name, current_class); + } + let key = OriginalName(original); + current_class_name = Some(key); + current_class = ClassInfo::default(); + class_names.insert(ObfuscatedName(obfuscated), key); + unique_methods.clear(); + + // consume R8 headers attached to this class + while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() { + match r8_header { + R8Header::SourceFile { file_name } => { + current_class.source_file = Some(file_name); + } + R8Header::Other => {} + } + + records.next(); + } + } + + ProguardRecord::Method { + original, + obfuscated, + original_class, + line_mapping, + arguments, + .. + } => { + let current_line = if initialize_param_mapping { + line_mapping + } else { + None + }; + // in case the mapping has no line records, we use `0` here. + let (startline, endline) = + line_mapping.as_ref().map_or((0, 0), |line_mapping| { + (line_mapping.startline, line_mapping.endline) + }); + let (original_startline, original_endline) = + line_mapping.map_or((0, None), |line_mapping| { + match line_mapping.original_startline { + Some(original_startline) => { + (original_startline, line_mapping.original_endline) + } + None => (line_mapping.startline, Some(line_mapping.endline)), + } + }); + + let members = current_class + .members + .entry(ObfuscatedName(obfuscated)) + .or_default(); + + let method = MethodKey { + class: original_class + .map(OriginalName) + .or(current_class_name) + .unwrap(), + name: OriginalName(original), + arguments, + }; + + let _method_info: &mut MethodInfo = methods.entry(method).or_default(); + let member = Member { + method, + startline, + endline, + original_startline, + original_endline, + }; + + members.all.push(member); + + if !initialize_param_mapping { + continue; + } + // If the next line has the same leading line range then this method + // has been inlined by the code minification process, as a result + // it can't show in method traces and can be safely ignored. + if let Some(ProguardRecord::Method { + line_mapping: Some(next_line), + .. + }) = records.peek() + { + if let Some(current_line_mapping) = current_line { + if (current_line_mapping.startline == next_line.startline) + && (current_line_mapping.endline == next_line.endline) + { + continue; + } + } + } + + let key = (obfuscated, arguments, original); + if unique_methods.insert(key) { + members + .by_params + .entry(arguments) + .or_insert_with(|| Vec::with_capacity(1)) + .push(member); + } + } // end ProguardRecord::Method + } + } + + if let Some(name) = current_class_name { + classes.insert(name, current_class); + } + + Self { + classes, + class_names, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9cd2a92..68d1371 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ #![warn(missing_docs)] +mod builder; mod cache; mod java; mod mapper; diff --git a/src/mapper.rs b/src/mapper.rs index 6cb8cdd..ff8ddcd 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -1,12 +1,11 @@ use std::collections::HashMap; -use std::collections::HashSet; use std::fmt; use std::fmt::{Error as FmtError, Write}; use std::iter::FusedIterator; +use crate::builder::{Member, ParsedProguardMapping}; use crate::java; -use crate::mapping::R8Header; -use crate::mapping::{ProguardMapping, ProguardRecord}; +use crate::mapping::ProguardMapping; use crate::stacktrace::{self, StackFrame, StackTrace, Throwable}; /// A deobfuscated method signature. @@ -52,59 +51,6 @@ impl fmt::Display for DeobfuscatedSignature { } } -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -struct ObfuscatedName<'s>(&'s str); - -impl std::ops::Deref for ObfuscatedName<'_> { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -struct OriginalName<'s>(&'s str); - -impl std::ops::Deref for OriginalName<'_> { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -#[derive(Clone, Debug, Default)] -struct ClassInfo<'s> { - source_file: Option<&'s str>, - members: HashMap, Members<'s>>, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -struct MethodKey<'s> { - class: OriginalName<'s>, - name: OriginalName<'s>, - arguments: &'s str, -} - -#[derive(Clone, Debug, Default)] -struct MethodInfo {} - -#[derive(Clone, Debug)] -struct Member<'s> { - method: MethodKey<'s>, - startline: usize, - endline: usize, - original_startline: usize, - original_endline: Option, -} - -#[derive(Clone, Debug, Default)] -struct Members<'s> { - all: Vec>, - by_params: HashMap<&'s str, Vec>>, -} - #[derive(Clone, Debug, PartialEq, Eq, Hash)] struct MemberMapping<'s> { startline: usize, @@ -276,153 +222,29 @@ impl<'s> ProguardMapper<'s> { mapping: ProguardMapping<'s>, initialize_param_mapping: bool, ) -> Self { - let mut classes = HashMap::new(); - let mut methods = HashMap::new(); - let mut class_name_mapping = HashMap::new(); - // let mut method_name_mapping: HashMap::new(); - let mut current_class_name = None; - let mut current_class = ClassInfo::default(); - let mut unique_methods: HashSet<(&str, &str, &str)> = HashSet::new(); - - let mut records = mapping.iter().filter_map(Result::ok).peekable(); - while let Some(record) = records.next() { - match record { - ProguardRecord::Header { .. } => {} - ProguardRecord::R8Header(_) => {} - ProguardRecord::Class { - original, - obfuscated, - } => { - // Flush the previous class if there is one. - if let Some(name) = current_class_name { - classes.insert(name, current_class); - } - let key = OriginalName(original); - current_class_name = Some(key); - current_class = ClassInfo::default(); - class_name_mapping.insert(ObfuscatedName(obfuscated), key); - unique_methods.clear(); - - // consume R8 headers attached to this class - while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() { - match r8_header { - R8Header::SourceFile { file_name } => { - current_class.source_file = Some(file_name); - } - R8Header::Other => {} - } - - records.next(); - } - } - - ProguardRecord::Method { - original, - obfuscated, - original_class, - line_mapping, - arguments, - .. - } => { - let current_line = if initialize_param_mapping { - line_mapping - } else { - None - }; - // in case the mapping has no line records, we use `0` here. - let (startline, endline) = - line_mapping.as_ref().map_or((0, 0), |line_mapping| { - (line_mapping.startline, line_mapping.endline) - }); - let (original_startline, original_endline) = - line_mapping.map_or((0, None), |line_mapping| { - match line_mapping.original_startline { - Some(original_startline) => { - (original_startline, line_mapping.original_endline) - } - None => (line_mapping.startline, Some(line_mapping.endline)), - } - }); - - let members = current_class - .members - .entry(ObfuscatedName(obfuscated)) - .or_default(); - - let method = MethodKey { - class: original_class - .map(OriginalName) - .or(current_class_name) - .unwrap(), - name: OriginalName(original), - arguments, - }; - - let _method_info: &mut MethodInfo = methods.entry(method).or_default(); - let member = Member { - method, - startline, - endline, - original_startline, - original_endline, - }; - - members.all.push(member.clone()); - - if !initialize_param_mapping { - continue; - } - // If the next line has the same leading line range then this method - // has been inlined by the code minification process, as a result - // it can't show in method traces and can be safely ignored. - if let Some(ProguardRecord::Method { - line_mapping: Some(next_line), - .. - }) = records.peek() - { - if let Some(current_line_mapping) = current_line { - if (current_line_mapping.startline == next_line.startline) - && (current_line_mapping.endline == next_line.endline) - { - continue; - } - } - } - - let key = (obfuscated, arguments, original); - if unique_methods.insert(key) { - members - .by_params - .entry(arguments) - .or_insert_with(|| Vec::with_capacity(1)) - .push(member); - } - } // end ProguardRecord::Method - ProguardRecord::Field { .. } => {} - } - } - - if let Some(name) = current_class_name { - classes.insert(name, current_class); - } + let ParsedProguardMapping { + class_names, + mut classes, + } = ParsedProguardMapping::parse(mapping, initialize_param_mapping); let mut class_mappings = HashMap::new(); - for (obfuscated, original) in class_name_mapping { + for (obfuscated, original) in class_names { let Some(class) = classes.get_mut(&original) else { continue; }; - let class_mapping: &mut ClassMapping = class_mappings.entry(obfuscated.0).or_default(); + let class_mapping: &mut ClassMapping = + class_mappings.entry(obfuscated.as_str()).or_default(); - class_mapping.original = original.0; + class_mapping.original = original.as_str(); let class_members = std::mem::take(&mut class.members); for (obfuscated_method, members) in class_members { let method_mappings = class_mapping .members - .entry(obfuscated_method.0) + .entry(obfuscated_method.as_str()) .or_default(); for Member { method, @@ -438,9 +260,9 @@ impl<'s> ProguardMapper<'s> { let member_mapping = MemberMapping { startline, endline, - original_class: original_class_name.map(|name| name.0), + original_class: original_class_name.map(|name| name.as_str()), original_file: original_class.and_then(|class| class.source_file), - original: method.name.0, + original: method.name.as_str(), original_startline, original_endline, }; @@ -464,9 +286,9 @@ impl<'s> ProguardMapper<'s> { let member_mapping = MemberMapping { startline, endline, - original_class: original_class_name.map(|name| name.0), + original_class: original_class_name.map(|name| name.as_str()), original_file: original_class.and_then(|class| class.source_file), - original: method.name.0, + original: method.name.as_str(), original_startline, original_endline, }; From 93da98ef841245a5ac37b1165d28d79296acf5df Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Jul 2025 15:50:11 +0200 Subject: [PATCH 3/7] ref --- src/builder.rs | 49 +++++++++-------- src/mapper.rs | 141 ++++++++++++++++++++++++------------------------- 2 files changed, 96 insertions(+), 94 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 1a4fae9..56b2f7a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -39,7 +39,6 @@ impl std::ops::Deref for OriginalName<'_> { #[derive(Clone, Debug, Default)] pub(crate) struct ClassInfo<'s> { pub(crate) source_file: Option<&'s str>, - pub(crate) members: HashMap, Members<'s>>, } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] @@ -67,18 +66,17 @@ pub(crate) struct Members<'s> { pub(crate) by_params: HashMap<&'s str, Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub(crate) struct ParsedProguardMapping<'s> { pub(crate) class_names: HashMap, OriginalName<'s>>, pub(crate) classes: HashMap, ClassInfo<'s>>, + pub(crate) methods: HashMap, MethodInfo>, + pub(crate) members: HashMap<(ObfuscatedName<'s>, ObfuscatedName<'s>), Members<'s>>, } impl<'s> ParsedProguardMapping<'s> { pub(crate) fn parse(mapping: ProguardMapping<'s>, initialize_param_mapping: bool) -> Self { - let mut classes = HashMap::new(); - let mut methods = HashMap::new(); - let mut class_names = HashMap::new(); - // let mut method_name_mapping: HashMap::new(); + let mut slf = Self::default(); let mut current_class_name = None; let mut current_class = ClassInfo::default(); let mut unique_methods: HashSet<(&str, &str, &str)> = HashSet::new(); @@ -95,13 +93,14 @@ impl<'s> ParsedProguardMapping<'s> { obfuscated, } => { // Flush the previous class if there is one. - if let Some(name) = current_class_name { - classes.insert(name, current_class); + if let Some((obfuscated, original)) = current_class_name { + slf.class_names.insert(obfuscated, original); + slf.classes.insert(original, current_class); } - let key = OriginalName(original); - current_class_name = Some(key); + let new_orig = OriginalName(original); + let new_obfus = ObfuscatedName(obfuscated); + current_class_name = Some((new_obfus, new_orig)); current_class = ClassInfo::default(); - class_names.insert(ObfuscatedName(obfuscated), key); unique_methods.clear(); // consume R8 headers attached to this class @@ -145,21 +144,28 @@ impl<'s> ParsedProguardMapping<'s> { } }); - let members = current_class + let Some((current_class_obfuscated, current_class_original)) = + current_class_name + else { + return Self::default(); + }; + + let members = slf .members - .entry(ObfuscatedName(obfuscated)) + .entry((current_class_obfuscated, ObfuscatedName(obfuscated))) .or_default(); let method = MethodKey { class: original_class .map(OriginalName) - .or(current_class_name) - .unwrap(), + .unwrap_or(current_class_original), name: OriginalName(original), arguments, }; - let _method_info: &mut MethodInfo = methods.entry(method).or_default(); + // This does nothing for now because we are not saving any per-method information. + let _method_info: &mut MethodInfo = slf.methods.entry(method).or_default(); + let member = Member { method, startline, @@ -202,13 +208,12 @@ impl<'s> ParsedProguardMapping<'s> { } } - if let Some(name) = current_class_name { - classes.insert(name, current_class); + // Flush the last class + if let Some((obfuscated, original)) = current_class_name { + slf.class_names.insert(obfuscated, original); + slf.classes.insert(original, current_class); } - Self { - classes, - class_names, - } + slf } } diff --git a/src/mapper.rs b/src/mapper.rs index ff8ddcd..e57b698 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -222,79 +222,48 @@ impl<'s> ProguardMapper<'s> { mapping: ProguardMapping<'s>, initialize_param_mapping: bool, ) -> Self { - let ParsedProguardMapping { - class_names, - mut classes, - } = ParsedProguardMapping::parse(mapping, initialize_param_mapping); - - let mut class_mappings = HashMap::new(); - - for (obfuscated, original) in class_names { - let Some(class) = classes.get_mut(&original) else { - continue; - }; - - let class_mapping: &mut ClassMapping = - class_mappings.entry(obfuscated.as_str()).or_default(); - - class_mapping.original = original.as_str(); - - let class_members = std::mem::take(&mut class.members); - - for (obfuscated_method, members) in class_members { - let method_mappings = class_mapping - .members - .entry(obfuscated_method.as_str()) - .or_default(); - for Member { - method, - startline, - endline, - original_startline, - original_endline, - } in members.all - { - let original_class = classes.get(&method.class); - let original_class_name = - Some(&method.class).filter(|cn| **cn != original).copied(); - let member_mapping = MemberMapping { - startline, - endline, - original_class: original_class_name.map(|name| name.as_str()), - original_file: original_class.and_then(|class| class.source_file), - original: method.name.as_str(), - original_startline, - original_endline, - }; - - method_mappings.all_mappings.push(member_mapping); - } + let parsed = ParsedProguardMapping::parse(mapping, initialize_param_mapping); + + // Initialize class mappings with obfuscated -> original name data. The mappings will be filled in afterwards. + let mut class_mappings: HashMap<&str, ClassMapping<'s>> = parsed + .class_names + .iter() + .map(|(obfuscated, original)| { + ( + obfuscated.as_str(), + ClassMapping { + original: original.as_str(), + ..Default::default() + }, + ) + }) + .collect(); + + for ((obfuscated_class, obfuscated_method), members) in &parsed.members { + let class_mapping = class_mappings.entry(obfuscated_class.as_str()).or_default(); + + let method_mappings = class_mapping + .members + .entry(obfuscated_method.as_str()) + .or_default(); + + for member in members.all.iter().copied() { + method_mappings.all_mappings.push(Self::resolve_mapping( + &parsed, + class_mapping.original, + member, + )); + } - for (args, arg_members) in members.by_params { - let arg_mappings = method_mappings.mappings_by_params.entry(args).or_default(); - for Member { - method, - startline, - endline, - original_startline, - original_endline, - } in arg_members - { - let original_class = classes.get(&method.class); - let original_class_name = - Some(&method.class).filter(|cn| **cn != original).copied(); - let member_mapping = MemberMapping { - startline, - endline, - original_class: original_class_name.map(|name| name.as_str()), - original_file: original_class.and_then(|class| class.source_file), - original: method.name.as_str(), - original_startline, - original_endline, - }; - - arg_mappings.push(member_mapping); - } + for (args, param_members) in members.by_params.iter() { + let param_mappings = method_mappings.mappings_by_params.entry(args).or_default(); + + for member in param_members { + param_mappings.push(Self::resolve_mapping( + &parsed, + class_mapping.original, + *member, + )); } } } @@ -304,6 +273,34 @@ impl<'s> ProguardMapper<'s> { } } + fn resolve_mapping( + parsed: &ParsedProguardMapping<'s>, + current_class_name: &str, + member: Member<'s>, + ) -> MemberMapping<'s> { + let original_file = parsed + .classes + .get(&member.method.class) + .and_then(|class| class.source_file); + + // Only fill in `original_class` if it is _not_ the current class + let original_class_name = if member.method.class.as_str() != current_class_name { + Some(member.method.class.as_str()) + } else { + None + }; + + MemberMapping { + startline: member.startline, + endline: member.endline, + original_class: original_class_name, + original_file, + original: member.method.name.as_str(), + original_startline: member.original_startline, + original_endline: member.original_endline, + } + } + /// Remaps an obfuscated Class. /// /// This works on the fully-qualified name of the class, with its complete From 79d39880bd29cab807db70855a77c8c122938ecf Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Jul 2025 16:55:52 +0200 Subject: [PATCH 4/7] WIP cache --- src/cache/mod.rs | 6 +- src/cache/raw.rs | 237 +++++++++++++++++++++++------------------------ src/mapping.rs | 2 +- 3 files changed, 120 insertions(+), 125 deletions(-) diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 8566abd..d11b3c4 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -467,7 +467,7 @@ fn iterate_with_lines<'a>( .read_string(member.original_class_offset) .unwrap_or(frame.class); - let file = if member.original_file_offset != u32::MAX { + let file = if dbg!(member.original_file_offset) != u32::MAX { let Ok(file_name) = cache.read_string(member.original_file_offset) else { continue; }; @@ -477,12 +477,12 @@ fn iterate_with_lines<'a>( } else { Some(file_name) } - } else if member.original_class_offset != u32::MAX { + } else if dbg!(member.original_class_offset) != u32::MAX { // when an inlined function is from a foreign class, we // don’t know the file it is defined in. None } else { - frame.file + dbg!(frame.file) }; let Ok(method) = cache.read_string(member.original_name_offset) else { diff --git a/src/cache/raw.rs b/src/cache/raw.rs index d965bec..af40fa3 100644 --- a/src/cache/raw.rs +++ b/src/cache/raw.rs @@ -3,6 +3,7 @@ use std::io::Write; use watto::{Pod, StringTable}; +use crate::builder::{self, ParsedProguardMapping}; use crate::mapping::R8Header; use crate::{ProguardMapping, ProguardRecord}; @@ -188,128 +189,87 @@ impl<'data> ProguardCache<'data> { /// Writes a [`ProguardMapping`] into a writer in the proguard cache format. pub fn write(mapping: &ProguardMapping, writer: &mut W) -> std::io::Result<()> { let mut string_table = StringTable::new(); - let mut classes: BTreeMap<&str, ClassInProgress> = BTreeMap::new(); - // Create an empty [`ClassInProgress`]; this gets updated as we parse method records. - let mut current_class = ClassInProgress::default(); - - let mut records = mapping.iter().filter_map(Result::ok).peekable(); - while let Some(record) = records.next() { - match record { - ProguardRecord::R8Header(R8Header::SourceFile { file_name }) => { - current_class.class.file_name_offset = string_table.insert(file_name) as u32; - } - ProguardRecord::Header { .. } => {} - ProguardRecord::R8Header(R8Header::Other) => {} - ProguardRecord::Class { - original, - obfuscated, - } => { - // Finalize the previous class, but only if it has a name (otherwise it's the dummy class we created at the beginning). - if !current_class.name.is_empty() { - classes.insert(current_class.name, current_class); - } - - let obfuscated_name_offset = string_table.insert(obfuscated) as u32; - let original_name_offset = string_table.insert(original) as u32; - - // Create a new `ClassInProgress` for the record we just encountered. - current_class = ClassInProgress { - name: obfuscated, - class: Class { - original_name_offset, - obfuscated_name_offset, - ..Default::default() - }, - ..Default::default() - }; - } - ProguardRecord::Method { - original, - obfuscated, - original_class, - line_mapping, - arguments, - .. - } => { - // In case the mapping has no line records, we use `0` here. - let (startline, endline) = line_mapping.map_or((0, 0), |line_mapping| { - (line_mapping.startline as u32, line_mapping.endline as u32) - }); - let (original_startline, original_endline) = - line_mapping.map_or((0, u32::MAX), |line_mapping| { - match line_mapping.original_startline { - Some(original_startline) => ( - original_startline as u32, - line_mapping.original_endline.map_or(u32::MAX, |l| l as u32), - ), - None => { - (line_mapping.startline as u32, line_mapping.endline as u32) - } - } - }); - - let obfuscated_name_offset = string_table.insert(obfuscated) as u32; - let original_name_offset = string_table.insert(original) as u32; - let original_class_offset = original_class.map_or(u32::MAX, |class_name| { - string_table.insert(class_name) as u32 - }); - let params_offset = string_table.insert(arguments) as u32; - let original_file_offset = current_class.class.file_name_offset; - let member = Member { - obfuscated_name_offset, - startline, - endline, - original_class_offset, - original_file_offset, + let parsed = ParsedProguardMapping::parse(*mapping, true); + + // Initialize class mappings with obfuscated -> original name data. The mappings will be filled in afterwards. + let mut classes: BTreeMap<&str, ClassInProgress> = parsed + .class_names + .iter() + .map(|(obfuscated, original)| { + let obfuscated_name_offset = string_table.insert(obfuscated.as_str()) as u32; + let original_name_offset = string_table.insert(original.as_str()) as u32; + let class = ClassInProgress { + class: Class { original_name_offset, - original_startline, - original_endline, - params_offset, - }; - - current_class - .members - .entry(obfuscated) - .or_default() - .push(member.clone()); - current_class.class.members_len += 1; - - // If the next line has the same leading line range then this method - // has been inlined by the code minification process, as a result - // it can't show in method traces and can be safely ignored. - if let Some(ProguardRecord::Method { - line_mapping: Some(next_line), - .. - }) = records.peek() - { - if let Some(current_line_mapping) = line_mapping { - if (current_line_mapping.startline == next_line.startline) - && (current_line_mapping.endline == next_line.endline) - { - continue; - } - } - } - - let key = (obfuscated, arguments, original); - if current_class.unique_methods.insert(key) { - current_class - .members_by_params - .entry((obfuscated, arguments)) - .or_default() - .push(member); - current_class.class.members_by_params_len += 1; - } + obfuscated_name_offset, + ..Default::default() + }, + ..Default::default() + }; + + (obfuscated.as_str(), class) + }) + .collect(); + + for ((obfuscated_class, obfuscated_method), members) in &parsed.members { + let current_class = classes.entry(obfuscated_class.as_str()).or_default(); + + let obfuscated_method_offset = string_table.insert(obfuscated_method.as_str()) as u32; + + let method_mappings = current_class + .members + .entry(obfuscated_method.as_str()) + .or_default(); + + for member in members.all.iter().copied() { + method_mappings.push(Self::resolve_mapping( + &mut string_table, + &parsed, + obfuscated_method_offset, + current_class.class.original_name_offset, + member, + )); + current_class.class.members_len += 1; + } + + for (args, param_members) in members.by_params.iter() { + let param_mappings = current_class + .members_by_params + .entry((obfuscated_method.as_str(), args)) + .or_default(); + + for member in param_members { + param_mappings.push(Self::resolve_mapping( + &mut string_table, + &parsed, + obfuscated_method_offset, + current_class.class.original_name_offset, + *member, + )); + current_class.class.members_by_params_len += 1; } - _ => {} } } - // Flush the last constructed class - if !current_class.name.is_empty() { - classes.insert(current_class.name, current_class); - } + // let obfuscated_name_offset = string_table.insert(obfuscated) as u32; + // let original_name_offset = string_table.insert(original) as u32; + // let original_class_offset = original_class.map_or(u32::MAX, |class_name| { + // string_table.insert(class_name) as u32 + // }); + // let params_offset = string_table.insert(arguments) as u32; + // let original_file_offset = current_class.class.file_name_offset; + // let member = Member { + // obfuscated_name_offset, + // startline, + // endline, + // original_class_offset, + // original_file_offset, + // original_name_offset, + // original_startline, + // original_endline, + // params_offset, + // }; // At this point, we know how many members/members-by-params each class has because we kept count, // but we don't know where each class's entries start. We'll rectify that below. @@ -363,6 +323,46 @@ impl<'data> ProguardCache<'data> { Ok(()) } + fn resolve_mapping( + string_table: &mut StringTable, + parsed: &ParsedProguardMapping<'_>, + obfuscated_name_offset: u32, + current_class_name_offset: u32, + member: builder::Member, + ) -> Member { + let original_file = parsed + .classes + .get(&member.method.class) + .and_then(|class| class.source_file); + + let original_file_offset = + original_file.map_or(u32::MAX, |s| string_table.insert(s) as u32); + let original_name_offset = string_table.insert(member.method.name.as_str()) as u32; + + let method_class_offset = string_table.insert(member.method.class.as_str()) as u32; + + // Only fill in `original_class` if it is _not_ the current class + let original_class_offset = if method_class_offset != current_class_name_offset { + method_class_offset + } else { + u32::MAX + }; + + let params_offset = string_table.insert(member.method.arguments) as u32; + + Member { + startline: member.startline as u32, + endline: member.endline as u32, + original_class_offset, + original_file_offset, + original_name_offset, + original_startline: member.original_startline as u32, + original_endline: member.original_endline.map_or(u32::MAX, |l| l as u32), + obfuscated_name_offset, + params_offset, + } + } + /// Tests the integrity of this cache. /// /// Specifically it checks the following: @@ -412,15 +412,10 @@ impl<'data> ProguardCache<'data> { /// A class that is currently being constructed in the course of writing a [`ProguardCache`]. #[derive(Debug, Clone, Default)] struct ClassInProgress<'data> { - /// The name of the class being constructed. - name: &'data str, /// The class record. class: Class, /// The members records for the class, grouped by method name. members: BTreeMap<&'data str, Vec>, /// The member records for the class, grouped by method name and parameter string. members_by_params: BTreeMap<(&'data str, &'data str), Vec>, - /// A map to keep track of which combinations of (obfuscated method name, original method name, parameters) - /// we have already seen for this class. - unique_methods: HashSet<(&'data str, &'data str, &'data str)>, } diff --git a/src/mapping.rs b/src/mapping.rs index 690a27b..1da08b3 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -134,7 +134,7 @@ impl<'s> MappingSummary<'s> { } /// A Proguard Mapping file. -#[derive(Clone, Default)] +#[derive(Clone, Copy, Default)] pub struct ProguardMapping<'s> { source: &'s [u8], } From d8fb850af0f0be02075269dfae1fb6aa798582b0 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Fri, 4 Jul 2025 14:03:36 +0200 Subject: [PATCH 5/7] Docs, minor ref --- benches/proguard_parsing.rs | 2 +- src/builder.rs | 54 ++++++++++++++++++++++++++++++------- src/cache/mod.rs | 6 ++--- src/cache/raw.rs | 7 +++-- src/mapper.rs | 2 +- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/benches/proguard_parsing.rs b/benches/proguard_parsing.rs index 341e23e..b118d2d 100644 --- a/benches/proguard_parsing.rs +++ b/benches/proguard_parsing.rs @@ -18,7 +18,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("Proguard Parsing"); group.bench_function("Proguard Mapper", |b| { - b.iter(|| proguard_mapper(black_box(mapping.clone()))) + b.iter(|| proguard_mapper(black_box(mapping))) }); group.bench_function("Proguard Cache creation", |b| { diff --git a/src/builder.rs b/src/builder.rs index 56b2f7a..c1fcebe 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,7 +1,13 @@ +//! Contains functionality for parsing ProGuard mapping files into a +//! structured representation ([`ParsedProguardMapping`]) that can be +//! used to create a [`ProguardMapper`](crate::ProguardMapper) or +//! [`ProguardCache`](crate::ProguardCache). + use std::collections::{HashMap, HashSet}; use crate::{mapping::R8Header, ProguardMapping, ProguardRecord}; +/// Newtype around &str for obfuscated class and method names. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub(crate) struct ObfuscatedName<'s>(&'s str); @@ -19,6 +25,7 @@ impl std::ops::Deref for ObfuscatedName<'_> { } } +/// Newtype around &str for original class and method names. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub(crate) struct OriginalName<'s>(&'s str); @@ -36,41 +43,64 @@ impl std::ops::Deref for OriginalName<'_> { } } +/// Information about a class in a ProGuard file. #[derive(Clone, Debug, Default)] pub(crate) struct ClassInfo<'s> { + /// The source file in which the class is defined. pub(crate) source_file: Option<&'s str>, } +/// A key that uniquely identifies a method. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub(crate) struct MethodKey<'s> { + /// The method's receiver. pub(crate) class: OriginalName<'s>, + /// The method's name. pub(crate) name: OriginalName<'s>, + /// The method's argument string. pub(crate) arguments: &'s str, } +/// Information about a method in a ProGuard file. #[derive(Clone, Copy, Debug, Default)] pub(crate) struct MethodInfo {} +/// A member record in a Proguard file. #[derive(Clone, Copy, Debug)] pub(crate) struct Member<'s> { + /// The method the member refers to. pub(crate) method: MethodKey<'s>, + /// The obfuscated/minified start line. pub(crate) startline: usize, + /// The obfuscated/minified end line. pub(crate) endline: usize, + /// The original start line. pub(crate) original_startline: usize, + /// The original end line. pub(crate) original_endline: Option, } +/// A collection of member records for a particular class +/// and obfuscated method. #[derive(Clone, Debug, Default)] pub(crate) struct Members<'s> { + /// The complete list of members for the class and method. pub(crate) all: Vec>, + /// The complete list of members for the class and method, + /// grouped by arguments string. pub(crate) by_params: HashMap<&'s str, Vec>>, } +/// A parsed representation of a [`ProguardMapping`]. #[derive(Clone, Debug, Default)] pub(crate) struct ParsedProguardMapping<'s> { + /// A mapping from obfuscated to original class names. pub(crate) class_names: HashMap, OriginalName<'s>>, - pub(crate) classes: HashMap, ClassInfo<'s>>, - pub(crate) methods: HashMap, MethodInfo>, + /// A mapping from original class names to class information. + pub(crate) class_infos: HashMap, ClassInfo<'s>>, + /// A mapping from method keys to method information. + pub(crate) method_infos: HashMap, MethodInfo>, + /// A mapping from obfuscated class and method names to members. pub(crate) members: HashMap<(ObfuscatedName<'s>, ObfuscatedName<'s>), Members<'s>>, } @@ -87,7 +117,9 @@ impl<'s> ParsedProguardMapping<'s> { match record { ProguardRecord::Field { .. } => {} ProguardRecord::Header { .. } => {} - ProguardRecord::R8Header(_) => {} + ProguardRecord::R8Header(_) => { + // R8 headers are already handled in the class case below. + } ProguardRecord::Class { original, obfuscated, @@ -95,15 +127,14 @@ impl<'s> ParsedProguardMapping<'s> { // Flush the previous class if there is one. if let Some((obfuscated, original)) = current_class_name { slf.class_names.insert(obfuscated, original); - slf.classes.insert(original, current_class); + slf.class_infos.insert(original, current_class); } - let new_orig = OriginalName(original); - let new_obfus = ObfuscatedName(obfuscated); - current_class_name = Some((new_obfus, new_orig)); + + current_class_name = Some((ObfuscatedName(obfuscated), OriginalName(original))); current_class = ClassInfo::default(); unique_methods.clear(); - // consume R8 headers attached to this class + // Consume R8 headers attached to this class. while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() { match r8_header { R8Header::SourceFile { file_name } => { @@ -147,6 +178,9 @@ impl<'s> ParsedProguardMapping<'s> { let Some((current_class_obfuscated, current_class_original)) = current_class_name else { + // `current_class_name` is only `None` before the first class entry is encountered. + // If we hit this case, there's a member record before the first class record, which + // is an error. Properly handling this would be nice here, for now we return an empty `Self`. return Self::default(); }; @@ -164,7 +198,7 @@ impl<'s> ParsedProguardMapping<'s> { }; // This does nothing for now because we are not saving any per-method information. - let _method_info: &mut MethodInfo = slf.methods.entry(method).or_default(); + let _method_info: &mut MethodInfo = slf.method_infos.entry(method).or_default(); let member = Member { method, @@ -211,7 +245,7 @@ impl<'s> ParsedProguardMapping<'s> { // Flush the last class if let Some((obfuscated, original)) = current_class_name { slf.class_names.insert(obfuscated, original); - slf.classes.insert(original, current_class); + slf.class_infos.insert(original, current_class); } slf diff --git a/src/cache/mod.rs b/src/cache/mod.rs index d11b3c4..8566abd 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -467,7 +467,7 @@ fn iterate_with_lines<'a>( .read_string(member.original_class_offset) .unwrap_or(frame.class); - let file = if dbg!(member.original_file_offset) != u32::MAX { + let file = if member.original_file_offset != u32::MAX { let Ok(file_name) = cache.read_string(member.original_file_offset) else { continue; }; @@ -477,12 +477,12 @@ fn iterate_with_lines<'a>( } else { Some(file_name) } - } else if dbg!(member.original_class_offset) != u32::MAX { + } else if member.original_class_offset != u32::MAX { // when an inlined function is from a foreign class, we // don’t know the file it is defined in. None } else { - dbg!(frame.file) + frame.file }; let Ok(method) = cache.read_string(member.original_name_offset) else { diff --git a/src/cache/raw.rs b/src/cache/raw.rs index af40fa3..ba635fa 100644 --- a/src/cache/raw.rs +++ b/src/cache/raw.rs @@ -1,11 +1,10 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; use std::io::Write; use watto::{Pod, StringTable}; use crate::builder::{self, ParsedProguardMapping}; -use crate::mapping::R8Header; -use crate::{ProguardMapping, ProguardRecord}; +use crate::ProguardMapping; use super::{CacheError, CacheErrorKind}; @@ -331,7 +330,7 @@ impl<'data> ProguardCache<'data> { member: builder::Member, ) -> Member { let original_file = parsed - .classes + .class_infos .get(&member.method.class) .and_then(|class| class.source_file); diff --git a/src/mapper.rs b/src/mapper.rs index e57b698..d476e85 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -279,7 +279,7 @@ impl<'s> ProguardMapper<'s> { member: Member<'s>, ) -> MemberMapping<'s> { let original_file = parsed - .classes + .class_infos .get(&member.method.class) .and_then(|class| class.source_file); From 55e80941239994e40804d9c66181253665ccc2cf Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Fri, 4 Jul 2025 14:32:07 +0200 Subject: [PATCH 6/7] Introduce MethodReceiver enum --- src/builder.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++---- src/cache/raw.rs | 14 ++++------- src/mapper.rs | 28 ++++++++-------------- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index c1fcebe..e8b26b3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -4,6 +4,7 @@ //! [`ProguardCache`](crate::ProguardCache). use std::collections::{HashMap, HashSet}; +use std::hash::Hash; use crate::{mapping::R8Header, ProguardMapping, ProguardRecord}; @@ -50,11 +51,59 @@ pub(crate) struct ClassInfo<'s> { pub(crate) source_file: Option<&'s str>, } +/// The receiver of a method. +/// +/// This enum is used to keep track of whether +/// a method's receiver is the class under which +/// it is encountered (`ThisClass`) or another +/// class (`OtherClass`). +/// +/// # Example +/// Consider this mapping: +/// ```text +/// example.Main -> a: +/// 1:1 run() 1:1 -> a +/// 2:2 example.Other.run() 1:1 -> b +/// ``` +/// The `receiver` of the first method would be +/// `ThisClass("example.Main")` (because it is defined +/// under `"example.Main"` and has no explicit receiver), +/// while the receiver of the second method would be +/// `OtherClass("example.Other")`. +#[derive(Clone, Copy, Debug)] +pub(crate) enum MethodReceiver<'s> { + ThisClass(OriginalName<'s>), + OtherClass(OriginalName<'s>), +} + +impl<'s> MethodReceiver<'s> { + pub(crate) fn name(&self) -> OriginalName<'s> { + match self { + Self::ThisClass(name) => *name, + Self::OtherClass(name) => *name, + } + } +} + +impl PartialEq for MethodReceiver<'_> { + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() + } +} + +impl Eq for MethodReceiver<'_> {} + +impl std::hash::Hash for MethodReceiver<'_> { + fn hash(&self, state: &mut H) { + self.name().hash(state) + } +} + /// A key that uniquely identifies a method. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub(crate) struct MethodKey<'s> { /// The method's receiver. - pub(crate) class: OriginalName<'s>, + pub(crate) receiver: MethodReceiver<'s>, /// The method's name. pub(crate) name: OriginalName<'s>, /// The method's argument string. @@ -190,9 +239,14 @@ impl<'s> ParsedProguardMapping<'s> { .or_default(); let method = MethodKey { - class: original_class - .map(OriginalName) - .unwrap_or(current_class_original), + // Save the receiver name, keeping track of whether it's the current class + // (i.e. the one to which this member record belongs) or another class. + receiver: match original_class { + Some(original_class) => { + MethodReceiver::OtherClass(OriginalName(original_class)) + } + None => MethodReceiver::ThisClass(current_class_original), + }, name: OriginalName(original), arguments, }; diff --git a/src/cache/raw.rs b/src/cache/raw.rs index ba635fa..cb30fbe 100644 --- a/src/cache/raw.rs +++ b/src/cache/raw.rs @@ -226,7 +226,6 @@ impl<'data> ProguardCache<'data> { &mut string_table, &parsed, obfuscated_method_offset, - current_class.class.original_name_offset, member, )); current_class.class.members_len += 1; @@ -243,7 +242,6 @@ impl<'data> ProguardCache<'data> { &mut string_table, &parsed, obfuscated_method_offset, - current_class.class.original_name_offset, *member, )); current_class.class.members_by_params_len += 1; @@ -326,25 +324,21 @@ impl<'data> ProguardCache<'data> { string_table: &mut StringTable, parsed: &ParsedProguardMapping<'_>, obfuscated_name_offset: u32, - current_class_name_offset: u32, member: builder::Member, ) -> Member { let original_file = parsed .class_infos - .get(&member.method.class) + .get(&member.method.receiver.name()) .and_then(|class| class.source_file); let original_file_offset = original_file.map_or(u32::MAX, |s| string_table.insert(s) as u32); let original_name_offset = string_table.insert(member.method.name.as_str()) as u32; - let method_class_offset = string_table.insert(member.method.class.as_str()) as u32; - // Only fill in `original_class` if it is _not_ the current class - let original_class_offset = if method_class_offset != current_class_name_offset { - method_class_offset - } else { - u32::MAX + let original_class_offset = match member.method.receiver { + builder::MethodReceiver::ThisClass(_) => u32::MAX, + builder::MethodReceiver::OtherClass(name) => string_table.insert(name.as_str()) as u32, }; let params_offset = string_table.insert(member.method.arguments) as u32; diff --git a/src/mapper.rs b/src/mapper.rs index d476e85..fae0b5e 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -3,7 +3,7 @@ use std::fmt; use std::fmt::{Error as FmtError, Write}; use std::iter::FusedIterator; -use crate::builder::{Member, ParsedProguardMapping}; +use crate::builder::{Member, MethodReceiver, ParsedProguardMapping}; use crate::java; use crate::mapping::ProguardMapping; use crate::stacktrace::{self, StackFrame, StackTrace, Throwable}; @@ -248,22 +248,16 @@ impl<'s> ProguardMapper<'s> { .or_default(); for member in members.all.iter().copied() { - method_mappings.all_mappings.push(Self::resolve_mapping( - &parsed, - class_mapping.original, - member, - )); + method_mappings + .all_mappings + .push(Self::resolve_mapping(&parsed, member)); } for (args, param_members) in members.by_params.iter() { let param_mappings = method_mappings.mappings_by_params.entry(args).or_default(); for member in param_members { - param_mappings.push(Self::resolve_mapping( - &parsed, - class_mapping.original, - *member, - )); + param_mappings.push(Self::resolve_mapping(&parsed, *member)); } } } @@ -275,25 +269,23 @@ impl<'s> ProguardMapper<'s> { fn resolve_mapping( parsed: &ParsedProguardMapping<'s>, - current_class_name: &str, member: Member<'s>, ) -> MemberMapping<'s> { let original_file = parsed .class_infos - .get(&member.method.class) + .get(&member.method.receiver.name()) .and_then(|class| class.source_file); // Only fill in `original_class` if it is _not_ the current class - let original_class_name = if member.method.class.as_str() != current_class_name { - Some(member.method.class.as_str()) - } else { - None + let original_class = match member.method.receiver { + MethodReceiver::ThisClass(_) => None, + MethodReceiver::OtherClass(original_class_name) => Some(original_class_name.as_str()), }; MemberMapping { startline: member.startline, endline: member.endline, - original_class: original_class_name, + original_class, original_file, original: member.method.name.as_str(), original_startline: member.original_startline, From 80e2ec1a79d1c007d987a5d993a7803af6e07d96 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Fri, 4 Jul 2025 14:36:09 +0200 Subject: [PATCH 7/7] Remove commented code --- src/cache/raw.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/cache/raw.rs b/src/cache/raw.rs index cb30fbe..74286b5 100644 --- a/src/cache/raw.rs +++ b/src/cache/raw.rs @@ -249,25 +249,6 @@ impl<'data> ProguardCache<'data> { } } - // let obfuscated_name_offset = string_table.insert(obfuscated) as u32; - // let original_name_offset = string_table.insert(original) as u32; - // let original_class_offset = original_class.map_or(u32::MAX, |class_name| { - // string_table.insert(class_name) as u32 - // }); - // let params_offset = string_table.insert(arguments) as u32; - // let original_file_offset = current_class.class.file_name_offset; - // let member = Member { - // obfuscated_name_offset, - // startline, - // endline, - // original_class_offset, - // original_file_offset, - // original_name_offset, - // original_startline, - // original_endline, - // params_offset, - // }; - // At this point, we know how many members/members-by-params each class has because we kept count, // but we don't know where each class's entries start. We'll rectify that below.