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 new file mode 100644 index 0000000..e8b26b3 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,307 @@ +//! 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 std::hash::Hash; + +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); + +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 + } +} + +/// Newtype around &str for original class and method names. +#[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 + } +} + +/// 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>, +} + +/// 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) receiver: MethodReceiver<'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>>, + /// 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>>, +} + +impl<'s> ParsedProguardMapping<'s> { + pub(crate) fn parse(mapping: ProguardMapping<'s>, initialize_param_mapping: bool) -> Self { + 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(); + + let mut records = mapping.iter().filter_map(Result::ok).peekable(); + + while let Some(record) = records.next() { + match record { + ProguardRecord::Field { .. } => {} + ProguardRecord::Header { .. } => {} + ProguardRecord::R8Header(_) => { + // R8 headers are already handled in the class case below. + } + ProguardRecord::Class { + original, + obfuscated, + } => { + // Flush the previous class if there is one. + if let Some((obfuscated, original)) = current_class_name { + slf.class_names.insert(obfuscated, original); + slf.class_infos.insert(original, current_class); + } + + current_class_name = Some((ObfuscatedName(obfuscated), OriginalName(original))); + current_class = ClassInfo::default(); + 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 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(); + }; + + let members = slf + .members + .entry((current_class_obfuscated, ObfuscatedName(obfuscated))) + .or_default(); + + let method = MethodKey { + // 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, + }; + + // This does nothing for now because we are not saving any per-method information. + let _method_info: &mut MethodInfo = slf.method_infos.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 + } + } + + // Flush the last class + if let Some((obfuscated, original)) = current_class_name { + slf.class_names.insert(obfuscated, original); + slf.class_infos.insert(original, current_class); + } + + slf + } +} diff --git a/src/cache/raw.rs b/src/cache/raw.rs index d965bec..74286b5 100644 --- a/src/cache/raw.rs +++ b/src/cache/raw.rs @@ -1,10 +1,10 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; use std::io::Write; use watto::{Pod, StringTable}; -use crate::mapping::R8Header; -use crate::{ProguardMapping, ProguardRecord}; +use crate::builder::{self, ParsedProguardMapping}; +use crate::ProguardMapping; use super::{CacheError, CacheErrorKind}; @@ -188,127 +188,65 @@ 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, + member, + )); + current_class.class.members_len += 1; } - } - // Flush the last constructed class - if !current_class.name.is_empty() { - classes.insert(current_class.name, current_class); + 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, + *member, + )); + current_class.class.members_by_params_len += 1; + } + } } // At this point, we know how many members/members-by-params each class has because we kept count, @@ -363,6 +301,42 @@ impl<'data> ProguardCache<'data> { Ok(()) } + fn resolve_mapping( + string_table: &mut StringTable, + parsed: &ParsedProguardMapping<'_>, + obfuscated_name_offset: u32, + member: builder::Member, + ) -> Member { + let original_file = parsed + .class_infos + .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; + + // Only fill in `original_class` if it is _not_ the current class + 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; + + 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 +386,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/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 c085501..fae0b5e 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, MethodReceiver, 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. @@ -63,18 +62,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>>, } @@ -225,122 +222,75 @@ impl<'s> ProguardMapper<'s> { mapping: ProguardMapping<'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 unique_methods: HashSet<(&str, &str, &str)> = HashSet::new(); + 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, member)); + } - 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::Class { - original, - obfuscated, - } => { - if !class.original.is_empty() { - classes.insert(class.obfuscated, class); - } - class = ClassMapping { - original, - obfuscated, - file_name: None, - members: HashMap::new(), - }; - unique_methods.clear(); - } - 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 = class - .members - .entry(obfuscated) - .or_insert_with(|| ClassMembers { - all_mappings: Vec::with_capacity(1), - mappings_by_params: Default::default(), - }); - - let member_mapping = MemberMapping { - startline, - endline, - original_class, - original_file: class.file_name, - original, - original_startline, - original_endline, - }; - members.all_mappings.push(member_mapping.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; - } - } - } + for (args, param_members) in members.by_params.iter() { + let param_mappings = method_mappings.mappings_by_params.entry(args).or_default(); - let key = (obfuscated, arguments, original); - if unique_methods.insert(key) { - members - .mappings_by_params - .entry(arguments) - .or_insert_with(|| Vec::with_capacity(1)) - .push(member_mapping); - } - } // end ProguardRecord::Method - _ => {} + for member in param_members { + param_mappings.push(Self::resolve_mapping(&parsed, *member)); + } } } - if !class.original.is_empty() { - classes.insert(class.obfuscated, class); + + Self { + classes: class_mappings, } + } - Self { classes } + fn resolve_mapping( + parsed: &ParsedProguardMapping<'s>, + member: Member<'s>, + ) -> MemberMapping<'s> { + let original_file = parsed + .class_infos + .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 = 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_file, + original: member.method.name.as_str(), + original_startline: member.original_startline, + original_endline: member.original_endline, + } } /// Remaps an obfuscated Class. @@ -617,8 +567,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 +604,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/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], } 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);