Skip to content

Commit 9e64ae5

Browse files
authored
feat: Handle "synthesized" class/member annotations (#52)
This adds support for "synthesized" class and member annotations. * It adds a new variant `Synthesized` to `R8Header` to parse these annotations. * It adds `is_synthesized` flags to `ClassMapping` and `MemberMapping` and their corresponding cache types. In the cache the flags have type `u8` because `bool` would be unsound to transmute. * It adds an `method_synthesized` flag to `StackFrame` that is set if the frame's method is flagged as synthesized. * It adds cache-using variants to the tests in `callback.rs`. * It bumps the version of the proguard cache format to 2. The version needs to be increased because records now have an extra field. With this change, `remap_frame` (both the `mapper` and the `cache` version) will return exactly the same frames as before, but some of them might be marked as synthesized. This means it's up to the caller to decide what to do with synthesized frames. Closes #48. Closes RUSTPRO-3.
1 parent 2d3ec7b commit 9e64ae5

File tree

8 files changed

+311
-19
lines changed

8 files changed

+311
-19
lines changed

src/builder.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ impl std::ops::Deref for OriginalName<'_> {
4949
pub(crate) struct ClassInfo<'s> {
5050
/// The source file in which the class is defined.
5151
pub(crate) source_file: Option<&'s str>,
52+
/// Whether this class was synthesized by the compiler.
53+
pub(crate) is_synthesized: bool,
5254
}
5355

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

113115
/// Information about a method in a ProGuard file.
114116
#[derive(Clone, Copy, Debug, Default)]
115-
pub(crate) struct MethodInfo {}
117+
pub(crate) struct MethodInfo {
118+
/// Whether this method was synthesized by the compiler.
119+
pub(crate) is_synthesized: bool,
120+
}
116121

117122
/// A member record in a Proguard file.
118123
#[derive(Clone, Copy, Debug)]
@@ -167,7 +172,8 @@ impl<'s> ParsedProguardMapping<'s> {
167172
ProguardRecord::Field { .. } => {}
168173
ProguardRecord::Header { .. } => {}
169174
ProguardRecord::R8Header(_) => {
170-
// R8 headers are already handled in the class case below.
175+
// R8 headers can be skipped; they are already
176+
// handled in the branches for `Class` and `Method`.
171177
}
172178
ProguardRecord::Class {
173179
original,
@@ -187,8 +193,9 @@ impl<'s> ParsedProguardMapping<'s> {
187193
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
188194
match r8_header {
189195
R8Header::SourceFile { file_name } => {
190-
current_class.source_file = Some(file_name);
196+
current_class.source_file = Some(file_name)
191197
}
198+
R8Header::Synthesized => current_class.is_synthesized = true,
192199
R8Header::Other => {}
193200
}
194201

@@ -251,8 +258,17 @@ impl<'s> ParsedProguardMapping<'s> {
251258
arguments,
252259
};
253260

254-
// This does nothing for now because we are not saving any per-method information.
255-
let _method_info: &mut MethodInfo = slf.method_infos.entry(method).or_default();
261+
let method_info: &mut MethodInfo = slf.method_infos.entry(method).or_default();
262+
263+
// Consume R8 headers attached to this method.
264+
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
265+
match r8_header {
266+
R8Header::Synthesized => method_info.is_synthesized = true,
267+
R8Header::SourceFile { .. } | R8Header::Other => {}
268+
}
269+
270+
records.next();
271+
}
256272

257273
let member = Member {
258274
method,

src/cache/debug.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl fmt::Debug for ClassDebug<'_, '_> {
3535
.field("obfuscated_name", &self.obfuscated_name())
3636
.field("original_name", &self.original_name())
3737
.field("file_name", &self.file_name())
38+
.field("is_synthesized", &self.raw.is_synthesized())
3839
.finish()
3940
}
4041
}
@@ -105,6 +106,7 @@ impl fmt::Debug for MemberDebug<'_, '_> {
105106
.field("original_startline", &self.raw.original_startline)
106107
.field("original_endline", &self.original_endline())
107108
.field("params", &self.params())
109+
.field("is_synthesized", &self.raw.is_synthesized())
108110
.finish()
109111
}
110112
}

src/cache/mod.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,25 @@
1313
//! it is by offset into this section.
1414
//!
1515
//! ## Class entries
16-
//! A class entry contains an obfuscated and an original name, optionally a file name,
17-
//! and an offset and length for the class's associated records in the `members`
18-
//! and `members_by_params` section, respectively.
16+
//! A class entry contains
17+
//! * an obfuscated and an original name,
18+
//! * optionally a file name,
19+
//! * an offset and length for the class's associated records in the `members` and `members_by_params` section, respectively,
20+
//! * and an `is_synthesized` flag.
1921
//!
2022
//! Class entries are sorted by obfuscated name.
2123
//!
2224
//! ## Member entries
23-
//! A member entry always contains an obfuscated and an original method name, a start
24-
//! and end line (1- based and inclusive), and a params string.
25-
//! It may also contain an original class name,
26-
//! original file name, and original start and end line.
25+
//! A member entry always contains
26+
//! * an obfuscated and an original method name,
27+
//! * a start and end line (1- based and inclusive),
28+
//! * a params string,
29+
//! * and an `is_synthesized` flag.
30+
//!
31+
//! It may also contain
32+
//! * an original class name,
33+
//! * an original file name,
34+
//! * and original start and end lines.
2735
//!
2836
//! Member entries in `members` are sorted by the class they belong to, then by
2937
//! obfuscated method name, and finally by the order in which they were encountered
@@ -268,7 +276,7 @@ impl<'data> ProguardCache<'data> {
268276
/// Finds the range of elements of `members` for which `f(m) == Ordering::Equal`.
269277
///
270278
/// This works by first binary searching for any element fitting the criteria
271-
/// and then linearly searching foraward and backward from that one to find
279+
/// and then linearly searching forward and backward from that one to find
272280
/// the exact range.
273281
///
274282
/// Obviously this only works if the criteria are consistent with the order
@@ -495,6 +503,7 @@ fn iterate_with_lines<'a>(
495503
file,
496504
line,
497505
parameters: frame.parameters,
506+
method_synthesized: member.is_synthesized(),
498507
});
499508
}
500509
None
@@ -519,6 +528,7 @@ fn iterate_without_lines<'a>(
519528
file: None,
520529
line: 0,
521530
parameters: frame.parameters,
531+
method_synthesized: member.is_synthesized(),
522532
})
523533
}
524534

@@ -558,13 +568,15 @@ com.example.MainFragment$onActivityCreated$4 -> com.example.MainFragment$g:
558568
line: 2,
559569
file: Some("SourceFile"),
560570
parameters: None,
571+
method_synthesized: false,
561572
},
562573
StackFrame {
563574
class: "android.view.View",
564575
method: "performClick",
565576
line: 7393,
566577
file: Some("View.java"),
567578
parameters: None,
579+
method_synthesized: false,
568580
},
569581
],
570582
cause: Some(Box::new(StackTrace {
@@ -578,6 +590,7 @@ com.example.MainFragment$onActivityCreated$4 -> com.example.MainFragment$g:
578590
line: 1,
579591
file: Some("SourceFile"),
580592
parameters: None,
593+
method_synthesized: false,
581594
}],
582595
cause: None,
583596
})),

src/cache/raw.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub(crate) const PRGCACHE_MAGIC: u32 = u32::from_le_bytes(PRGCACHE_MAGIC_BYTES);
1818
/// The byte-flipped magic, which indicates an endianness mismatch.
1919
pub(crate) const PRGCACHE_MAGIC_FLIPPED: u32 = PRGCACHE_MAGIC.swap_bytes();
2020

21-
pub const PRGCACHE_VERSION: u32 = 1;
21+
pub const PRGCACHE_VERSION: u32 = 2;
2222

2323
/// The header of a proguard cache file.
2424
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -56,6 +56,23 @@ pub(crate) struct Class {
5656
pub(crate) members_by_params_offset: u32,
5757
/// The number of member-by-params entries for this class.
5858
pub(crate) members_by_params_len: u32,
59+
/// Whether this class was synthesized by the compiler.
60+
///
61+
/// `0` means `false`, all other values mean `true`.
62+
///
63+
/// Note: It's currently unknown what effect a synthesized
64+
/// class has.
65+
pub(crate) is_synthesized: u8,
66+
67+
/// Reserved space.
68+
pub(crate) _reserved: [u8; 3],
69+
}
70+
71+
impl Class {
72+
/// Returns true if this class was synthesized by the compiler.
73+
pub(crate) fn is_synthesized(&self) -> bool {
74+
self.is_synthesized != 0
75+
}
5976
}
6077

6178
impl Default for Class {
@@ -68,6 +85,8 @@ impl Default for Class {
6885
members_len: 0,
6986
members_by_params_offset: u32::MAX,
7087
members_by_params_len: 0,
88+
is_synthesized: 0,
89+
_reserved: [0; 3],
7190
}
7291
}
7392
}
@@ -94,6 +113,20 @@ pub(crate) struct Member {
94113
pub(crate) original_endline: u32,
95114
/// The entry's parameter string (offset into the strings section).
96115
pub(crate) params_offset: u32,
116+
/// Whether this member was synthesized by the compiler.
117+
///
118+
/// `0` means `false`, all other values mean `true`.
119+
pub(crate) is_synthesized: u8,
120+
121+
/// Reserved space.
122+
pub(crate) _reserved: [u8; 3],
123+
}
124+
125+
impl Member {
126+
/// Returns true if this member was synthesized by the compiler.
127+
pub(crate) fn is_synthesized(&self) -> bool {
128+
self.is_synthesized != 0
129+
}
97130
}
98131

99132
unsafe impl Pod for Header {}
@@ -198,10 +231,16 @@ impl<'data> ProguardCache<'data> {
198231
.map(|(obfuscated, original)| {
199232
let obfuscated_name_offset = string_table.insert(obfuscated.as_str()) as u32;
200233
let original_name_offset = string_table.insert(original.as_str()) as u32;
234+
let is_synthesized = parsed
235+
.class_infos
236+
.get(original)
237+
.map(|ci| ci.is_synthesized)
238+
.unwrap_or_default();
201239
let class = ClassInProgress {
202240
class: Class {
203241
original_name_offset,
204242
obfuscated_name_offset,
243+
is_synthesized: is_synthesized as u8,
205244
..Default::default()
206245
},
207246
..Default::default()
@@ -324,6 +363,13 @@ impl<'data> ProguardCache<'data> {
324363

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

366+
let method_info = parsed
367+
.method_infos
368+
.get(&member.method)
369+
.copied()
370+
.unwrap_or_default();
371+
let is_synthesized = method_info.is_synthesized as u8;
372+
327373
Member {
328374
startline: member.startline as u32,
329375
endline: member.endline as u32,
@@ -334,6 +380,8 @@ impl<'data> ProguardCache<'data> {
334380
original_endline: member.original_endline.map_or(u32::MAX, |l| l as u32),
335381
obfuscated_name_offset,
336382
params_offset,
383+
is_synthesized,
384+
_reserved: [0; 3],
337385
}
338386
}
339387

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

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

366417
if member.params_offset != u32::MAX {
367418
assert!(self.read_string(member.params_offset).is_ok());

0 commit comments

Comments
 (0)