Skip to content

Commit ce3f3c9

Browse files
authored
feat(r8): Support outline and outlineCallsite annotations in ProguardMapping (#60)
This only targets `ProguardMapping` at the moment, will address `ProguardCache` in a separate PR Part of #59
1 parent 695c543 commit ce3f3c9

File tree

7 files changed

+2429
-29
lines changed

7 files changed

+2429
-29
lines changed

src/builder.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,12 @@ pub(crate) struct MethodKey<'s> {
117117
pub(crate) struct MethodInfo {
118118
/// Whether this method was synthesized by the compiler.
119119
pub(crate) is_synthesized: bool,
120+
/// Whether this method is an outline.
121+
pub(crate) is_outline: bool,
120122
}
121123

122124
/// A member record in a Proguard file.
123-
#[derive(Clone, Copy, Debug)]
125+
#[derive(Clone, Debug)]
124126
pub(crate) struct Member<'s> {
125127
/// The method the member refers to.
126128
pub(crate) method: MethodKey<'s>,
@@ -132,6 +134,8 @@ pub(crate) struct Member<'s> {
132134
pub(crate) original_startline: usize,
133135
/// The original end line.
134136
pub(crate) original_endline: Option<usize>,
137+
/// Optional outline callsite positions map attached to this member.
138+
pub(crate) outline_callsite_positions: Option<HashMap<usize, usize>>,
135139
}
136140

137141
/// A collection of member records for a particular class
@@ -196,6 +200,8 @@ impl<'s> ParsedProguardMapping<'s> {
196200
current_class.source_file = Some(file_name)
197201
}
198202
R8Header::Synthesized => current_class.is_synthesized = true,
203+
R8Header::Outline => {}
204+
R8Header::OutlineCallsite { .. } => {}
199205
R8Header::Other => {}
200206
}
201207

@@ -260,10 +266,29 @@ impl<'s> ParsedProguardMapping<'s> {
260266

261267
let method_info: &mut MethodInfo = slf.method_infos.entry(method).or_default();
262268

263-
// Consume R8 headers attached to this method.
269+
// Collect any OutlineCallsite mapping attached to this member.
270+
let mut outline_callsite_positions: Option<HashMap<usize, usize>> = None;
271+
272+
// Consume R8 headers attached to this method/member.
264273
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
265274
match r8_header {
266275
R8Header::Synthesized => method_info.is_synthesized = true,
276+
R8Header::Outline => {
277+
method_info.is_outline = true;
278+
}
279+
R8Header::OutlineCallsite {
280+
positions,
281+
outline: _,
282+
} => {
283+
// Attach outline callsite mapping to this specific member.
284+
let map: HashMap<usize, usize> = positions
285+
.iter()
286+
.filter_map(|(k, v)| k.parse::<usize>().ok().map(|kk| (kk, *v)))
287+
.collect();
288+
if !map.is_empty() {
289+
outline_callsite_positions = Some(map);
290+
}
291+
}
267292
R8Header::SourceFile { .. } | R8Header::Other => {}
268293
}
269294

@@ -276,9 +301,10 @@ impl<'s> ParsedProguardMapping<'s> {
276301
endline,
277302
original_startline,
278303
original_endline,
304+
outline_callsite_positions,
279305
};
280306

281-
members.all.push(member);
307+
members.all.push(member.clone());
282308

283309
if !initialize_param_mapping {
284310
continue;
@@ -306,7 +332,7 @@ impl<'s> ParsedProguardMapping<'s> {
306332
.by_params
307333
.entry(arguments)
308334
.or_insert_with(|| Vec::with_capacity(1))
309-
.push(member);
335+
.push(member.clone());
310336
}
311337
} // end ProguardRecord::Method
312338
}

src/cache/raw.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ impl<'data> ProguardCache<'data> {
261261
.entry(obfuscated_method.as_str())
262262
.or_default();
263263

264-
for member in members.all.iter().copied() {
264+
for member in members.all.iter() {
265265
method_mappings.push(Self::resolve_mapping(
266266
&mut string_table,
267267
&parsed,
@@ -277,12 +277,12 @@ impl<'data> ProguardCache<'data> {
277277
.entry((obfuscated_method.as_str(), args))
278278
.or_default();
279279

280-
for member in param_members {
280+
for member in param_members.iter() {
281281
param_mappings.push(Self::resolve_mapping(
282282
&mut string_table,
283283
&parsed,
284284
obfuscated_method_offset,
285-
*member,
285+
member,
286286
));
287287
current_class.class.members_by_params_len += 1;
288288
}
@@ -345,7 +345,7 @@ impl<'data> ProguardCache<'data> {
345345
string_table: &mut StringTable,
346346
parsed: &ParsedProguardMapping<'_>,
347347
obfuscated_name_offset: u32,
348-
member: builder::Member,
348+
member: &builder::Member,
349349
) -> Member {
350350
let original_file = parsed
351351
.class_infos

src/mapper.rs

Lines changed: 147 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl fmt::Display for DeobfuscatedSignature {
5151
}
5252
}
5353

54-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
54+
#[derive(Clone, Debug, PartialEq, Eq)]
5555
struct MemberMapping<'s> {
5656
startline: usize,
5757
endline: usize,
@@ -61,6 +61,8 @@ struct MemberMapping<'s> {
6161
original_startline: usize,
6262
original_endline: Option<usize>,
6363
is_synthesized: bool,
64+
is_outline: bool,
65+
outline_callsite_positions: Option<HashMap<usize, usize>>,
6466
}
6567

6668
#[derive(Clone, Debug, Default)]
@@ -265,7 +267,7 @@ impl<'s> ProguardMapper<'s> {
265267
.entry(obfuscated_method.as_str())
266268
.or_default();
267269

268-
for member in members.all.iter().copied() {
270+
for member in members.all.iter() {
269271
method_mappings
270272
.all_mappings
271273
.push(Self::resolve_mapping(&parsed, member));
@@ -274,8 +276,8 @@ impl<'s> ProguardMapper<'s> {
274276
for (args, param_members) in members.by_params.iter() {
275277
let param_mappings = method_mappings.mappings_by_params.entry(args).or_default();
276278

277-
for member in param_members {
278-
param_mappings.push(Self::resolve_mapping(&parsed, *member));
279+
for member in param_members.iter() {
280+
param_mappings.push(Self::resolve_mapping(&parsed, member));
279281
}
280282
}
281283
}
@@ -287,7 +289,7 @@ impl<'s> ProguardMapper<'s> {
287289

288290
fn resolve_mapping(
289291
parsed: &ParsedProguardMapping<'s>,
290-
member: Member<'s>,
292+
member: &Member<'s>,
291293
) -> MemberMapping<'s> {
292294
let original_file = parsed
293295
.class_infos
@@ -306,6 +308,9 @@ impl<'s> ProguardMapper<'s> {
306308
.copied()
307309
.unwrap_or_default();
308310
let is_synthesized = method_info.is_synthesized;
311+
let is_outline = method_info.is_outline;
312+
313+
let outline_callsite_positions = member.outline_callsite_positions.clone();
309314

310315
MemberMapping {
311316
startline: member.startline,
@@ -316,9 +321,94 @@ impl<'s> ProguardMapper<'s> {
316321
original_startline: member.original_startline,
317322
original_endline: member.original_endline,
318323
is_synthesized,
324+
is_outline,
325+
outline_callsite_positions,
319326
}
320327
}
321328

329+
/// If the previous frame was an outline and carried a position, attempt to
330+
/// map that outline position to a callsite position for the given method.
331+
fn map_outline_position(
332+
&self,
333+
class: &str,
334+
method: &str,
335+
callsite_line: usize,
336+
pos: usize,
337+
parameters: Option<&str>,
338+
) -> Option<usize> {
339+
let ms = self.classes.get(class)?.members.get(method)?;
340+
let candidates: &[_] = if let Some(params) = parameters {
341+
match ms.mappings_by_params.get(params) {
342+
Some(v) => &v[..],
343+
None => &[],
344+
}
345+
} else {
346+
&ms.all_mappings[..]
347+
};
348+
349+
// Find the member mapping covering the callsite line, then map the pos.
350+
candidates
351+
.iter()
352+
.filter(|m| {
353+
m.endline == 0 || (callsite_line >= m.startline && callsite_line <= m.endline)
354+
})
355+
.find_map(|m| {
356+
m.outline_callsite_positions
357+
.as_ref()
358+
.and_then(|mm| mm.get(&pos).copied())
359+
})
360+
}
361+
362+
/// Determines if a frame refers to an outline method, either via the
363+
/// method-level flag or via any matching mapping entry for the frame line.
364+
fn is_outline_frame(
365+
&self,
366+
class: &str,
367+
method: &str,
368+
line: usize,
369+
parameters: Option<&str>,
370+
) -> bool {
371+
self.classes
372+
.get(class)
373+
.and_then(|c| c.members.get(method))
374+
.map(|ms| {
375+
let mappings: &[_] = if let Some(params) = parameters {
376+
match ms.mappings_by_params.get(params) {
377+
Some(v) => &v[..],
378+
None => &[],
379+
}
380+
} else {
381+
&ms.all_mappings[..]
382+
};
383+
mappings.iter().any(|m| {
384+
m.is_outline && (m.endline == 0 || (line >= m.startline && line <= m.endline))
385+
})
386+
})
387+
.unwrap_or(false)
388+
}
389+
390+
/// Applies any carried outline position to the frame line and returns the adjusted frame.
391+
fn prepare_frame_for_mapping<'a>(
392+
&self,
393+
frame: &StackFrame<'a>,
394+
carried_outline_pos: &mut Option<usize>,
395+
) -> StackFrame<'a> {
396+
let mut effective = frame.clone();
397+
if let Some(pos) = carried_outline_pos.take() {
398+
if let Some(mapped) = self.map_outline_position(
399+
effective.class,
400+
effective.method,
401+
effective.line,
402+
pos,
403+
effective.parameters,
404+
) {
405+
effective.line = mapped;
406+
}
407+
}
408+
409+
effective
410+
}
411+
322412
/// Remaps an obfuscated Class.
323413
///
324414
/// This works on the fully-qualified name of the class, with its complete
@@ -423,12 +513,31 @@ impl<'s> ProguardMapper<'s> {
423513
pub fn remap_stacktrace(&self, input: &str) -> Result<String, std::fmt::Error> {
424514
let mut stacktrace = String::new();
425515
let mut lines = input.lines();
516+
let mut carried_outline_pos: Option<usize> = None;
426517

427518
if let Some(line) = lines.next() {
428519
match stacktrace::parse_throwable(line) {
429520
None => match stacktrace::parse_frame(line) {
430521
None => writeln!(&mut stacktrace, "{line}")?,
431-
Some(frame) => format_frames(&mut stacktrace, line, self.remap_frame(&frame))?,
522+
Some(frame) => {
523+
if self.is_outline_frame(
524+
frame.class,
525+
frame.method,
526+
frame.line,
527+
frame.parameters,
528+
) {
529+
carried_outline_pos = Some(frame.line);
530+
} else {
531+
let effective_frame =
532+
self.prepare_frame_for_mapping(&frame, &mut carried_outline_pos);
533+
534+
format_frames(
535+
&mut stacktrace,
536+
line,
537+
self.remap_frame(&effective_frame),
538+
)?;
539+
}
540+
}
432541
},
433542
Some(throwable) => {
434543
format_throwable(&mut stacktrace, line, self.remap_throwable(&throwable))?
@@ -447,7 +556,22 @@ impl<'s> ProguardMapper<'s> {
447556
format_cause(&mut stacktrace, line, self.remap_throwable(&cause))?
448557
}
449558
},
450-
Some(frame) => format_frames(&mut stacktrace, line, self.remap_frame(&frame))?,
559+
Some(frame) => {
560+
if self.is_outline_frame(
561+
frame.class,
562+
frame.method,
563+
frame.line,
564+
frame.parameters,
565+
) {
566+
carried_outline_pos = Some(frame.line);
567+
continue;
568+
}
569+
570+
let effective_frame =
571+
self.prepare_frame_for_mapping(&frame, &mut carried_outline_pos);
572+
573+
format_frames(&mut stacktrace, line, self.remap_frame(&effective_frame))?;
574+
}
451575
}
452576
}
453577
Ok(stacktrace)
@@ -460,20 +584,22 @@ impl<'s> ProguardMapper<'s> {
460584
.as_ref()
461585
.and_then(|t| self.remap_throwable(t));
462586

463-
let frames =
464-
trace
465-
.frames
466-
.iter()
467-
.fold(Vec::with_capacity(trace.frames.len()), |mut frames, f| {
468-
let mut peek_frames = self.remap_frame(f).peekable();
469-
if peek_frames.peek().is_some() {
470-
frames.extend(peek_frames);
471-
} else {
472-
frames.push(f.clone());
473-
}
587+
let mut carried_outline_pos: Option<usize> = None;
588+
let mut frames_out = Vec::with_capacity(trace.frames.len());
589+
for f in trace.frames.iter() {
590+
if self.is_outline_frame(f.class, f.method, f.line, f.parameters) {
591+
carried_outline_pos = Some(f.line);
592+
continue;
593+
}
474594

475-
frames
476-
});
595+
let effective = self.prepare_frame_for_mapping(f, &mut carried_outline_pos);
596+
let mut iter = self.remap_frame(&effective).peekable();
597+
if iter.peek().is_some() {
598+
frames_out.extend(iter);
599+
} else {
600+
frames_out.push(f.clone());
601+
}
602+
}
477603

478604
let cause = trace
479605
.cause
@@ -482,7 +608,7 @@ impl<'s> ProguardMapper<'s> {
482608

483609
StackTrace {
484610
exception,
485-
frames,
611+
frames: frames_out,
486612
cause,
487613
}
488614
}

0 commit comments

Comments
 (0)