Skip to content

Commit 3cf716b

Browse files
committed
fix: make start & endline optional
1 parent b17c5c1 commit 3cf716b

File tree

1 file changed

+97
-13
lines changed

1 file changed

+97
-13
lines changed

apps/backend/src/mappings/proguard.rs

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ struct ClassMapping {
3030
struct MethodMapping {
3131
original_name: String,
3232
obfuscated_name: String,
33-
start_line: u32,
34-
end_line: u32,
33+
start_line: Option<u32>,
34+
end_line: Option<u32>,
3535
}
3636

3737
impl ProguardMapping {
@@ -100,11 +100,17 @@ impl ProguardMapping {
100100
}
101101

102102
fn parse_many<'a>(inputs: impl IntoIterator<Item = &'a str>) -> Result<Self, AppError> {
103-
let mut classes = HashMap::new();
103+
let mut classes: HashMap<String, ClassMapping> = HashMap::new();
104104

105105
for input in inputs {
106106
let mapping = Self::parse(input)?;
107-
classes.extend(mapping.classes);
107+
for (obfuscated_name, class) in mapping.classes {
108+
if let Some(existing) = classes.get_mut(&obfuscated_name) {
109+
existing.merge(class);
110+
} else {
111+
classes.insert(obfuscated_name, class);
112+
}
113+
}
108114
}
109115

110116
Ok(ProguardMapping { classes })
@@ -214,8 +220,11 @@ impl ProguardMapping {
214220
.iter()
215221
.find(|m| {
216222
m.obfuscated_name == obf_method
217-
&& line_num >= m.start_line
218-
&& line_num <= m.end_line
223+
&& matches!(
224+
(m.start_line, m.end_line),
225+
(Some(start_line), Some(end_line))
226+
if line_num >= start_line && line_num <= end_line
227+
)
219228
})
220229
.or_else(|| {
221230
class
@@ -251,6 +260,16 @@ impl ProguardMapping {
251260
}
252261
}
253262

263+
impl ClassMapping {
264+
fn merge(&mut self, other: ClassMapping) {
265+
if self.file_name.is_none() {
266+
self.file_name = other.file_name;
267+
}
268+
self.methods.extend(other.methods);
269+
self.fields.extend(other.fields);
270+
}
271+
}
272+
254273
/// Parse "original.Class -> obfuscated.Class:" into (original, obfuscated)
255274
fn parse_class_line(line: &str) -> Option<(String, String)> {
256275
let line = line.strip_suffix(':')?;
@@ -274,11 +293,18 @@ fn parse_member_line(line: &str, class: &mut ClassMapping) {
274293
class.methods.push(MethodMapping {
275294
original_name: method.name,
276295
obfuscated_name: obfuscated,
277-
start_line: method.start_line,
278-
end_line: method.end_line,
296+
start_line: Some(method.start_line),
297+
end_line: Some(method.end_line),
279298
});
280299
} else if original_part.contains('(') {
281-
// Method without line numbers — no range info to store
300+
if let Some(method_name) = parse_method_name(original_part) {
301+
class.methods.push(MethodMapping {
302+
original_name: method_name,
303+
obfuscated_name: obfuscated,
304+
start_line: None,
305+
end_line: None,
306+
});
307+
}
282308
} else {
283309
// Field mapping: "type fieldName -> obfuscated"
284310
// We just need the field name (last token before ->)
@@ -295,6 +321,17 @@ struct ParsedMethod {
295321
end_line: u32,
296322
}
297323

324+
fn parse_method_name(s: &str) -> Option<String> {
325+
let s = s.trim();
326+
let paren_pos = s.find('(')?;
327+
let before_paren = &s[..paren_pos];
328+
let method_name = before_paren
329+
.rsplit_once(' ')
330+
.map(|(_, name)| name)
331+
.unwrap_or(before_paren);
332+
Some(method_name.to_string())
333+
}
334+
298335
fn parse_method_with_lines(s: &str) -> Option<ParsedMethod> {
299336
let s = s.trim();
300337
// Format: "startLine:endLine:returnType methodName(params)"
@@ -435,10 +472,26 @@ core.file.Validatable -> a.a.b:
435472
.filter(|m| m.obfuscated_name == "<init>")
436473
.collect();
437474
assert_eq!(init_methods.len(), 2);
438-
assert_eq!(init_methods[0].start_line, 29);
439-
assert_eq!(init_methods[0].end_line, 33);
440-
assert_eq!(init_methods[1].start_line, 42);
441-
assert_eq!(init_methods[1].end_line, 43);
475+
assert_eq!(init_methods[0].start_line, Some(29));
476+
assert_eq!(init_methods[0].end_line, Some(33));
477+
assert_eq!(init_methods[1].start_line, Some(42));
478+
assert_eq!(init_methods[1].end_line, Some(43));
479+
}
480+
481+
#[test]
482+
fn parse_method_mappings_without_line_numbers() {
483+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
484+
let file_io = &mapping.classes["a.a.a"];
485+
486+
let load_method = file_io
487+
.methods
488+
.iter()
489+
.find(|m| m.obfuscated_name == "b")
490+
.expect("load method should be retained");
491+
492+
assert_eq!(load_method.original_name, "load");
493+
assert_eq!(load_method.start_line, None);
494+
assert_eq!(load_method.end_line, None);
442495
}
443496

444497
#[test]
@@ -528,6 +581,14 @@ java.lang.RuntimeException: oops
528581
assert_eq!(output, "\tat core.file.FileIO.reload(FileIO.java)");
529582
}
530583

584+
#[test]
585+
fn retrace_unknown_source_uses_method_without_line_numbers() {
586+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
587+
let input = "\tat a.a.a.b(Unknown Source)";
588+
let output = mapping.retrace(input);
589+
assert_eq!(output, "\tat core.file.FileIO.load(FileIO.java)");
590+
}
591+
531592
#[test]
532593
fn parse_many_combines_split_mapping_files() {
533594
const PART_ONE: &str = r#"core.file.FileIO -> a.a.a:
@@ -548,6 +609,29 @@ java.lang.RuntimeException: oops
548609
);
549610
}
550611

612+
#[test]
613+
fn parse_many_merges_split_class_mappings() {
614+
const PART_ONE: &str = r#"core.file.FileIO -> a.a.a:
615+
# {"fileName":"FileIO.java","id":"sourceFile"}
616+
92:92:core.file.FileIO reload() -> c
617+
"#;
618+
const PART_TWO: &str = r#"core.file.FileIO -> a.a.a:
619+
java.lang.Object load() -> b
620+
"#;
621+
622+
let mapping = parse_test_mappings([PART_ONE, PART_TWO]);
623+
let file_io = &mapping.classes["a.a.a"];
624+
625+
assert_eq!(file_io.file_name.as_deref(), Some("FileIO.java"));
626+
assert!(file_io.methods.iter().any(|m| m.obfuscated_name == "c"));
627+
assert!(
628+
file_io
629+
.methods
630+
.iter()
631+
.any(|m| m.obfuscated_name == "b" && m.original_name == "load")
632+
);
633+
}
634+
551635
#[test]
552636
fn retrace_stacktrace_uses_all_mapping_parts() {
553637
const PART_ONE: &str = r#"core.file.FileIO -> a.a.a:

0 commit comments

Comments
 (0)