use asc_dex::Dex;
use asc_engine::names::{NameMappings, NameResolver};
use std::collections::BTreeSet;
const DEX: &[u8] = include_bytes!("r8/classes.dex");
const MAPPING: &str = include_str!("r8/mapping.txt");
#[derive(Default)]
struct Defined {
classes: BTreeSet<String>,
fields: BTreeSet<String>,
methods: BTreeSet<String>,
}
fn defined(dex: &Dex<'_>) -> Defined {
let mut d = Defined::default();
for class in dex.class_defs() {
let class = class.unwrap();
d.classes.insert(dex.type_descriptor(class.class_idx).unwrap());
if class.class_data_off == 0 {
continue;
}
let data = dex.class_data(class.class_data_off).unwrap();
for f in data.fields() {
let id = dex.field_id(f.field_idx).unwrap();
let owner = dex.type_descriptor(u32::from(id.class_idx)).unwrap();
let ty = dex.type_descriptor(u32::from(id.type_idx)).unwrap();
d.fields.insert(format!("{owner}->{}:{ty}", dex.string(id.name_idx).unwrap()));
}
for m in data.methods() {
let id = dex.method_id(m.method_idx).unwrap();
let owner = dex.type_descriptor(u32::from(id.class_idx)).unwrap();
let proto = dex.proto_id(u32::from(id.proto_idx)).unwrap();
let params: String = dex
.proto_params(u32::from(id.proto_idx))
.unwrap()
.into_iter()
.map(|t| dex.type_descriptor(u32::from(t)).unwrap())
.collect();
let ret = dex.type_descriptor(proto.return_type_idx).unwrap();
d.methods.insert(format!("{owner}->{}({params}){ret}", dex.string(id.name_idx).unwrap()));
}
}
d
}
#[test]
fn r8_mapping_keys_are_the_dex_names() {
let parsed = NameMappings::from_proguard(MAPPING).unwrap();
assert_eq!(parsed.warnings, []);
let m = parsed.mappings;
let dex = Dex::parse(DEX).unwrap();
let d = defined(&dex);
for key in m.classes().keys() {
assert!(d.classes.contains(key), "class {key} not in the DEX: {:?}", d.classes);
}
for key in m.fields().keys() {
assert!(d.fields.contains(key), "field {key} not in the DEX: {:?}", d.fields);
}
for key in m.methods().keys() {
assert!(d.methods.contains(key), "method {key} not in the DEX: {:?}", d.methods);
}
let classes: Vec<(&str, &str)> = m.classes().iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
assert_eq!(
classes,
[
("Lz/a;", "Lcom/example/shop/model/Cart$1;"),
("Lz/b;", "Lcom/example/shop/model/Cart$Line$Tag;"),
("Lz/c;", "Lcom/example/shop/model/Cart$Line;"),
("Lz/d;", "Lcom/example/shop/model/Cart;"),
("Lz/e;", "Lcom/example/shop/model/Item;"),
("Lz/f;", "Lcom/example/shop/util/Pricing;"),
]
);
let unnamed: Vec<&String> = d.methods.iter().filter(|s| m.method(s).is_none()).filter(|s| !s.contains("-><")).collect();
assert_eq!(unnamed, ["Lcom/example/shop/Main;->main([Ljava/lang/String;)V", "Lz/a;->run()V", "Lz/e;->toString()Ljava/lang/String;"]);
assert!(d.fields.iter().all(|f| m.field(f).is_some()), "{:?}", d.fields);
assert_eq!(m.field("Lz/d;->a:Ljava/util/ArrayList;"), Some("items"));
assert_eq!(m.field("Lz/c;->a:Lz/d;"), Some("owner"));
assert_eq!(m.field("Lz/a;->a:[Ljava/lang/String;"), Some("val$args"));
assert_eq!(m.method("Lz/c;->a([Lz/e;)J"), Some("cost"));
assert_eq!(m.method("Lz/d;->a(Ljava/lang/String;I)V"), Some("add"));
assert_eq!(m.method("Lz/d;->b(Lz/e;)V"), Some("add"));
assert_eq!(m.method("Lz/d;->g()[Lz/c;"), Some("lines"));
assert_eq!(m.method("Lz/f;->b([Lz/c;)J"), Some("total"));
assert_eq!(m.method("Lz/f;->c([Lz/e;Z)J"), Some("total"));
assert!(m.methods().values().all(|name| !name.contains('.') && name != "access$000"));
}