1use super::dalvik_types;
15use crate::Engine;
16use sha2::{Digest, Sha256};
17use std::collections::BTreeMap;
18use std::fmt;
19use std::sync::Arc;
20
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
23#[non_exhaustive]
24pub enum MappingError {
25 #[error("not a class descriptor: {0:?} (want Lpkg/Name;)")]
27 Class(String),
28 #[error("not a field signature: {0:?} (want Lowner;->name:Type)")]
30 Field(String),
31 #[error("not a method signature: {0:?} (want Lowner;->name(args)Ret)")]
33 Method(String),
34 #[error("not a usable new name: {0:?}")]
36 NewName(String),
37 #[error("not a usable new class name: {0:?} (want Lpkg/Name;)")]
39 NewClass(String),
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
44#[non_exhaustive]
45pub struct FieldRef<'a> {
46 pub class: &'a str,
48 pub name: &'a str,
50 pub ty: &'a str,
52}
53
54impl<'a> FieldRef<'a> {
55 pub fn parse(signature: &'a str) -> Result<FieldRef<'a>, MappingError> {
62 let bad = || MappingError::Field(signature.to_string());
63 let (class, member) = signature.split_once("->").ok_or_else(bad)?;
64 let (name, ty) = member.split_once(':').ok_or_else(bad)?;
65 let field_type = matches!(dalvik_types(ty).as_deref(), Some([t]) if *t != "V");
66 (is_class_descriptor(class) && is_simple_name(name) && field_type).then_some(FieldRef { class, name, ty }).ok_or_else(bad)
67 }
68}
69
70impl fmt::Display for FieldRef<'_> {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}->{}:{}", self.class, self.name, self.ty)
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
79#[non_exhaustive]
80pub struct MethodRef<'a> {
81 pub class: &'a str,
83 pub name: &'a str,
85 pub proto: &'a str,
87}
88
89impl<'a> MethodRef<'a> {
90 pub fn parse(signature: &'a str) -> Result<MethodRef<'a>, MappingError> {
96 let bad = || MappingError::Method(signature.to_string());
97 let (class, member) = signature.split_once("->").ok_or_else(bad)?;
98 let open = member.find('(').ok_or_else(bad)?;
99 let (name, proto) = member.split_at(open);
100 let (params, ret) = proto[1..].split_once(')').ok_or_else(bad)?;
101 let name_ok = is_simple_name(name) || name == "<init>" || name == "<clinit>";
102 let params_ok = params.is_empty() || dalvik_types(params).is_some_and(|ps| !ps.contains(&"V"));
103 let ret_ok = matches!(dalvik_types(ret).as_deref(), Some([_]));
104 (is_class_descriptor(class) && name_ok && params_ok && ret_ok).then_some(MethodRef { class, name, proto }).ok_or_else(bad)
105 }
106}
107
108impl fmt::Display for MethodRef<'_> {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(f, "{}->{}{}", self.class, self.name, self.proto)
111 }
112}
113
114pub(super) fn is_class_descriptor(s: &str) -> bool {
117 s.strip_prefix('L').and_then(|d| d.strip_suffix(';')).is_some_and(|inner| inner.split('/').all(is_simple_name))
118}
119
120fn is_simple_name(s: &str) -> bool {
124 !s.is_empty() && !s.contains(['/', ';', '.', '[', '(', ')', ':', '<', '>'])
125}
126
127fn is_new_name(s: &str) -> bool {
131 is_simple_name(s) && !s.chars().any(|c| c.is_whitespace() || c.is_control())
132}
133
134fn is_new_class(s: &str) -> bool {
137 s.strip_prefix('L').and_then(|d| d.strip_suffix(';')).is_some_and(|inner| inner.split('/').all(is_new_name))
138}
139
140pub trait NameResolver {
145 fn class(&self, descriptor: &str) -> Option<&str>;
149 fn field(&self, signature: &str) -> Option<&str>;
151 fn method(&self, signature: &str) -> Option<&str>;
153}
154
155#[derive(Debug, Clone, Default, PartialEq, Eq)]
162pub struct NameMappings {
163 classes: BTreeMap<String, String>,
164 fields: BTreeMap<String, String>,
165 methods: BTreeMap<String, String>,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
171pub struct MappingsHash([u8; 32]);
172
173impl MappingsHash {
174 pub fn as_bytes(&self) -> &[u8; 32] {
176 &self.0
177 }
178}
179
180impl fmt::Display for MappingsHash {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 self.0.iter().try_for_each(|b| write!(f, "{b:02x}"))
184 }
185}
186
187impl NameMappings {
188 pub fn new() -> NameMappings {
190 NameMappings::default()
191 }
192
193 pub fn is_empty(&self) -> bool {
195 self.classes.is_empty() && self.fields.is_empty() && self.methods.is_empty()
196 }
197
198 pub fn insert_class(&mut self, descriptor: &str, new_descriptor: &str) -> Result<Option<String>, MappingError> {
206 if !is_class_descriptor(descriptor) {
207 return Err(MappingError::Class(descriptor.to_string()));
208 }
209 if !is_new_class(new_descriptor) {
210 return Err(MappingError::NewClass(new_descriptor.to_string()));
211 }
212 Ok(self.classes.insert(descriptor.to_string(), new_descriptor.to_string()))
213 }
214
215 pub fn insert_field(&mut self, signature: &str, new_name: &str) -> Result<Option<String>, MappingError> {
223 FieldRef::parse(signature)?;
224 Ok(self.fields.insert(signature.to_string(), checked_new_name(new_name)?))
225 }
226
227 pub fn insert_method(&mut self, signature: &str, new_name: &str) -> Result<Option<String>, MappingError> {
236 let method = MethodRef::parse(signature)?;
237 if method.name.starts_with('<') {
238 return Err(MappingError::Method(signature.to_string()));
239 }
240 Ok(self.methods.insert(signature.to_string(), checked_new_name(new_name)?))
241 }
242
243 pub fn classes(&self) -> &BTreeMap<String, String> {
245 &self.classes
246 }
247
248 pub fn fields(&self) -> &BTreeMap<String, String> {
250 &self.fields
251 }
252
253 pub fn methods(&self) -> &BTreeMap<String, String> {
255 &self.methods
256 }
257
258 #[must_use]
263 pub fn restricted_to(&self, keep: impl Fn(&str) -> bool) -> NameMappings {
264 let owned_by_kept = |signature: &String| signature.split_once("->").is_some_and(|(class, _)| keep(class));
265 let filter = |map: &BTreeMap<String, String>, keep_key: &dyn Fn(&String) -> bool| -> BTreeMap<String, String> {
266 map.iter().filter(|(k, _)| keep_key(k)).map(|(k, v)| (k.clone(), v.clone())).collect()
267 };
268 NameMappings {
269 classes: filter(&self.classes, &|d| keep(d)),
270 fields: filter(&self.fields, &owned_by_kept),
271 methods: filter(&self.methods, &owned_by_kept),
272 }
273 }
274
275 pub fn hash(&self) -> MappingsHash {
278 let mut h = Sha256::new();
279 h.update(b"asc-name-mappings/2");
280 for (tag, map) in [(b'c', &self.classes), (b'f', &self.fields), (b'm', &self.methods)] {
281 h.update([tag]);
282 h.update((map.len() as u64).to_le_bytes());
283 for (k, v) in map {
284 for s in [k, v] {
285 h.update((s.len() as u64).to_le_bytes());
286 h.update(s.as_bytes());
287 }
288 }
289 }
290 MappingsHash(h.finalize().into())
291 }
292}
293
294fn checked_new_name(new_name: &str) -> Result<String, MappingError> {
295 if is_new_name(new_name) { Ok(new_name.to_string()) } else { Err(MappingError::NewName(new_name.to_string())) }
296}
297
298impl NameResolver for NameMappings {
299 fn class(&self, descriptor: &str) -> Option<&str> {
300 self.classes.get(descriptor).map(String::as_str)
301 }
302
303 fn field(&self, signature: &str) -> Option<&str> {
304 self.fields.get(signature).map(String::as_str)
305 }
306
307 fn method(&self, signature: &str) -> Option<&str> {
308 self.methods.get(signature).map(String::as_str)
309 }
310}
311
312impl Engine {
313 pub fn with_name_mappings(mut self, mappings: impl Into<Arc<NameMappings>>) -> Engine {
317 self.name_mappings = mappings.into();
318 self
319 }
320
321 pub fn name_mappings(&self) -> &NameMappings {
327 &self.name_mappings
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn signatures_round_trip_through_their_parts() {
337 let fields = ["Lbin/mt/json/JSONObject;->a:I", "Ll/ۢۤܺ;->ۢ:[Ljava/lang/String;", "LA$B$1;->this$0:LA$B;", "LC;->-x:J"];
338 for s in fields {
339 assert_eq!(FieldRef::parse(s).unwrap().to_string(), s, "{s}");
340 }
341 let f = FieldRef::parse("Lpkg/Outer$Inner;->val$x:Lpkg/Outer;").unwrap();
342 assert_eq!(f, FieldRef { class: "Lpkg/Outer$Inner;", name: "val$x", ty: "Lpkg/Outer;" });
343
344 let methods = ["Lc;->run()V", "Lc;->run(I)V", "Lc;-><init>(ILjava/lang/String;[[B)V", "La/b$c;->ۢۤ(La/b;)La/b$c;"];
345 for s in methods {
346 assert_eq!(MethodRef::parse(s).unwrap().to_string(), s, "{s}");
347 }
348 let m = MethodRef::parse("Lpkg/Outer$1;->access$000(Lpkg/Outer;)I").unwrap();
349 assert_eq!(m, MethodRef { class: "Lpkg/Outer$1;", name: "access$000", proto: "(Lpkg/Outer;)I" });
350 }
351
352 #[test]
353 fn non_canonical_signatures_are_refused() {
354 for s in ["Lc;->f", "Lc;->f:V", "Lc;->:I", "c->f:I", "[I->f:I", "Lc;->f:I ", " Lc;->f:I", "Lc;->f:Lx", "La//b;->f:I"] {
355 assert_eq!(FieldRef::parse(s), Err(MappingError::Field(s.into())), "{s}");
356 }
357 for s in ["Lc;->run", "Lc;->run()", "Lc;->run(V)V", "Lc;->(I)V", "Lc;->run(I)VV", "Lc;->a.b()V", "com.a.B->run()V"] {
358 assert_eq!(MethodRef::parse(s), Err(MappingError::Method(s.into())), "{s}");
359 }
360 }
361
362 #[test]
363 fn overloads_are_distinct_entries() {
364 let mut m = NameMappings::new();
365 m.insert_method("Lc;->a(I)V", "setCount").unwrap();
366 m.insert_method("Lc;->a(Ljava/lang/String;)V", "setName").unwrap();
367 m.insert_method("Lc;->a()I", "count").unwrap();
368 assert_eq!(m.method("Lc;->a(I)V"), Some("setCount"));
369 assert_eq!(m.method("Lc;->a(Ljava/lang/String;)V"), Some("setName"));
370 assert_eq!(m.method("Lc;->a()I"), Some("count"));
371 assert_eq!(m.method("Lc;->a(J)V"), None);
372 m.insert_field("Lc;->a:I", "count").unwrap();
374 assert_eq!((m.field("Lc;->a:I"), m.field("Lc;->a:J")), (Some("count"), None));
375 }
376
377 #[test]
378 fn nested_classes_keep_their_descriptor() {
379 let mut m = NameMappings::new();
380 m.insert_class("Lbin/mt/json/JSONObject$Member;", "Lbin/mt/json/JSONObject$Entry;").unwrap();
381 m.insert_class("Ll/ۢۤܺ;", "Lbin/mt/json/LinkedTable;").unwrap();
382 assert_eq!(m.class("Lbin/mt/json/JSONObject$Member;"), Some("Lbin/mt/json/JSONObject$Entry;"));
383 assert_eq!(m.class("Lbin/mt/json/JSONObject;"), None);
384 assert_eq!(m.class("Ll/ۢۤܺ;"), Some("Lbin/mt/json/LinkedTable;"));
386 }
387
388 #[test]
389 fn bad_keys_and_names_are_refused() {
390 let mut m = NameMappings::new();
391 assert_eq!(m.insert_class("com.a.B", "LX;"), Err(MappingError::Class("com.a.B".into())));
392 assert_eq!(m.insert_class("[La;", "LX;"), Err(MappingError::Class("[La;".into())));
393 for bad in ["X", "com.a.X", "Lcom/a/X", "Lcom//X;", "L;", "Lcom/a b;", "[LX;", "I"] {
394 assert_eq!(m.insert_class("La;", bad), Err(MappingError::NewClass(bad.into())), "{bad:?}");
395 }
396 assert_eq!(m.insert_method("Lc;-><init>()V", "make"), Err(MappingError::Method("Lc;-><init>()V".into())));
397 for bad in ["", "a b", "a.b", "La;", "x\n"] {
398 assert_eq!(m.insert_field("Lc;->f:I", bad), Err(MappingError::NewName(bad.into())), "{bad:?}");
399 }
400 assert!(m.is_empty());
401 assert_eq!(m.insert_class("La;", "Lp/X;"), Ok(None));
402 assert_eq!(m.insert_class("La;", "Lq/Y$Z;"), Ok(Some("Lp/X;".into())));
403 }
404
405 fn sample(order: &[usize]) -> NameMappings {
406 let entries: [(&str, &str); 6] = [
407 ("c:Lb;", "Lcom/x/Bee;"),
408 ("c:La$1;", "Lcom/x/Aye$1;"),
409 ("f:Lb;->x:I", "size"),
410 ("f:La;->x:I", "count"),
411 ("m:La;->x(I)V", "put"),
412 ("m:La;->x()V", "clear"),
413 ];
414 let mut m = NameMappings::new();
415 for &i in order {
416 let (key, new) = entries[i];
417 match key.split_at(2) {
418 ("c:", d) => m.insert_class(d, new),
419 ("f:", s) => m.insert_field(s, new),
420 (_, s) => m.insert_method(s, new),
421 }
422 .unwrap();
423 }
424 m
425 }
426
427 #[test]
428 fn order_and_hash_do_not_depend_on_insertion() {
429 let (a, b) = (sample(&[0, 1, 2, 3, 4, 5]), sample(&[5, 3, 1, 4, 0, 2]));
430 assert_eq!(a, b);
431 assert_eq!(a.hash(), b.hash());
432 assert_eq!(a.classes().keys().collect::<Vec<_>>(), ["La$1;", "Lb;"]);
433 assert_eq!(a.fields().keys().collect::<Vec<_>>(), ["La;->x:I", "Lb;->x:I"]);
434 assert_eq!(a.methods().keys().collect::<Vec<_>>(), ["La;->x()V", "La;->x(I)V"]);
435 }
436
437 #[test]
438 fn hash_is_pinned_and_tells_contents_apart() {
439 assert_eq!(NameMappings::new().hash().to_string(), "eb2a94d4e5862de88b4a5b6162d60ce874a1b831bf01c074921ec976becdad4e");
442 assert_eq!(sample(&[0, 1, 2, 3, 4, 5]).hash().to_string(), "6eb831a95f17919d15479902c0e6f8d04471980d53733d9be3650064ef0cea5c");
443 let mut class = NameMappings::new();
445 class.insert_class("La;", "Lab;").unwrap();
446 let mut other = NameMappings::new();
447 other.insert_class("La;", "La;").unwrap();
448 assert_ne!(class.hash(), other.hash());
449 let mut field = NameMappings::new();
450 field.insert_field("La;->f:I", "x").unwrap();
451 let mut method = NameMappings::new();
452 method.insert_method("La;->f()I", "x").unwrap();
453 assert_ne!(field.hash(), method.hash());
454 assert_ne!(field.hash(), NameMappings::new().hash());
455 }
456
457 #[test]
458 fn restriction_keeps_the_classes_and_their_members() {
459 let m = sample(&[0, 1, 2, 3, 4, 5]);
460 let only_a = m.restricted_to(|c| c.starts_with("La"));
461 assert_eq!(only_a.classes().keys().collect::<Vec<_>>(), ["La$1;"]);
462 assert_eq!(only_a.fields().keys().collect::<Vec<_>>(), ["La;->x:I"]);
463 assert_eq!(only_a.methods().len(), 2);
464 assert!(m.restricted_to(|_| false).is_empty());
465 assert_eq!(m.restricted_to(|_| true), m);
466 }
467
468 #[test]
469 fn engine_mappings_are_the_identity_unless_supplied() {
470 let engine = Engine::new("unused.apk");
471 assert!(engine.name_mappings().is_empty());
472 assert_eq!(engine.name_mappings().class("La;"), None);
473 let supplied = Arc::new(sample(&[0, 1, 2, 3, 4, 5]));
474 let engine = Engine::new("unused.apk").with_name_mappings(Arc::clone(&supplied));
475 assert_eq!(engine.name_mappings(), &*supplied);
476 assert_eq!(engine.name_mappings().class("Lb;"), Some("Lcom/x/Bee;"));
477 }
478}