Skip to content

Commit 8eb23e5

Browse files
romtsnclaude
andcommitted
feat(r8): Sort ambiguous no-range entries alphabetically by method name
When multiple no-range entries with different original method names all have line mappings, sort them alphabetically. Bare method entries (no line mapping) preserve original mapping file order. Fixes test_single_line_no_line_number_stacktrace — all 10 R8 line number handling tests now pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 86ca9fb commit 8eb23e5

File tree

2 files changed

+34
-9
lines changed

2 files changed

+34
-9
lines changed

src/cache/mod.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,7 @@ fn iterate_with_lines<'a>(
10051005
// Span expansion: if the original range spans multiple lines,
10061006
// emit one frame per original line.
10071007
if member.original_endline != u32::MAX
1008-
&& member.original_endline > member.original_startline().unwrap_or(0) as u32
1008+
&& member.original_endline > member.original_startline().unwrap_or(0)
10091009
{
10101010
let first_line = member.original_startline().unwrap_or(0) as usize;
10111011
let last_line = member.original_endline as usize;
@@ -1044,7 +1044,7 @@ fn iterate_with_lines<'a>(
10441044
// parents of inlined frames don't have an `endline`, and
10451045
// the top inlined frame need to be correctly offset.
10461046
let line = if member.original_endline == u32::MAX
1047-
|| member.original_endline == member.original_startline().unwrap_or(0) as u32
1047+
|| member.original_endline == member.original_startline().unwrap_or(0)
10481048
{
10491049
member.original_startline().unwrap_or(0) as usize
10501050
} else {
@@ -1197,15 +1197,19 @@ fn resolve_base_entries<'a>(
11971197
let mut no_range_count = 0usize;
11981198
let mut first_no_range_offset: Option<u32> = None;
11991199
let mut all_no_range_same_name = true;
1200+
let mut all_no_range_have_line_mapping = true;
12001201
for member in base_entries {
12011202
if member.startline().is_some() {
12021203
if member.original_endline != u32::MAX
1203-
&& member.original_endline != member.original_startline().unwrap_or(0) as u32
1204+
&& member.original_endline != member.original_startline().unwrap_or(0)
12041205
{
12051206
any_zero_zero_has_range = true;
12061207
}
12071208
} else {
12081209
no_range_count += 1;
1210+
if member.original_startline().is_none() {
1211+
all_no_range_have_line_mapping = false;
1212+
}
12091213
match first_no_range_offset {
12101214
None => first_no_range_offset = Some(member.original_name_offset),
12111215
Some(first) if member.original_name_offset != first => {
@@ -1235,11 +1239,13 @@ fn resolve_base_entries<'a>(
12351239
let line = if no_range_count > 1 {
12361240
Some(0)
12371241
} else {
1238-
compute_member_output_line(member).or(if member.original_startline().is_none() {
1239-
Some(0)
1240-
} else {
1241-
None
1242-
})
1242+
compute_member_output_line(member).or(
1243+
if member.original_startline().is_none() {
1244+
Some(0)
1245+
} else {
1246+
None
1247+
},
1248+
)
12431249
};
12441250
if let Some(f) =
12451251
map_member_without_lines(cache, frame, member, outer_source_file, line)
@@ -1254,6 +1260,12 @@ fn resolve_base_entries<'a>(
12541260
}
12551261
}
12561262

1263+
// Sort no-range frames by original method name when all have line mappings;
1264+
// bare method entries preserve original mapping file order.
1265+
if !all_no_range_same_name && all_no_range_have_line_mapping {
1266+
frames.sort_by(|a, b| a.method.cmp(b.method));
1267+
}
1268+
12571269
frames
12581270
}
12591271

src/mapper.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ fn resolve_base_entries<'s>(
348348
let mut no_range_count = 0usize;
349349
let mut first_no_range_name: Option<&str> = None;
350350
let mut all_no_range_same_name = true;
351+
let mut all_no_range_have_line_mapping = true;
351352
for member in base_entries {
352353
if member.startline.is_some() {
353354
if member.original_endline.is_some()
@@ -357,6 +358,9 @@ fn resolve_base_entries<'s>(
357358
}
358359
} else {
359360
no_range_count += 1;
361+
if member.original_startline.is_none() {
362+
all_no_range_have_line_mapping = false;
363+
}
360364
match first_no_range_name {
361365
None => first_no_range_name = Some(member.original),
362366
Some(first) if member.original != first => all_no_range_same_name = false,
@@ -403,6 +407,12 @@ fn resolve_base_entries<'s>(
403407
collected.rewrite_rules.extend(member.rewrite_rules.iter());
404408
}
405409
}
410+
411+
// Sort no-range frames by original method name when all have line mappings;
412+
// bare method entries preserve original mapping file order.
413+
if !all_no_range_same_name && all_no_range_have_line_mapping {
414+
collected.frames.sort_by_key(|f| f.method);
415+
}
406416
}
407417

408418
/// A Proguard Remapper.
@@ -805,7 +815,10 @@ impl<'s> ProguardMapper<'s> {
805815
members.all_mappings.iter()
806816
};
807817

808-
let has_line_info = members.all_mappings.iter().any(|m| m.endline.unwrap_or(0) > 0);
818+
let has_line_info = members
819+
.all_mappings
820+
.iter()
821+
.any(|m| m.endline.unwrap_or(0) > 0);
809822
RemappedFrameIter::members(frame, mappings, has_line_info)
810823
}
811824

0 commit comments

Comments
 (0)