proguard 5.10.2

Basic proguard mapping file handling for Rust
Documentation
use std::sync::LazyLock;

use proguard::{ProguardCache, ProguardMapper, ProguardMapping, StackFrame};
#[cfg(feature = "uuid")]
use uuid::uuid;

static MAPPING: &[u8] = include_bytes!("res/mapping.txt");

static MAPPING_WIN: LazyLock<Vec<u8>> = LazyLock::new(|| {
    MAPPING
        .iter()
        .flat_map(|&byte| {
            if byte == b'\n' {
                vec![b'\r', b'\n']
            } else {
                vec![byte]
            }
        })
        .collect()
});

#[test]
fn test_basic() {
    let mapping = ProguardMapping::new(MAPPING);
    assert!(mapping.is_valid());
    assert!(mapping.has_line_info());

    let mapper = ProguardMapper::new(mapping);

    let class = mapper.remap_class("android.support.constraint.ConstraintLayout$a");
    assert_eq!(
        class,
        Some("android.support.constraint.ConstraintLayout$LayoutParams")
    );
}

#[test]
fn test_basic_cache() {
    let mapping = ProguardMapping::new(MAPPING);
    assert!(mapping.is_valid());
    assert!(mapping.has_line_info());

    let mut cache = Vec::new();
    ProguardCache::write(&mapping, &mut cache).unwrap();
    let cache = ProguardCache::parse(&cache).unwrap();

    let class = cache.remap_class("android.support.constraint.ConstraintLayout$a");
    assert_eq!(
        class,
        Some("android.support.constraint.ConstraintLayout$LayoutParams")
    );
}

#[test]
fn test_basic_win() {
    let mapping = ProguardMapping::new(&MAPPING_WIN[..]);
    assert!(mapping.is_valid());
    assert!(mapping.has_line_info());

    let mapper = ProguardMapper::new(mapping);

    let class = mapper.remap_class("android.support.constraint.ConstraintLayout$a");
    assert_eq!(
        class,
        Some("android.support.constraint.ConstraintLayout$LayoutParams")
    );
}

#[test]
fn test_basic_win_cache() {
    let mapping = ProguardMapping::new(&MAPPING_WIN[..]);
    assert!(mapping.is_valid());
    assert!(mapping.has_line_info());

    let mut cache = Vec::new();
    ProguardCache::write(&mapping, &mut cache).unwrap();
    let cache = ProguardCache::parse(&cache).unwrap();

    let class = cache.remap_class("android.support.constraint.ConstraintLayout$a");
    assert_eq!(
        class,
        Some("android.support.constraint.ConstraintLayout$LayoutParams")
    );
}

#[test]
fn test_method_matches() {
    let mapper = ProguardMapper::new(ProguardMapping::new(MAPPING));

    let mut mapped =
        mapper.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 320));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "remove",
            320,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);

    let mut mapped =
        mapper.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 200));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "put",
            200,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);
}

#[test]
fn test_method_matches_cache() {
    let mapping = ProguardMapping::new(MAPPING);

    let mut cache = Vec::new();
    ProguardCache::write(&mapping, &mut cache).unwrap();
    let cache = ProguardCache::parse(&cache).unwrap();

    let mut mapped =
        cache.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 320));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "remove",
            320,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);

    let mut mapped =
        cache.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 200));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "put",
            200,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);
}

#[test]
fn test_method_matches_win() {
    let mapper = ProguardMapper::new(ProguardMapping::new(&MAPPING_WIN[..]));

    let mut mapped =
        mapper.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 320));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "remove",
            320,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);

    let mut mapped =
        mapper.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 200));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "put",
            200,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);
}

#[test]
fn test_method_matches_win_cache() {
    let mapping = ProguardMapping::new(&MAPPING_WIN[..]);

    let mut cache = Vec::new();
    ProguardCache::write(&mapping, &mut cache).unwrap();
    let cache = ProguardCache::parse(&cache).unwrap();

    let mut mapped =
        cache.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 320));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "remove",
            320,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);

    let mut mapped =
        cache.remap_frame(&StackFrame::new("android.support.constraint.a.a", "a", 200));

    assert_eq!(
        mapped.next().unwrap(),
        StackFrame::with_file(
            "android.support.constraint.solver.ArrayLinkedVariables",
            "put",
            200,
            "ArrayLinkedVariables.java"
        )
    );
    assert_eq!(mapped.next(), None);
}

#[test]
fn test_inlines() {
    let mapping = ProguardMapping::new(include_bytes!("res/mapping-inlines.txt"));
    assert!(mapping.is_valid());
    assert!(mapping.has_line_info());
    #[cfg(feature = "uuid")]
    {
        assert_eq!(
            mapping.uuid(),
            uuid!("3828bd45-950f-5e77-9737-b6b3a1d80299")
        );
    }

    let mapper = ProguardMapper::new(mapping);

    let raw = r#"java.lang.RuntimeException: Button press caused an exception!
    at io.sentry.sample.MainActivity.t(MainActivity.java:1)
    at e.a.c.a.onClick
    at android.view.View.performClick(View.java:7125)
    at android.view.View.performClickInternal(View.java:7102)
    at android.view.View.access$3500(View.java:801)
    at android.view.View$PerformClick.run(View.java:27336)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)"#;
    let remapped = mapper.remap_stacktrace(raw).unwrap();

    assert_eq!(
        remapped.trim(),
        r#"java.lang.RuntimeException: Button press caused an exception!
    at io.sentry.sample.MainActivity.bar(MainActivity.java:54)
    at io.sentry.sample.MainActivity.foo(MainActivity.java:44)
    at io.sentry.sample.MainActivity.onClickHandler(MainActivity.java:40)
    at e.a.c.a.onClick
    at android.view.View.performClick(View.java:7125)
    at android.view.View.performClickInternal(View.java:7102)
    at android.view.View.access$3500(View.java:801)
    at android.view.View$PerformClick.run(View.java:27336)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)"#
    );
}

#[test]
fn test_inlines_cache() {
    let mapping = ProguardMapping::new(include_bytes!("res/mapping-inlines.txt"));
    assert!(mapping.is_valid());
    assert!(mapping.has_line_info());
    #[cfg(feature = "uuid")]
    {
        assert_eq!(
            mapping.uuid(),
            uuid!("3828bd45-950f-5e77-9737-b6b3a1d80299")
        );
    }

    let mut cache = Vec::new();
    ProguardCache::write(&mapping, &mut cache).unwrap();
    let cache = ProguardCache::parse(&cache).unwrap();

    let raw = r#"java.lang.RuntimeException: Button press caused an exception!
    at io.sentry.sample.MainActivity.t(MainActivity.java:1)
    at e.a.c.a.onClick
    at android.view.View.performClick(View.java:7125)
    at android.view.View.performClickInternal(View.java:7102)
    at android.view.View.access$3500(View.java:801)
    at android.view.View$PerformClick.run(View.java:27336)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)"#;
    let remapped = cache.remap_stacktrace(raw).unwrap();

    assert_eq!(
        remapped.trim(),
        r#"java.lang.RuntimeException: Button press caused an exception!
    at io.sentry.sample.MainActivity.bar(MainActivity.java:54)
    at io.sentry.sample.MainActivity.foo(MainActivity.java:44)
    at io.sentry.sample.MainActivity.onClickHandler(MainActivity.java:40)
    at e.a.c.a.onClick
    at android.view.View.performClick(View.java:7125)
    at android.view.View.performClickInternal(View.java:7102)
    at android.view.View.access$3500(View.java:801)
    at android.view.View$PerformClick.run(View.java:27336)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)"#
    );
}

#[cfg(feature = "uuid")]
#[test]
fn test_uuid() {
    assert_eq!(
        ProguardMapping::new(MAPPING).uuid(),
        uuid!("5cd8e873-1127-5276-81b7-8ff25043ecfd")
    );
}

#[cfg(feature = "uuid")]
#[test]
fn test_uuid_win() {
    assert_eq!(
        ProguardMapping::new(&MAPPING_WIN[..]).uuid(),
        uuid!("71d468f2-0dc4-5017-9f12-1a81081913ef")
    );
}