diff --git a/src/builder.rs b/src/builder.rs index 5fa8cc9..2f70138 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -149,12 +149,12 @@ pub(crate) struct RewriteRule<'s> { 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 obfuscated/minified start line, `None` when no minified range prefix was present. + pub(crate) startline: Option, + /// The obfuscated/minified end line, `None` when no minified range prefix was present. + pub(crate) endline: Option, + /// The original start line, `None` when no line mapping was present. + pub(crate) original_startline: Option, /// The original end line. pub(crate) original_endline: Option, /// Optional outline callsite positions map attached to this member. @@ -291,29 +291,29 @@ impl<'s> ParsedProguardMapping<'s> { } else { None }; - // in case the mapping has no line records, we use `0` here. - let (mut startline, mut endline) = - line_mapping.as_ref().map_or((0, 0), |line_mapping| { - (line_mapping.startline, line_mapping.endline) - }); - let (mut original_startline, mut 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 (mut startline, mut endline) = match line_mapping.as_ref() { + Some(lm) => (lm.startline, lm.endline), + None => (None, None), + }; + let (mut original_startline, mut original_endline) = match line_mapping { + None => (None, None), + Some(lm) => match lm.original_startline { + Some(os) => (Some(os), lm.original_endline), + None => (startline, endline), + }, + }; // Normalize inverted ranges independently. - if startline > endline { - std::mem::swap(&mut startline, &mut endline); + if let (Some(s), Some(e)) = (startline, endline) { + if s > e { + startline = Some(e); + endline = Some(s); + } } - if let Some(oe) = original_endline { - if original_startline > oe { - original_endline = Some(original_startline); - original_startline = oe; + if let (Some(os), Some(oe)) = (original_startline, original_endline) { + if os > oe { + original_startline = Some(oe); + original_endline = Some(os); } } diff --git a/src/cache/mod.rs b/src/cache/mod.rs index ac8e0a7..a8f38f2 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -390,9 +390,9 @@ impl<'data> ProguardCache<'data> { for member in mapping_entries { // Check if this member would produce a frame (line matching) let pf_line = prepared_frame.line.unwrap_or(0); - if member.endline == 0 - || (pf_line >= member.startline as usize && pf_line <= member.endline as usize) - { + let startline = member.startline().unwrap_or(0) as usize; + let endline = member.endline().unwrap_or(0) as usize; + if endline == 0 || (pf_line >= startline && pf_line <= endline) { had_mappings = true; rewrite_rules.extend(self.decode_rewrite_rules(member)); } @@ -405,7 +405,7 @@ impl<'data> ProguardCache<'data> { } } - let has_line_info = mapping_entries.iter().any(|m| m.endline > 0); + let has_line_info = mapping_entries.iter().any(|m| m.endline().unwrap_or(0) > 0); Some(( mapping_entries, @@ -631,9 +631,9 @@ impl<'data> ProguardCache<'data> { candidates .iter() .filter(|m| { - m.endline == 0 - || (callsite_line >= m.startline as usize - && callsite_line <= m.endline as usize) + m.endline().unwrap_or(0) == 0 + || (callsite_line >= m.startline().unwrap_or(0) as usize + && callsite_line <= m.endline().unwrap_or(0) as usize) }) .find_map(|m| { self.member_outline_pairs(m) @@ -917,7 +917,7 @@ impl<'r, 'data> RemappedFrameIter<'r, 'data> { NoLineSelection::IterateBase => { let mut mapped = None; for member in members.by_ref() { - if member.endline == 0 { + if member.endline().unwrap_or(0) == 0 { mapped = map_member_without_lines( cache, &frame, @@ -985,28 +985,29 @@ fn iterate_with_lines<'a>( ) -> Option> { let frame_line = frame.line.unwrap_or(0); for member in members { + let member_endline = member.endline().unwrap_or(0) as usize; + let member_startline = member.startline().unwrap_or(0) as usize; // If this method has line mappings, skip base (no-line) entries when we have a concrete line. - if has_line_info && frame_line > 0 && member.endline == 0 { + if has_line_info && frame_line > 0 && member_endline == 0 { continue; } // If the mapping entry has no line range, preserve the input line number (if any). - if member.endline == 0 { + if member_endline == 0 { return map_member_without_lines(cache, frame, member, outer_source_file); } // skip any members which do not match our frames line - if member.endline > 0 - && (frame_line < member.startline as usize || frame_line > member.endline as usize) - { + if member_endline > 0 && (frame_line < member_startline || frame_line > member_endline) { continue; } + let original_startline = member.original_startline().unwrap_or(0) as usize; // parents of inlined frames don't have an `endline`, and // the top inlined frame need to be correctly offset. let line = if member.original_endline == u32::MAX - || member.original_endline == member.original_startline + || member.original_endline as usize == original_startline { - member.original_startline as usize + original_startline } else { - member.original_startline as usize + frame_line - member.startline as usize + original_startline + frame_line - member_startline }; let class = cache @@ -1058,7 +1059,7 @@ enum NoLineSelection<'a> { fn select_no_line_members<'a>(members: &'a [raw::Member]) -> Option> { // Prefer base entries (endline == 0) if present. - let mut base_members = members.iter().filter(|m| m.endline == 0); + let mut base_members = members.iter().filter(|m| m.endline().unwrap_or(0) == 0); if let Some(first_base) = base_members.next() { let all_same = base_members.all(|m| { m.original_class_offset == first_base.original_class_offset @@ -1097,10 +1098,7 @@ fn map_member_without_lines<'a>( let method = cache.read_string(member.original_name_offset).ok()?; let file = synthesize_source_file(class, outer_source_file).map(Cow::Owned); - let original_startline = match member.original_startline { - 0 | u32::MAX => None, - value => Some(value as usize), - }; + let original_startline = member.original_startline().map(|v| v as usize); Some(StackFrame { class, @@ -1109,8 +1107,8 @@ fn map_member_without_lines<'a>( line: Some(resolve_no_line_output_line( frame.line.unwrap_or(0), original_startline, - member.startline as usize, - member.endline as usize, + member.startline().map(|v| v as usize), + member.endline().map(|v| v as usize), )), parameters: frame.parameters, method_synthesized: member.is_synthesized(), @@ -1134,10 +1132,7 @@ fn iterate_without_lines<'a>( // Synthesize from class name (input filename is not reliable) let file = synthesize_source_file(class, outer_source_file).map(Cow::Owned); - let original_startline = match member.original_startline { - 0 | u32::MAX => None, - value => Some(value as usize), - }; + let original_startline = member.original_startline().map(|v| v as usize); Some(StackFrame { class, @@ -1146,8 +1141,8 @@ fn iterate_without_lines<'a>( line: Some(resolve_no_line_output_line( frame.line.unwrap_or(0), original_startline, - member.startline as usize, - member.endline as usize, + member.startline().map(|v| v as usize), + member.endline().map(|v| v as usize), )), parameters: frame.parameters, method_synthesized: member.is_synthesized(), diff --git a/src/cache/raw.rs b/src/cache/raw.rs index 66a18a1..c1fd467 100644 --- a/src/cache/raw.rs +++ b/src/cache/raw.rs @@ -98,15 +98,18 @@ impl Default for Class { } } +/// Sentinel value representing absent/`None` for u32 fields in the binary format. +const NONE_VALUE: u32 = u32::MAX; + /// An entry corresponding to a method line in a proguard cache file. #[derive(Debug, Clone, PartialEq, Eq, Default)] #[repr(C)] pub(crate) struct Member { /// The obfuscated method name (offset into the string section). pub(crate) obfuscated_name_offset: u32, - /// The start of the range covered by this entry (1-based). + /// The start of the range covered by this entry (1-based), `u32::MAX` if absent. pub(crate) startline: u32, - /// The end of the range covered by this entry (inclusive). + /// The end of the range covered by this entry (inclusive), `u32::MAX` if absent. pub(crate) endline: u32, /// The original class name (offset into the string section). pub(crate) original_class_offset: u32, @@ -114,7 +117,7 @@ pub(crate) struct Member { pub(crate) original_file_offset: u32, /// The original method name (offset into the string section). pub(crate) original_name_offset: u32, - /// The original start line (1-based). + /// The original start line (0-based), `u32::MAX` if absent. pub(crate) original_startline: u32, /// The original end line (inclusive). pub(crate) original_endline: u32, @@ -136,6 +139,7 @@ pub(crate) struct Member { /// /// `0` means `false`, all other values mean `true`. pub(crate) is_outline: u8, + /// Reserved space. pub(crate) _reserved: [u8; 2], } @@ -149,6 +153,30 @@ impl Member { pub(crate) fn is_outline(&self) -> bool { self.is_outline != 0 } + /// Returns the startline as `Option`, where `NONE_VALUE` maps to `None`. + pub(crate) fn startline(&self) -> Option { + if self.startline == NONE_VALUE { + None + } else { + Some(self.startline) + } + } + /// Returns the endline as `Option`, where `NONE_VALUE` maps to `None`. + pub(crate) fn endline(&self) -> Option { + if self.endline == NONE_VALUE { + None + } else { + Some(self.endline) + } + } + /// Returns the original_startline as `Option`, where `NONE_VALUE` maps to `None`. + pub(crate) fn original_startline(&self) -> Option { + if self.original_startline == NONE_VALUE { + None + } else { + Some(self.original_startline) + } + } } unsafe impl Pod for Header {} @@ -616,22 +644,22 @@ impl<'data> ProguardCache<'data> { .collect(); let member: Member = Member { - startline: member.startline as u32, - endline: member.endline as u32, + startline: member.startline.map_or(NONE_VALUE, |v| v as u32), + endline: member.endline.map_or(NONE_VALUE, |v| v 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), + original_startline: member.original_startline.map_or(NONE_VALUE, |v| v as u32), + original_endline: member.original_endline.map_or(NONE_VALUE, |l| l as u32), obfuscated_name_offset, params_offset, is_synthesized, is_outline, + _reserved: [0; 2], outline_pairs_offset: 0, outline_pairs_len: 0, rewrite_rules_offset: 0, rewrite_rules_len: 0, - _reserved: [0; 2], }; MemberInProgress { diff --git a/src/mapper.rs b/src/mapper.rs index 6f7e18e..34673ce 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -60,12 +60,12 @@ impl fmt::Display for DeobfuscatedSignature { #[derive(Clone, Debug, PartialEq, Eq)] struct MemberMapping<'s> { - startline: usize, - endline: usize, + startline: Option, + endline: Option, original_class: Option<&'s str>, original_file: Option<&'s str>, original: &'s str, - original_startline: usize, + original_startline: Option, original_endline: Option, is_synthesized: bool, is_outline: bool, @@ -141,18 +141,21 @@ fn map_member_with_lines<'a>( member: &MemberMapping<'a>, ) -> Option> { let frame_line = frame.line.unwrap_or(0); - if member.endline > 0 && (frame_line < member.startline || frame_line > member.endline) { + let member_endline = member.endline.unwrap_or(0); + let member_startline = member.startline.unwrap_or(0); + if member_endline > 0 && (frame_line < member_startline || frame_line > member_endline) { return None; } + let original_startline = member.original_startline.unwrap_or(0); // parents of inlined frames don't have an `endline`, and // the top inlined frame need to be correctly offset. let line = if member.original_endline.is_none() - || member.original_endline == Some(member.original_startline) + || member.original_endline == Some(original_startline) { - member.original_startline + original_startline } else { - member.original_startline + frame_line - member.startline + original_startline + frame_line - member_startline }; let class = member.original_class.unwrap_or(frame.class); @@ -186,14 +189,9 @@ fn map_member_without_lines<'a>( let class = member.original_class.unwrap_or(frame.class); // Synthesize from class name (input filename is not reliable) let file = synthesize_source_file(class, member.outer_source_file).map(Cow::Owned); - let original_startline = if member.original_startline > 0 { - Some(member.original_startline) - } else { - None - }; let line = resolve_no_line_output_line( frame.line.unwrap_or(0), - original_startline, + member.original_startline, member.startline, member.endline, ); @@ -237,7 +235,9 @@ fn select_no_line_members<'a>( mapping_entries: &'a [MemberMapping<'a>], has_line_info: bool, ) -> Option> { - let mut base_members = mapping_entries.iter().filter(|m| m.endline == 0); + let mut base_members = mapping_entries + .iter() + .filter(|m| m.endline.unwrap_or(0) == 0); if has_line_info { if let Some(first_base) = base_members.next() { let all_same = base_members.all(|m| m.original == first_base.original); @@ -298,12 +298,13 @@ fn iterate_with_lines<'a>( ) -> Option> { let frame_line = frame.line.unwrap_or(0); for member in members { + let member_endline = member.endline.unwrap_or(0); // If this method has line mappings, skip base (no-line) entries when we have a concrete line. - if has_line_info && frame_line > 0 && member.endline == 0 { + if has_line_info && frame_line > 0 && member_endline == 0 { continue; } // If the mapping entry has no line range, preserve the input line number (if any). - if member.endline == 0 { + if member_endline == 0 { return Some(map_member_without_lines(frame, member)); } if let Some(mapped) = map_member_with_lines(frame, member) { @@ -493,7 +494,9 @@ impl<'s> ProguardMapper<'s> { candidates .iter() .filter(|m| { - m.endline == 0 || (callsite_line >= m.startline && callsite_line <= m.endline) + m.endline.unwrap_or(0) == 0 + || (callsite_line >= m.startline.unwrap_or(0) + && callsite_line <= m.endline.unwrap_or(0)) }) .find_map(|m| { m.outline_callsite_positions @@ -582,7 +585,7 @@ impl<'s> ProguardMapper<'s> { }; if frame.parameters.is_none() { - let has_line_info = mapping_entries.iter().any(|m| m.endline > 0); + let has_line_info = mapping_entries.iter().any(|m| m.endline.unwrap_or(0) > 0); // If the stacktrace has no line number, treat it as unknown and remap without // applying line filters. If there are base (no-line) mappings present, prefer those. @@ -604,7 +607,10 @@ impl<'s> ProguardMapper<'s> { } } Some(NoLineSelection::IterateBase) => { - for member in mapping_entries.iter().filter(|m| m.endline == 0) { + for member in mapping_entries + .iter() + .filter(|m| m.endline.unwrap_or(0) == 0) + { collected .frames .push(map_member_without_lines(&frame, member)); @@ -617,10 +623,10 @@ impl<'s> ProguardMapper<'s> { } for member in mapping_entries { - if has_line_info && member.endline == 0 { + if has_line_info && member.endline.unwrap_or(0) == 0 { continue; } - if member.endline == 0 { + if member.endline.unwrap_or(0) == 0 { collected .frames .push(map_member_without_lines(&frame, member)); @@ -694,7 +700,10 @@ impl<'s> ProguardMapper<'s> { members.all_mappings.iter() }; - let has_line_info = members.all_mappings.iter().any(|m| m.endline > 0); + let has_line_info = members + .all_mappings + .iter() + .any(|m| m.endline.unwrap_or(0) > 0); RemappedFrameIter::members(frame, mappings, has_line_info) } diff --git a/src/mapping.rs b/src/mapping.rs index f9668d6..ebafa83 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -287,10 +287,10 @@ impl<'s> Iterator for ProguardRecordIter<'s> { /// All line mappings are 1-based and inclusive. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LineMapping { - /// Start Line, 1-based. - pub startline: usize, - /// End Line, inclusive. - pub endline: usize, + /// Start Line, 1-based. `None` when no minified range prefix was present. + pub startline: Option, + /// End Line, inclusive. `None` when no minified range prefix was present. + pub endline: Option, /// The original Start Line. pub original_startline: Option, /// The original End Line. @@ -468,8 +468,8 @@ impl<'s> ProguardRecord<'s> { /// arguments: "", /// original_class: Some("com.example1.domain.MyBean"), /// line_mapping: Some(proguard::LineMapping { - /// startline: 1016, - /// endline: 1016, + /// startline: Some(1016), + /// endline: Some(1016), /// original_startline: Some(16), /// original_endline: Some(16), /// }), @@ -637,16 +637,16 @@ fn parse_proguard_field_or_method( let line_mapping = match (startline, endline, original_startline) { (Some(startline), Some(endline), _) => Some(LineMapping { - startline, - endline, + startline: Some(startline), + endline: Some(endline), original_startline, original_endline, }), // Preserve original line info even when no minified range is present. // This enables this crate to use the original line for no-line mappings. (None, None, Some(original_startline)) => Some(LineMapping { - startline: 0, - endline: 0, + startline: None, + endline: None, original_startline: Some(original_startline), original_endline, }), @@ -981,8 +981,8 @@ mod tests { arguments: "androidx.appcompat.widget.Toolbar", original_class: Some("androidx.appcompat.app.AppCompatDelegateImpl"), line_mapping: Some(LineMapping { - startline: 14, - endline: 15, + startline: Some(14), + endline: Some(15), original_startline: None, original_endline: None, }), @@ -1003,8 +1003,8 @@ mod tests { arguments: "androidx.appcompat.widget.Toolbar", original_class: Some("androidx.appcompat.app.AppCompatDelegateImpl"), line_mapping: Some(LineMapping { - startline: 14, - endline: 15, + startline: Some(14), + endline: Some(15), original_startline: Some(436), original_endline: None, }), @@ -1025,8 +1025,8 @@ mod tests { arguments: "androidx.appcompat.widget.Toolbar", original_class: Some("androidx.appcompat.app.AppCompatDelegateImpl"), line_mapping: Some(LineMapping { - startline: 14, - endline: 15, + startline: Some(14), + endline: Some(15), original_startline: Some(436), original_endline: Some(437), }), @@ -1161,8 +1161,8 @@ androidx.activity.OnBackPressedCallback arguments: "", original_class: None, line_mapping: Some(LineMapping { - startline: 1, - endline: 4, + startline: Some(1), + endline: Some(4), original_startline: Some(184), original_endline: Some(187), }), diff --git a/src/utils.rs b/src/utils.rs index fa0089c..a7db5b7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,12 +4,12 @@ pub(crate) fn resolve_no_line_output_line( frame_line: usize, original_startline: Option, - startline: usize, - endline: usize, + startline: Option, + endline: Option, ) -> usize { if frame_line > 0 { frame_line - } else if startline == 0 && endline == 0 { + } else if startline.unwrap_or(0) == 0 && endline.unwrap_or(0) == 0 { original_startline.unwrap_or(0) } else { 0