Skip to content

Commit 92bba06

Browse files
romtsnclaude
andauthored
feat(r8): Make startline, endline and original_startline optional (#84)
## Summary - Make `startline`, `endline`, and `original_startline` fields `Option`-typed on both `Member` (builder) and `MemberMapping` (mapper) structs - Replaces the earlier boolean flags (`has_minified_range`, `has_line_mapping`) — presence/absence is now encoded directly: `startline.is_some()` means a minified range was present, `original_startline.is_some()` means a line mapping existed - In the binary cache format, `u32::MAX` is used as a sentinel for absent values (via `NONE_VALUE` constant) - Change `StackFrame.line` from `usize` to `Option<usize>` to distinguish "no line provided" from "line 0" - Fix inverted line ranges in mapping parser (swap start/end when start > end) - Bumps cache version 4 → 5 ## Test plan - [x] All existing tests pass (no regressions) - [x] Same 5 known-failing r8-line-number-handling tests remain (will be fixed in subsequent PRs) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f132a33 commit 92bba06

File tree

6 files changed

+138
-106
lines changed

6 files changed

+138
-106
lines changed

src/builder.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,12 @@ pub(crate) struct RewriteRule<'s> {
149149
pub(crate) struct Member<'s> {
150150
/// The method the member refers to.
151151
pub(crate) method: MethodKey<'s>,
152-
/// The obfuscated/minified start line.
153-
pub(crate) startline: usize,
154-
/// The obfuscated/minified end line.
155-
pub(crate) endline: usize,
156-
/// The original start line.
157-
pub(crate) original_startline: usize,
152+
/// The obfuscated/minified start line, `None` when no minified range prefix was present.
153+
pub(crate) startline: Option<usize>,
154+
/// The obfuscated/minified end line, `None` when no minified range prefix was present.
155+
pub(crate) endline: Option<usize>,
156+
/// The original start line, `None` when no line mapping was present.
157+
pub(crate) original_startline: Option<usize>,
158158
/// The original end line.
159159
pub(crate) original_endline: Option<usize>,
160160
/// Optional outline callsite positions map attached to this member.
@@ -291,29 +291,29 @@ impl<'s> ParsedProguardMapping<'s> {
291291
} else {
292292
None
293293
};
294-
// in case the mapping has no line records, we use `0` here.
295-
let (mut startline, mut endline) =
296-
line_mapping.as_ref().map_or((0, 0), |line_mapping| {
297-
(line_mapping.startline, line_mapping.endline)
298-
});
299-
let (mut original_startline, mut original_endline) =
300-
line_mapping.map_or((0, None), |line_mapping| {
301-
match line_mapping.original_startline {
302-
Some(original_startline) => {
303-
(original_startline, line_mapping.original_endline)
304-
}
305-
None => (line_mapping.startline, Some(line_mapping.endline)),
306-
}
307-
});
294+
let (mut startline, mut endline) = match line_mapping.as_ref() {
295+
Some(lm) => (lm.startline, lm.endline),
296+
None => (None, None),
297+
};
298+
let (mut original_startline, mut original_endline) = match line_mapping {
299+
None => (None, None),
300+
Some(lm) => match lm.original_startline {
301+
Some(os) => (Some(os), lm.original_endline),
302+
None => (startline, endline),
303+
},
304+
};
308305

309306
// Normalize inverted ranges independently.
310-
if startline > endline {
311-
std::mem::swap(&mut startline, &mut endline);
307+
if let (Some(s), Some(e)) = (startline, endline) {
308+
if s > e {
309+
startline = Some(e);
310+
endline = Some(s);
311+
}
312312
}
313-
if let Some(oe) = original_endline {
314-
if original_startline > oe {
315-
original_endline = Some(original_startline);
316-
original_startline = oe;
313+
if let (Some(os), Some(oe)) = (original_startline, original_endline) {
314+
if os > oe {
315+
original_startline = Some(oe);
316+
original_endline = Some(os);
317317
}
318318
}
319319

src/cache/mod.rs

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,9 @@ impl<'data> ProguardCache<'data> {
390390
for member in mapping_entries {
391391
// Check if this member would produce a frame (line matching)
392392
let pf_line = prepared_frame.line.unwrap_or(0);
393-
if member.endline == 0
394-
|| (pf_line >= member.startline as usize && pf_line <= member.endline as usize)
395-
{
393+
let startline = member.startline().unwrap_or(0) as usize;
394+
let endline = member.endline().unwrap_or(0) as usize;
395+
if endline == 0 || (pf_line >= startline && pf_line <= endline) {
396396
had_mappings = true;
397397
rewrite_rules.extend(self.decode_rewrite_rules(member));
398398
}
@@ -405,7 +405,7 @@ impl<'data> ProguardCache<'data> {
405405
}
406406
}
407407

408-
let has_line_info = mapping_entries.iter().any(|m| m.endline > 0);
408+
let has_line_info = mapping_entries.iter().any(|m| m.endline().unwrap_or(0) > 0);
409409

410410
Some((
411411
mapping_entries,
@@ -631,9 +631,9 @@ impl<'data> ProguardCache<'data> {
631631
candidates
632632
.iter()
633633
.filter(|m| {
634-
m.endline == 0
635-
|| (callsite_line >= m.startline as usize
636-
&& callsite_line <= m.endline as usize)
634+
m.endline().unwrap_or(0) == 0
635+
|| (callsite_line >= m.startline().unwrap_or(0) as usize
636+
&& callsite_line <= m.endline().unwrap_or(0) as usize)
637637
})
638638
.find_map(|m| {
639639
self.member_outline_pairs(m)
@@ -917,7 +917,7 @@ impl<'r, 'data> RemappedFrameIter<'r, 'data> {
917917
NoLineSelection::IterateBase => {
918918
let mut mapped = None;
919919
for member in members.by_ref() {
920-
if member.endline == 0 {
920+
if member.endline().unwrap_or(0) == 0 {
921921
mapped = map_member_without_lines(
922922
cache,
923923
&frame,
@@ -985,28 +985,29 @@ fn iterate_with_lines<'a>(
985985
) -> Option<StackFrame<'a>> {
986986
let frame_line = frame.line.unwrap_or(0);
987987
for member in members {
988+
let member_endline = member.endline().unwrap_or(0) as usize;
989+
let member_startline = member.startline().unwrap_or(0) as usize;
988990
// If this method has line mappings, skip base (no-line) entries when we have a concrete line.
989-
if has_line_info && frame_line > 0 && member.endline == 0 {
991+
if has_line_info && frame_line > 0 && member_endline == 0 {
990992
continue;
991993
}
992994
// If the mapping entry has no line range, preserve the input line number (if any).
993-
if member.endline == 0 {
995+
if member_endline == 0 {
994996
return map_member_without_lines(cache, frame, member, outer_source_file);
995997
}
996998
// skip any members which do not match our frames line
997-
if member.endline > 0
998-
&& (frame_line < member.startline as usize || frame_line > member.endline as usize)
999-
{
999+
if member_endline > 0 && (frame_line < member_startline || frame_line > member_endline) {
10001000
continue;
10011001
}
1002+
let original_startline = member.original_startline().unwrap_or(0) as usize;
10021003
// parents of inlined frames don't have an `endline`, and
10031004
// the top inlined frame need to be correctly offset.
10041005
let line = if member.original_endline == u32::MAX
1005-
|| member.original_endline == member.original_startline
1006+
|| member.original_endline as usize == original_startline
10061007
{
1007-
member.original_startline as usize
1008+
original_startline
10081009
} else {
1009-
member.original_startline as usize + frame_line - member.startline as usize
1010+
original_startline + frame_line - member_startline
10101011
};
10111012

10121013
let class = cache
@@ -1058,7 +1059,7 @@ enum NoLineSelection<'a> {
10581059

10591060
fn select_no_line_members<'a>(members: &'a [raw::Member]) -> Option<NoLineSelection<'a>> {
10601061
// Prefer base entries (endline == 0) if present.
1061-
let mut base_members = members.iter().filter(|m| m.endline == 0);
1062+
let mut base_members = members.iter().filter(|m| m.endline().unwrap_or(0) == 0);
10621063
if let Some(first_base) = base_members.next() {
10631064
let all_same = base_members.all(|m| {
10641065
m.original_class_offset == first_base.original_class_offset
@@ -1097,10 +1098,7 @@ fn map_member_without_lines<'a>(
10971098
let method = cache.read_string(member.original_name_offset).ok()?;
10981099
let file = synthesize_source_file(class, outer_source_file).map(Cow::Owned);
10991100

1100-
let original_startline = match member.original_startline {
1101-
0 | u32::MAX => None,
1102-
value => Some(value as usize),
1103-
};
1101+
let original_startline = member.original_startline().map(|v| v as usize);
11041102

11051103
Some(StackFrame {
11061104
class,
@@ -1109,8 +1107,8 @@ fn map_member_without_lines<'a>(
11091107
line: Some(resolve_no_line_output_line(
11101108
frame.line.unwrap_or(0),
11111109
original_startline,
1112-
member.startline as usize,
1113-
member.endline as usize,
1110+
member.startline().map(|v| v as usize),
1111+
member.endline().map(|v| v as usize),
11141112
)),
11151113
parameters: frame.parameters,
11161114
method_synthesized: member.is_synthesized(),
@@ -1134,10 +1132,7 @@ fn iterate_without_lines<'a>(
11341132
// Synthesize from class name (input filename is not reliable)
11351133
let file = synthesize_source_file(class, outer_source_file).map(Cow::Owned);
11361134

1137-
let original_startline = match member.original_startline {
1138-
0 | u32::MAX => None,
1139-
value => Some(value as usize),
1140-
};
1135+
let original_startline = member.original_startline().map(|v| v as usize);
11411136

11421137
Some(StackFrame {
11431138
class,
@@ -1146,8 +1141,8 @@ fn iterate_without_lines<'a>(
11461141
line: Some(resolve_no_line_output_line(
11471142
frame.line.unwrap_or(0),
11481143
original_startline,
1149-
member.startline as usize,
1150-
member.endline as usize,
1144+
member.startline().map(|v| v as usize),
1145+
member.endline().map(|v| v as usize),
11511146
)),
11521147
parameters: frame.parameters,
11531148
method_synthesized: member.is_synthesized(),

src/cache/raw.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,26 @@ impl Default for Class {
9898
}
9999
}
100100

101+
/// Sentinel value representing absent/`None` for u32 fields in the binary format.
102+
const NONE_VALUE: u32 = u32::MAX;
103+
101104
/// An entry corresponding to a method line in a proguard cache file.
102105
#[derive(Debug, Clone, PartialEq, Eq, Default)]
103106
#[repr(C)]
104107
pub(crate) struct Member {
105108
/// The obfuscated method name (offset into the string section).
106109
pub(crate) obfuscated_name_offset: u32,
107-
/// The start of the range covered by this entry (1-based).
110+
/// The start of the range covered by this entry (1-based), `u32::MAX` if absent.
108111
pub(crate) startline: u32,
109-
/// The end of the range covered by this entry (inclusive).
112+
/// The end of the range covered by this entry (inclusive), `u32::MAX` if absent.
110113
pub(crate) endline: u32,
111114
/// The original class name (offset into the string section).
112115
pub(crate) original_class_offset: u32,
113116
/// The original file name (offset into the string section).
114117
pub(crate) original_file_offset: u32,
115118
/// The original method name (offset into the string section).
116119
pub(crate) original_name_offset: u32,
117-
/// The original start line (1-based).
120+
/// The original start line (0-based), `u32::MAX` if absent.
118121
pub(crate) original_startline: u32,
119122
/// The original end line (inclusive).
120123
pub(crate) original_endline: u32,
@@ -136,6 +139,7 @@ pub(crate) struct Member {
136139
///
137140
/// `0` means `false`, all other values mean `true`.
138141
pub(crate) is_outline: u8,
142+
139143
/// Reserved space.
140144
pub(crate) _reserved: [u8; 2],
141145
}
@@ -149,6 +153,30 @@ impl Member {
149153
pub(crate) fn is_outline(&self) -> bool {
150154
self.is_outline != 0
151155
}
156+
/// Returns the startline as `Option<u32>`, where `NONE_VALUE` maps to `None`.
157+
pub(crate) fn startline(&self) -> Option<u32> {
158+
if self.startline == NONE_VALUE {
159+
None
160+
} else {
161+
Some(self.startline)
162+
}
163+
}
164+
/// Returns the endline as `Option<u32>`, where `NONE_VALUE` maps to `None`.
165+
pub(crate) fn endline(&self) -> Option<u32> {
166+
if self.endline == NONE_VALUE {
167+
None
168+
} else {
169+
Some(self.endline)
170+
}
171+
}
172+
/// Returns the original_startline as `Option<u32>`, where `NONE_VALUE` maps to `None`.
173+
pub(crate) fn original_startline(&self) -> Option<u32> {
174+
if self.original_startline == NONE_VALUE {
175+
None
176+
} else {
177+
Some(self.original_startline)
178+
}
179+
}
152180
}
153181

154182
unsafe impl Pod for Header {}
@@ -616,22 +644,22 @@ impl<'data> ProguardCache<'data> {
616644
.collect();
617645

618646
let member: Member = Member {
619-
startline: member.startline as u32,
620-
endline: member.endline as u32,
647+
startline: member.startline.map_or(NONE_VALUE, |v| v as u32),
648+
endline: member.endline.map_or(NONE_VALUE, |v| v as u32),
621649
original_class_offset,
622650
original_file_offset,
623651
original_name_offset,
624-
original_startline: member.original_startline as u32,
625-
original_endline: member.original_endline.map_or(u32::MAX, |l| l as u32),
652+
original_startline: member.original_startline.map_or(NONE_VALUE, |v| v as u32),
653+
original_endline: member.original_endline.map_or(NONE_VALUE, |l| l as u32),
626654
obfuscated_name_offset,
627655
params_offset,
628656
is_synthesized,
629657
is_outline,
658+
_reserved: [0; 2],
630659
outline_pairs_offset: 0,
631660
outline_pairs_len: 0,
632661
rewrite_rules_offset: 0,
633662
rewrite_rules_len: 0,
634-
_reserved: [0; 2],
635663
};
636664

637665
MemberInProgress {

0 commit comments

Comments
 (0)