Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce ProguardCache format #42

Merged
merged 24 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ edition = "2018"
uuid = ["uuid_", "lazy_static"]

[dependencies]
uuid_ = { package = "uuid", version = "1.0.0", features = ["v5"], optional = true }
lazy_static = { version = "1.4.0", optional = true }
thiserror = "1.0.61"
uuid_ = { package = "uuid", version = "1.0.0", features = ["v5"], optional = true }
loewenheim marked this conversation as resolved.
Show resolved Hide resolved
watto = { version = "0.1.0", features = ["writer", "strings"] }

[dev-dependencies]
lazy_static = "1.4.0"
Expand All @@ -24,3 +26,7 @@ criterion = "0.4"
[[bench]]
name = "proguard_parsing"
harness = false

[[bench]]
name = "proguard_mapping"
harness = false
54 changes: 54 additions & 0 deletions benches/proguard_mapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use proguard::{ProguardCache, ProguardMapper, ProguardMapping};

static MAPPING: &[u8] = include_bytes!("../tests/res/mapping-inlines.txt");

static RAW: &str = r#"java.lang.RuntimeException: Button press caused an exception!
at io.sentry.sample.MainActivity.t(MainActivity.java:1)
at e.a.c.a.onClick
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27336)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)"#;

fn benchmark_remapping(c: &mut Criterion) {
let mut cache_buf = Vec::new();
let mapping = ProguardMapping::new(MAPPING);
ProguardCache::write(&mapping, &mut cache_buf).unwrap();
let cache = ProguardCache::parse(&cache_buf).unwrap();
let mapper = ProguardMapper::new(mapping);

let mut group = c.benchmark_group("Proguard Remapping");

group.bench_function("Cache, preparsed", |b| {
b.iter(|| cache.remap_stacktrace(black_box(RAW)))
});
group.bench_function("Mapper, preparsed", |b| {
b.iter(|| mapper.remap_stacktrace(black_box(RAW)))
});

group.bench_function("Cache", |b| {
b.iter(|| {
let cache = ProguardCache::parse(black_box(&cache_buf)).unwrap();
cache.remap_stacktrace(black_box(RAW))
})
});
group.bench_function("Mapper", |b| {
b.iter(|| {
let mapper = ProguardMapper::new(black_box(ProguardMapping::new(MAPPING)));
mapper.remap_stacktrace(black_box(RAW))
})
});

group.finish();
}

criterion_group!(benches, benchmark_remapping);
criterion_main!(benches);
19 changes: 16 additions & 3 deletions benches/proguard_parsing.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use proguard::{ProguardMapper, ProguardMapping};
use proguard::{ProguardCache, ProguardMapper, ProguardMapping};

static MAPPING: &[u8] = include_bytes!("../tests/res/mapping.txt");

fn proguard_mapper(mapping: ProguardMapping) -> ProguardMapper {
ProguardMapper::new(mapping)
}

fn proguard_cache(cache: &[u8]) -> ProguardCache {
ProguardCache::parse(cache).unwrap()
}

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("proguard mapper", |b| {
b.iter(|| proguard_mapper(black_box(ProguardMapping::new(MAPPING))))
let mut cache = Vec::new();
let mapping = ProguardMapping::new(MAPPING);
ProguardCache::write(&mapping, &mut cache).unwrap();

let mut group = c.benchmark_group("Proguard Parsing");
group.bench_function("Proguard Mapper", |b| {
b.iter(|| proguard_mapper(black_box(mapping.clone())))
});

group.bench_function("Proguard Cache", |b| {
b.iter(|| proguard_cache(black_box(&cache)))
});
}

Expand Down
200 changes: 200 additions & 0 deletions src/cache/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
use std::fmt;

use crate::ProguardCache;

use super::raw;

/// A variant of a class entry in a proguard cache file with
/// nice-ish `Debug` and `Display` representations.
pub struct ClassDebug<'a, 'data> {
pub(crate) cache: &'a ProguardCache<'data>,
pub(crate) raw: &'a raw::Class,
}

impl ClassDebug<'_, '_> {
fn obfuscated_name(&self) -> &str {
self.cache
.read_string(self.raw.obfuscated_name_offset)
.unwrap()
}

fn original_name(&self) -> &str {
self.cache
.read_string(self.raw.original_name_offset)
.unwrap()
}

fn file_name(&self) -> Option<&str> {
self.cache.read_string(self.raw.file_name_offset).ok()
}
}

impl fmt::Debug for ClassDebug<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Class")
.field("obfuscated_name", &self.obfuscated_name())
.field("original_name", &self.original_name())
.field("file_name", &self.file_name())
.finish()
}
}

impl fmt::Display for ClassDebug<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} -> {}:", self.original_name(), self.obfuscated_name())?;
if let Some(file_name) = self.file_name() {
writeln!(f)?;
write!(f, r##"# {{"id":"sourceFile","fileName":"{file_name}"}}"##)?;
}
Ok(())
}
}

/// A variant of a member entry in a proguard cache file with
/// nice-ish `Debug` and `Display` representations.
pub struct MemberDebug<'a, 'data> {
pub(crate) cache: &'a ProguardCache<'data>,
pub(crate) raw: &'a raw::Member,
}

impl MemberDebug<'_, '_> {
fn original_class(&self) -> Option<&str> {
self.cache.read_string(self.raw.original_class_offset).ok()
}

fn original_file(&self) -> Option<&str> {
self.cache.read_string(self.raw.original_file_offset).ok()
}

fn params(&self) -> &str {
self.cache
.read_string(self.raw.params_offset)
.unwrap_or_default()
}

fn obfuscated_name(&self) -> &str {
self.cache
.read_string(self.raw.obfuscated_name_offset)
.unwrap()
}

fn original_name(&self) -> &str {
self.cache
.read_string(self.raw.original_name_offset)
.unwrap()
}

fn original_endline(&self) -> Option<u32> {
if self.raw.original_endline != u32::MAX {
Some(self.raw.original_endline)
} else {
None
}
}
}

impl fmt::Debug for MemberDebug<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Member")
.field("obfuscated_name", &self.obfuscated_name())
.field("startline", &self.raw.startline)
.field("endline", &self.raw.endline)
.field("original_name", &self.original_name())
.field("original_class", &self.original_class())
.field("original_file", &self.original_file())
.field("original_startline", &self.raw.original_startline)
.field("original_endline", &self.original_endline())
.field("params", &self.params())
.finish()
}
}

impl fmt::Display for MemberDebug<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// XXX: We could print the actual return type here if we saved it in the format.
// Wonder if it's worth it, since we'd only use it in this display impl.
write!(f, " {}:{}:<ret> ", self.raw.startline, self.raw.endline)?;
if let Some(original_class) = self.original_class() {
write!(f, "{original_class}.")?;
}
write!(
f,
"{}({}):{}",
self.original_name(),
self.params(),
self.raw.original_startline
)?;
if let Some(end) = self.original_endline() {
write!(f, ":{end}")?;
}
write!(f, " -> {}", self.obfuscated_name())?;
Ok(())
}
}

pub struct CacheDebug<'a, 'data> {
cache: &'a ProguardCache<'data>,
}

impl fmt::Display for CacheDebug<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for class in self.cache.classes {
writeln!(
f,
"{}",
ClassDebug {
raw: class,
cache: self.cache
}
)?;
let Some(members) = self.cache.get_class_members(class) else {
continue;
};

for member in members {
writeln!(
f,
"{}",
MemberDebug {
raw: member,
cache: self.cache
}
)?;
}
}
Ok(())
}
}

impl<'data> ProguardCache<'data> {
/// Returns an iterator over class entries in this cache file that can be debug printed.
pub fn debug_classes<'r>(&'r self) -> impl Iterator<Item = ClassDebug<'r, 'data>> {
self.classes.iter().map(move |c| ClassDebug {
cache: self,
raw: c,
})
}

/// Returns an iterator over member entries in this cache file that can be debug printed.
pub fn debug_members<'r>(&'r self) -> impl Iterator<Item = MemberDebug<'r, 'data>> {
self.members.iter().map(move |m| MemberDebug {
cache: self,
raw: m,
})
}

/// Returns an iterator over by-params member entries in this cache file that can be debug printed.
pub fn debug_members_by_params<'r>(&'r self) -> impl Iterator<Item = MemberDebug<'r, 'data>> {
self.members_by_params.iter().map(move |m| MemberDebug {
cache: self,
raw: m,
})
}

/// Creates a view of the cache that implements `Display`.
///
/// The `Display` impl is very similar to the original proguard format.
pub fn display(&self) -> CacheDebug {
CacheDebug { cache: self }
}
}
Loading
Loading