-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Introduce ProguardCache format (#42)
This introduces a stable binary format similar to SymCache. It allows lookups that are fast, if not as fast as via a `ProguardMapper`, but doesn't require parsing the entire file into memory.
- Loading branch information
Showing
11 changed files
with
1,575 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} | ||
} |
Oops, something went wrong.