Skip to content

Commit c835b6e

Browse files
authored
Merge pull request #25 from faststats-dev/feat/proguard
fix: allow multiple files for progurad
2 parents ed88bb7 + 764ff88 commit c835b6e

File tree

2 files changed

+112
-19
lines changed

2 files changed

+112
-19
lines changed

apps/backend/src/mappings/proguard.rs

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::storage::Storage;
99
use super::OriginalPosition;
1010
use super::s3_key;
1111

12+
const PROGUARD_DIR: &str = "proguard";
1213
const PROGUARD_FILE_NAME: &str = "proguard/mapping.txt";
1314

1415
/// A parsed proguard mapping file.
@@ -99,6 +100,17 @@ impl ProguardMapping {
99100
Ok(ProguardMapping { classes })
100101
}
101102

103+
fn parse_many<'a>(inputs: impl IntoIterator<Item = &'a str>) -> Result<Self, AppError> {
104+
let mut classes = HashMap::new();
105+
106+
for input in inputs {
107+
let mapping = Self::parse(input)?;
108+
classes.extend(mapping.classes);
109+
}
110+
111+
Ok(ProguardMapping { classes })
112+
}
113+
102114
fn retrace(&self, stacktrace: &str) -> String {
103115
let mut output = String::with_capacity(stacktrace.len());
104116
for (i, line) in stacktrace.lines().enumerate() {
@@ -328,21 +340,44 @@ pub async fn ingest(
328340
build_id: &str,
329341
mapping: &str,
330342
) -> Result<(), AppError> {
331-
let key = s3_key(project_id, build_id, PROGUARD_FILE_NAME);
343+
let key = proguard_s3_key(project_id, build_id);
332344
storage.put(&key, mapping.as_bytes()).await
333345
}
334346

335-
pub fn retrace_stacktrace(data: &[u8], stacktrace: &str) -> Result<String, AppError> {
336-
let content = std::str::from_utf8(data)
337-
.map_err(|e| AppError::BadRequest(format!("invalid proguard mapping: {e}")))?;
338-
let mapping = ProguardMapping::parse(content)?;
347+
pub fn retrace_stacktrace<'a>(
348+
mapping_parts: impl IntoIterator<Item = &'a [u8]>,
349+
stacktrace: &str,
350+
) -> Result<String, AppError> {
351+
let mut contents = Vec::new();
352+
353+
for data in mapping_parts {
354+
let content = std::str::from_utf8(data)
355+
.map_err(|e| AppError::BadRequest(format!("invalid proguard mapping: {e}")))?;
356+
contents.push(content);
357+
}
358+
359+
let mapping = ProguardMapping::parse_many(contents)?;
339360
Ok(mapping.retrace(stacktrace))
340361
}
341362

363+
pub fn proguard_s3_prefix(project_id: Uuid, build_id: &str) -> String {
364+
s3_key(project_id, build_id, PROGUARD_DIR)
365+
}
366+
342367
pub fn proguard_s3_key(project_id: Uuid, build_id: &str) -> String {
343368
s3_key(project_id, build_id, PROGUARD_FILE_NAME)
344369
}
345370

371+
#[cfg(test)]
372+
fn parse_test_mapping(input: &str) -> ProguardMapping {
373+
ProguardMapping::parse(input).unwrap()
374+
}
375+
376+
#[cfg(test)]
377+
fn parse_test_mappings<'a>(inputs: impl IntoIterator<Item = &'a str>) -> ProguardMapping {
378+
ProguardMapping::parse_many(inputs).unwrap()
379+
}
380+
346381
#[cfg(test)]
347382
mod tests {
348383
use super::*;
@@ -368,7 +403,7 @@ core.file.Validatable -> a.a.b:
368403

369404
#[test]
370405
fn parse_class_mappings() {
371-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
406+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
372407
assert!(mapping.classes.contains_key("a.a.a"));
373408
assert!(mapping.classes.contains_key("a.a.b"));
374409

@@ -379,7 +414,7 @@ core.file.Validatable -> a.a.b:
379414

380415
#[test]
381416
fn parse_field_mappings() {
382-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
417+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
383418
let file_io = &mapping.classes["a.a.a"];
384419
assert_eq!(file_io.fields.get("a"), Some(&"file".to_string()));
385420
assert_eq!(file_io.fields.get("b"), Some(&"charset".to_string()));
@@ -388,7 +423,7 @@ core.file.Validatable -> a.a.b:
388423

389424
#[test]
390425
fn parse_method_mappings() {
391-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
426+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
392427
let file_io = &mapping.classes["a.a.a"];
393428

394429
let init_methods: Vec<_> = file_io
@@ -405,22 +440,22 @@ core.file.Validatable -> a.a.b:
405440

406441
#[test]
407442
fn resolve_class() {
408-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
443+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
409444
let result = mapping.resolve("a.a.a", None, None).unwrap();
410445
assert_eq!(result.source, "core.file.FileIO");
411446
}
412447

413448
#[test]
414449
fn resolve_method_with_line() {
415-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
450+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
416451
let result = mapping.resolve("a.a.a", Some("c"), Some(92)).unwrap();
417452
assert_eq!(result.source, "core.file.FileIO");
418453
assert_eq!(result.name.as_deref(), Some("reload"));
419454
}
420455

421456
#[test]
422457
fn resolve_method_without_line() {
423-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
458+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
424459
let result = mapping.resolve("a.a.a", Some("a"), None).unwrap();
425460
assert_eq!(result.source, "core.file.FileIO");
426461
// Should find one of the methods named "a" (setRoot or getRoot)
@@ -429,14 +464,14 @@ core.file.Validatable -> a.a.b:
429464

430465
#[test]
431466
fn resolve_unknown_class() {
432-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
467+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
433468
let result = mapping.resolve("z.z.z", None, None);
434469
assert!(result.is_err());
435470
}
436471

437472
#[test]
438473
fn retrace_stacktrace_full() {
439-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
474+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
440475
let input = "\
441476
java.lang.NullPointerException: something broke
442477
\tat a.a.a.c(SourceFile:92)
@@ -456,7 +491,7 @@ java.lang.NullPointerException: something broke
456491

457492
#[test]
458493
fn retrace_preserves_unknown_lines() {
459-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
494+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
460495
let input = "\
461496
java.lang.RuntimeException: oops
462497
\tat a.a.a.c(SourceFile:92)
@@ -476,17 +511,62 @@ java.lang.RuntimeException: oops
476511

477512
#[test]
478513
fn retrace_caused_by() {
479-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
514+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
480515
let input = "Caused by: a.a.a: some message";
481516
let output = mapping.retrace(input);
482517
assert_eq!(output, "Caused by: core.file.FileIO: some message");
483518
}
484519

485520
#[test]
486521
fn retrace_unknown_source() {
487-
let mapping = ProguardMapping::parse(SAMPLE_MAPPING).unwrap();
522+
let mapping = parse_test_mapping(SAMPLE_MAPPING);
488523
let input = "\tat a.a.a.c(Unknown Source)";
489524
let output = mapping.retrace(input);
490525
assert_eq!(output, "\tat core.file.FileIO.reload(FileIO.java)");
491526
}
527+
528+
#[test]
529+
fn parse_many_combines_split_mapping_files() {
530+
const PART_ONE: &str = r#"core.file.FileIO -> a.a.a:
531+
# {"fileName":"FileIO.java","id":"sourceFile"}
532+
92:92:core.file.FileIO reload() -> c
533+
"#;
534+
const PART_TWO: &str = r#"core.file.Validatable -> a.a.b:
535+
# {"fileName":"Validatable.java","id":"sourceFile"}
536+
26:26:core.file.FileIO validate() -> a_
537+
"#;
538+
539+
let mapping = parse_test_mappings([PART_ONE, PART_TWO]);
540+
541+
assert_eq!(mapping.classes["a.a.a"].original_name, "core.file.FileIO");
542+
assert_eq!(
543+
mapping.classes["a.a.b"].file_name.as_deref(),
544+
Some("Validatable.java")
545+
);
546+
}
547+
548+
#[test]
549+
fn retrace_stacktrace_uses_all_mapping_parts() {
550+
const PART_ONE: &str = r#"core.file.FileIO -> a.a.a:
551+
# {"fileName":"FileIO.java","id":"sourceFile"}
552+
92:92:core.file.FileIO reload() -> c
553+
"#;
554+
const PART_TWO: &str = r#"core.file.Validatable -> a.a.b:
555+
# {"fileName":"Validatable.java","id":"sourceFile"}
556+
26:26:core.file.FileIO validate() -> a_
557+
"#;
558+
559+
let input = "\
560+
\tat a.a.a.c(SourceFile:92)
561+
\tat a.a.b.a_(SourceFile:26)";
562+
563+
let output = retrace_stacktrace([PART_ONE.as_bytes(), PART_TWO.as_bytes()], input).unwrap();
564+
565+
assert_eq!(
566+
output,
567+
"\
568+
\tat core.file.FileIO.reload(FileIO.java:92)
569+
\tat core.file.Validatable.validate(Validatable.java:26)"
570+
);
571+
}
492572
}

apps/backend/src/routes.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,22 @@ pub async fn apply_sourcemap(
317317
} => {
318318
require_non_empty("build_id", build_id)?;
319319
require_non_empty("stacktrace", stacktrace)?;
320-
let key = crate::mappings::proguard::proguard_s3_key(auth.project_id, build_id);
321-
let data = state.storage.get(&key).await?;
322-
let retraced = crate::mappings::proguard::retrace_stacktrace(&data, stacktrace)?;
320+
let prefix = crate::mappings::proguard::proguard_s3_prefix(auth.project_id, build_id);
321+
let mut keys = state.storage.list_prefix_keys(&prefix).await?;
322+
keys.sort_unstable();
323+
if keys.is_empty() {
324+
return Err(AppError::NotFound);
325+
}
326+
327+
let mut mappings = Vec::with_capacity(keys.len());
328+
for key in keys {
329+
mappings.push(state.storage.get(&key).await?);
330+
}
331+
332+
let retraced = crate::mappings::proguard::retrace_stacktrace(
333+
mappings.iter().map(Vec::as_slice),
334+
stacktrace,
335+
)?;
323336
ApplyResponse::Proguard {
324337
ok: true,
325338
stacktrace: retraced,

0 commit comments

Comments
 (0)