Skip to content
26 changes: 21 additions & 5 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ impl std::ops::Deref for OriginalName<'_> {
pub(crate) struct ClassInfo<'s> {
/// The source file in which the class is defined.
pub(crate) source_file: Option<&'s str>,
/// Whether this class was synthesized by the compiler.
pub(crate) is_synthesized: bool,
}

/// The receiver of a method.
Expand Down Expand Up @@ -112,7 +114,10 @@ pub(crate) struct MethodKey<'s> {

/// Information about a method in a ProGuard file.
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct MethodInfo {}
pub(crate) struct MethodInfo {
/// Whether this method was synthesized by the compiler.
pub(crate) is_synthesized: bool,
}

/// A member record in a Proguard file.
#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -167,7 +172,8 @@ impl<'s> ParsedProguardMapping<'s> {
ProguardRecord::Field { .. } => {}
ProguardRecord::Header { .. } => {}
ProguardRecord::R8Header(_) => {
// R8 headers are already handled in the class case below.
// R8 headers can be skipped; they are already
// handled in the branches for `Class` and `Method`.
}
ProguardRecord::Class {
original,
Expand All @@ -187,8 +193,9 @@ impl<'s> ParsedProguardMapping<'s> {
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
match r8_header {
R8Header::SourceFile { file_name } => {
current_class.source_file = Some(file_name);
current_class.source_file = Some(file_name)
}
R8Header::Synthesized => current_class.is_synthesized = true,
R8Header::Other => {}
}

Expand Down Expand Up @@ -251,8 +258,17 @@ impl<'s> ParsedProguardMapping<'s> {
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 method_info: &mut MethodInfo = slf.method_infos.entry(method).or_default();

// Consume R8 headers attached to this method.
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
match r8_header {
R8Header::Synthesized => method_info.is_synthesized = true,
R8Header::SourceFile { .. } | R8Header::Other => {}
}

records.next();
}

let member = Member {
method,
Expand Down
2 changes: 2 additions & 0 deletions src/cache/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl fmt::Debug for ClassDebug<'_, '_> {
.field("obfuscated_name", &self.obfuscated_name())
.field("original_name", &self.original_name())
.field("file_name", &self.file_name())
.field("is_synthesized", &self.raw.is_synthesized())
.finish()
}
}
Expand Down Expand Up @@ -105,6 +106,7 @@ impl fmt::Debug for MemberDebug<'_, '_> {
.field("original_startline", &self.raw.original_startline)
.field("original_endline", &self.original_endline())
.field("params", &self.params())
.field("is_synthesized", &self.raw.is_synthesized())
.finish()
}
}
Expand Down
29 changes: 21 additions & 8 deletions src/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,25 @@
//! it is by offset into this section.
//!
//! ## Class entries
//! A class entry contains an obfuscated and an original name, optionally a file name,
//! and an offset and length for the class's associated records in the `members`
//! and `members_by_params` section, respectively.
//! A class entry contains
//! * an obfuscated and an original name,
//! * optionally a file name,
//! * an offset and length for the class's associated records in the `members` and `members_by_params` section, respectively,
//! * and an `is_synthesized` flag.
//!
//! Class entries are sorted by obfuscated name.
//!
//! ## Member entries
//! A member entry always contains an obfuscated and an original method name, a start
//! and end line (1- based and inclusive), and a params string.
//! It may also contain an original class name,
//! original file name, and original start and end line.
//! A member entry always contains
//! * an obfuscated and an original method name,
//! * a start and end line (1- based and inclusive),
//! * a params string,
//! * and an `is_synthesized` flag.
//!
//! It may also contain
//! * an original class name,
//! * an original file name,
//! * and original start and end lines.
//!
//! Member entries in `members` are sorted by the class they belong to, then by
//! obfuscated method name, and finally by the order in which they were encountered
Expand Down Expand Up @@ -268,7 +276,7 @@ impl<'data> ProguardCache<'data> {
/// Finds the range of elements of `members` for which `f(m) == Ordering::Equal`.
///
/// This works by first binary searching for any element fitting the criteria
/// and then linearly searching foraward and backward from that one to find
/// and then linearly searching forward and backward from that one to find
/// the exact range.
///
/// Obviously this only works if the criteria are consistent with the order
Expand Down Expand Up @@ -495,6 +503,7 @@ fn iterate_with_lines<'a>(
file,
line,
parameters: frame.parameters,
method_synthesized: member.is_synthesized(),
});
}
None
Expand All @@ -519,6 +528,7 @@ fn iterate_without_lines<'a>(
file: None,
line: 0,
parameters: frame.parameters,
method_synthesized: member.is_synthesized(),
})
}

Expand Down Expand Up @@ -558,13 +568,15 @@ com.example.MainFragment$onActivityCreated$4 -> com.example.MainFragment$g:
line: 2,
file: Some("SourceFile"),
parameters: None,
method_synthesized: false,
},
StackFrame {
class: "android.view.View",
method: "performClick",
line: 7393,
file: Some("View.java"),
parameters: None,
method_synthesized: false,
},
],
cause: Some(Box::new(StackTrace {
Expand All @@ -578,6 +590,7 @@ com.example.MainFragment$onActivityCreated$4 -> com.example.MainFragment$g:
line: 1,
file: Some("SourceFile"),
parameters: None,
method_synthesized: false,
}],
cause: None,
})),
Expand Down
53 changes: 52 additions & 1 deletion src/cache/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub(crate) const PRGCACHE_MAGIC: u32 = u32::from_le_bytes(PRGCACHE_MAGIC_BYTES);
/// The byte-flipped magic, which indicates an endianness mismatch.
pub(crate) const PRGCACHE_MAGIC_FLIPPED: u32 = PRGCACHE_MAGIC.swap_bytes();

pub const PRGCACHE_VERSION: u32 = 1;
pub const PRGCACHE_VERSION: u32 = 2;

/// The header of a proguard cache file.
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -56,6 +56,23 @@ pub(crate) struct Class {
pub(crate) members_by_params_offset: u32,
/// The number of member-by-params entries for this class.
pub(crate) members_by_params_len: u32,
/// Whether this class was synthesized by the compiler.
///
/// `0` means `false`, all other values mean `true`.
///
/// Note: It's currently unknown what effect a synthesized
/// class has.
pub(crate) is_synthesized: u8,

/// Reserved space.
pub(crate) _reserved: [u8; 3],
}

impl Class {
/// Returns true if this class was synthesized by the compiler.
pub(crate) fn is_synthesized(&self) -> bool {
self.is_synthesized != 0
}
}

impl Default for Class {
Expand All @@ -68,6 +85,8 @@ impl Default for Class {
members_len: 0,
members_by_params_offset: u32::MAX,
members_by_params_len: 0,
is_synthesized: 0,
_reserved: [0; 3],
}
}
}
Expand All @@ -94,6 +113,20 @@ pub(crate) struct Member {
pub(crate) original_endline: u32,
/// The entry's parameter string (offset into the strings section).
pub(crate) params_offset: u32,
/// Whether this member was synthesized by the compiler.
///
/// `0` means `false`, all other values mean `true`.
pub(crate) is_synthesized: u8,

/// Reserved space.
pub(crate) _reserved: [u8; 3],
}

impl Member {
/// Returns true if this member was synthesized by the compiler.
pub(crate) fn is_synthesized(&self) -> bool {
self.is_synthesized != 0
}
}

unsafe impl Pod for Header {}
Expand Down Expand Up @@ -198,10 +231,16 @@ impl<'data> ProguardCache<'data> {
.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 is_synthesized = parsed
.class_infos
.get(original)
.map(|ci| ci.is_synthesized)
.unwrap_or_default();
let class = ClassInProgress {
class: Class {
original_name_offset,
obfuscated_name_offset,
is_synthesized: is_synthesized as u8,
..Default::default()
},
..Default::default()
Expand Down Expand Up @@ -324,6 +363,13 @@ impl<'data> ProguardCache<'data> {

let params_offset = string_table.insert(member.method.arguments) as u32;

let method_info = parsed
.method_infos
.get(&member.method)
.copied()
.unwrap_or_default();
let is_synthesized = method_info.is_synthesized as u8;

Member {
startline: member.startline as u32,
endline: member.endline as u32,
Expand All @@ -334,6 +380,8 @@ impl<'data> ProguardCache<'data> {
original_endline: member.original_endline.map_or(u32::MAX, |l| l as u32),
obfuscated_name_offset,
params_offset,
is_synthesized,
_reserved: [0; 3],
}
}

Expand All @@ -342,11 +390,13 @@ impl<'data> ProguardCache<'data> {
/// Specifically it checks the following:
/// * All string offsets in class and member entries are either `u32::MAX` or defined.
/// * Member entries are ordered by the class they belong to.
/// * All `is_synthesized` fields on classes and members are either `0` or `1`.
pub fn test(&self) {
let mut prev_end = 0;
for class in self.classes {
assert!(self.read_string(class.obfuscated_name_offset).is_ok());
assert!(self.read_string(class.original_name_offset).is_ok());
assert!(class.is_synthesized == 0 || class.is_synthesized == 1);

if class.file_name_offset != u32::MAX {
assert!(self.read_string(class.file_name_offset).is_ok());
Expand All @@ -362,6 +412,7 @@ impl<'data> ProguardCache<'data> {
for member in members {
assert!(self.read_string(member.obfuscated_name_offset).is_ok());
assert!(self.read_string(member.original_name_offset).is_ok());
assert!(member.is_synthesized == 0 || member.is_synthesized == 1);

if member.params_offset != u32::MAX {
assert!(self.read_string(member.params_offset).is_ok());
Expand Down
Loading
Loading