1pub mod cfg;
9pub mod classdec;
10
11pub use classdec::ClassOptions;
12pub mod ctx;
13pub mod lift;
14pub mod method;
15pub mod passes;
16pub mod platform;
17
18use jdc_core::FxHashMap as HashMap;
19
20use ddc_dex::annotations::{self, EncodedValue};
21
22#[derive(Debug, Clone)]
24pub enum StaticValue {
25 Int(i64),
26 Float(f32),
27 Double(f64),
28 Str(String),
29 Type(String),
30 Boolean(bool),
31 Null,
32 Field(String, String),
34 Other,
35}
36use ddc_dex::{ClassDef, DexFile};
37use jdc_core::types::{
38 parse_field_descriptor, parse_method_descriptor, JavaType, MethodDescriptor,
39};
40
41pub mod access {
45 pub const ACC_PUBLIC: u32 = 0x1;
46 pub const ACC_PRIVATE: u32 = 0x2;
47 pub const ACC_PROTECTED: u32 = 0x4;
48 pub const ACC_STATIC: u32 = 0x8;
49 pub const ACC_FINAL: u32 = 0x10;
50 pub const ACC_SYNCHRONIZED: u32 = 0x20;
51 pub const ACC_BRIDGE: u32 = 0x40;
52 pub const ACC_VARARGS: u32 = 0x80;
53 pub const ACC_NATIVE: u32 = 0x100;
54 pub const ACC_INTERFACE: u32 = 0x200;
55 pub const ACC_ABSTRACT: u32 = 0x400;
56 pub const ACC_STRICT: u32 = 0x800;
57 pub const ACC_SYNTHETIC: u32 = 0x1000;
58 pub const ACC_ANNOTATION: u32 = 0x2000;
59 pub const ACC_ENUM: u32 = 0x4000;
60 pub const ACC_CONSTRUCTOR: u32 = 0x1_0000;
61 pub const ACC_DECLARED_SYNCHRONIZED: u32 = 0x2_0000;
62}
63
64#[derive(Debug, Clone)]
66pub struct PoolField {
67 pub name: String,
68 pub desc: String,
70 pub access: u32,
71 pub is_static: bool,
72}
73
74#[derive(Debug, Clone)]
76pub struct PoolMethod {
77 pub name: std::sync::Arc<str>,
79 pub desc: std::sync::Arc<str>,
83 pub access: u32,
84 pub code_off: u32,
85 pub debug_info_off: u32,
86 pub dex_idx: usize,
88}
89
90impl PoolMethod {
91 pub fn parsed_desc(&self) -> Option<MethodDescriptor> {
92 parse_method_descriptor(&self.desc)
93 }
94 pub fn is_static(&self) -> bool {
95 self.access & access::ACC_STATIC != 0
96 }
97 pub fn is_abstract_or_native(&self) -> bool {
98 self.access & (access::ACC_ABSTRACT | access::ACC_NATIVE) != 0
99 }
100}
101
102enum ClassEntry {
105 Eager(PoolClass),
106 Lazy {
107 at: (usize, usize),
108 pc: std::sync::OnceLock<PoolClass>,
109 },
110}
111
112#[derive(Debug, Clone)]
115pub struct PoolClass {
116 pub name: String,
117 pub access: u32,
118 pub super_name: Option<String>,
120 pub interfaces: Vec<String>,
121 pub source_file: Option<String>,
122 pub static_fields: Vec<PoolField>,
123 pub instance_fields: Vec<PoolField>,
124 pub direct_methods: Vec<PoolMethod>,
125 pub virtual_methods: Vec<PoolMethod>,
126 pub static_values: Vec<StaticValue>,
128 pub nesting: ResolvedNesting,
130 pub dex_idx: usize,
132}
133
134#[derive(Debug, Clone, Default)]
136pub struct ResolvedNesting {
137 pub enclosing_class: Option<String>,
138 pub enclosing_method: Option<(String, String)>,
140 pub member_classes: Vec<String>,
141}
142
143impl PoolClass {
144 pub fn is_interface(&self) -> bool {
145 self.access & access::ACC_INTERFACE != 0
146 }
147
148 pub fn is_enum(&self) -> bool {
149 self.access & access::ACC_ENUM != 0
150 }
151
152 pub fn is_synthetic(&self) -> bool {
153 self.access & access::ACC_SYNTHETIC != 0
154 }
155
156 pub fn is_static_nested(&self) -> bool {
157 self.access & access::ACC_STATIC != 0
158 }
159
160 pub fn all_methods(&self) -> impl Iterator<Item = &PoolMethod> {
162 self.direct_methods
163 .iter()
164 .chain(self.virtual_methods.iter())
165 }
166
167 pub fn find_method(&self, name: &str, desc: &str) -> Option<&PoolMethod> {
169 self.all_methods()
170 .find(|m| &*m.name == name && &*m.desc == desc)
171 }
172
173 pub fn ctors_by_arity(&self, arity: usize) -> Vec<&PoolMethod> {
175 self.all_methods()
176 .filter(|m| &*m.name == "<init>")
177 .filter(|m| {
178 m.parsed_desc()
179 .map(|d| d.args.len() == arity)
180 .unwrap_or(false)
181 })
182 .collect()
183 }
184
185 pub fn field_flags_of(&self, name: &str) -> Option<u32> {
186 self.static_fields
187 .iter()
188 .chain(self.instance_fields.iter())
189 .find(|f| f.name == name)
190 .map(|f| f.access)
191 }
192}
193
194pub struct DexPool {
196 dexes: Vec<std::sync::Arc<DexFile>>,
202 pub dex_labels: Vec<String>,
205 classes: HashMap<String, ClassEntry>,
210 pub order: Vec<String>,
212 retire_counts: std::sync::Mutex<Vec<u64>>,
214 retire_armed: std::sync::atomic::AtomicBool,
215 outers: std::sync::OnceLock<HashMap<String, Option<String>>>,
217 dollar_ix: std::sync::OnceLock<Vec<String>>,
218 children: std::sync::OnceLock<HashMap<String, Vec<String>>>,
220 ref_caches: Vec<DexRefCache>,
222}
223
224pub(crate) struct DexRefCache {
234 proto_descs: Box<[std::sync::OnceLock<std::sync::Arc<jdc_core::types::MethodDescriptor>>]>,
235 type_names: Box<[std::sync::OnceLock<std::sync::Arc<str>>]>,
236 type_javas: Box<[std::sync::OnceLock<std::sync::Arc<jdc_core::types::JavaType>>]>,
237}
238
239impl DexRefCache {
240 fn new(protos: usize, types: usize) -> Self {
241 DexRefCache {
242 proto_descs: (0..protos).map(|_| std::sync::OnceLock::new()).collect(),
243 type_names: (0..types).map(|_| std::sync::OnceLock::new()).collect(),
244 type_javas: (0..types).map(|_| std::sync::OnceLock::new()).collect(),
245 }
246 }
247}
248
249impl DexPool {
250 pub fn new() -> Self {
251 DexPool {
252 dexes: Vec::new(),
253 retire_armed: std::sync::atomic::AtomicBool::new(false),
254 dex_labels: Vec::new(),
255 classes: HashMap::default(),
256 order: Vec::new(),
257 retire_counts: std::sync::Mutex::new(Vec::new()),
258 outers: std::sync::OnceLock::new(),
259 dollar_ix: std::sync::OnceLock::new(),
260 children: std::sync::OnceLock::new(),
261 ref_caches: Vec::new(),
262 }
263 }
264
265 pub fn proto_desc_parsed(
267 &self,
268 di: usize,
269 proto_idx: u32,
270 ) -> std::sync::Arc<jdc_core::types::MethodDescriptor> {
271 let Some(dex) = self.dexes.get(di) else {
272 return std::sync::Arc::new(jdc_core::types::MethodDescriptor {
273 args: Vec::new(),
274 ret: jdc_core::types::JavaType::Void,
275 });
276 };
277 let build = || {
278 std::sync::Arc::new(parse_proto_desc(dex, proto_idx))
279 };
280 match self
281 .ref_caches
282 .get(di)
283 .and_then(|c| c.proto_descs.get(proto_idx as usize))
284 {
285 Some(slot) => slot.get_or_init(build).clone(),
286 None => build(),
287 }
288 }
289
290 pub fn type_name_arc(&self, di: usize, type_idx: u32) -> std::sync::Arc<str> {
292 let Some(dex) = self.dexes.get(di) else {
293 return std::sync::Arc::from("");
294 };
295 let build = || std::sync::Arc::from(dex.class_name(type_idx).as_str());
296 match self
297 .ref_caches
298 .get(di)
299 .and_then(|c| c.type_names.get(type_idx as usize))
300 {
301 Some(slot) => slot.get_or_init(build).clone(),
302 None => build(),
303 }
304 }
305
306 pub fn type_java(&self, di: usize, type_idx: u32) -> std::sync::Arc<jdc_core::types::JavaType> {
308 let Some(dex) = self.dexes.get(di) else {
309 return std::sync::Arc::new(jdc_core::types::JavaType::Object(
310 std::sync::Arc::from("java/lang/Object"),
311 ));
312 };
313 let build = || std::sync::Arc::new(desc_type(dex.type_name(type_idx)));
314 match self
315 .ref_caches
316 .get(di)
317 .and_then(|c| c.type_javas.get(type_idx as usize))
318 {
319 Some(slot) => slot.get_or_init(build).clone(),
320 None => build(),
321 }
322 }
323
324 fn outer_map(&self) -> &HashMap<String, Option<String>> {
326 self.outers.get_or_init(|| {
327 self.order
328 .iter()
329 .map(|n| (n.clone(), find_outer_name(self, n)))
330 .collect()
331 })
332 }
333
334 pub fn has_dollar_prefix(&self, prefix: &str) -> bool {
343 if self.dollar_ix.get().is_none() {
344 let mut sorted: Vec<String> = self.order.clone();
345 sorted.sort_unstable();
346 let _ = self.dollar_ix.set(sorted);
347 }
348 self.dollar_ix.get().is_some_and(|sorted| {
349 let needle = format!("{prefix}$");
350 match sorted.binary_search_by(|n| n.as_str().cmp(needle.as_str())) {
351 Ok(_) => true,
352 Err(pos) => pos < sorted.len() && sorted[pos].starts_with(&needle),
353 }
354 })
355 }
356
357 pub fn outer_of(&self, name: &str) -> Option<&str> {
358 self.outer_map().get(name).and_then(|o| o.as_deref())
359 }
360
361 pub fn children_of(&self, internal: &str) -> &[String] {
365 if self.children.get().is_none() {
366 let mut idx: HashMap<String, Vec<String>> = HashMap::default();
374 for name in &self.order {
375 if let Some(outer) = self.outer_of(name).map(str::to_string) {
376 idx.entry(outer).or_default().push(name.clone());
377 }
378 }
379 let _ = self.children.set(idx);
382 }
383 self.children
384 .get()
385 .and_then(|m| m.get(internal))
386 .map(|v| v.as_slice())
387 .unwrap_or(&[])
388 }
389
390 pub fn add_dex(&mut self, dex: DexFile) -> usize {
394 let dex_idx = self.dexes.len();
395 self.classes.reserve(dex.class_defs.len());
398 for cd in &dex.class_defs {
401 let name = dex.class_name(cd.class_idx);
402 if self.classes.contains_key(&name) {
403 continue;
404 }
405 let pc = pool_class_of(&dex, dex.raw(), cd, dex_idx);
406 self.classes.insert(name.clone(), ClassEntry::Eager(pc));
407 self.order.push(name);
408 }
409 self.ref_caches
410 .push(DexRefCache::new(dex.proto_count(), dex.type_count()));
411 self.dexes.push(std::sync::Arc::new(dex));
412 self.retire_counts.lock().unwrap().push(0);
413 self.dex_labels.push(format!("dex {}", dex_idx));
414 dex_idx
415 }
416
417 pub fn add_dex_lazy(&mut self, dex: DexFile) -> usize {
421 let dex_idx = self.dexes.len();
422 self.classes.reserve(dex.class_defs.len());
423 for (ci, cd) in dex.class_defs.iter().enumerate() {
424 let name = dex.class_name(cd.class_idx);
425 if self.classes.contains_key(&name) {
426 continue;
427 }
428 self.classes.insert(
429 name.clone(),
430 ClassEntry::Lazy {
431 at: (dex_idx, ci),
432 pc: std::sync::OnceLock::new(),
433 },
434 );
435 self.order.push(name);
436 }
437 self.ref_caches
438 .push(DexRefCache::new(dex.proto_count(), dex.type_count()));
439 self.dexes.push(std::sync::Arc::new(dex));
440 self.retire_counts.lock().unwrap().push(0);
441 self.dex_labels.push(format!("dex {}", dex_idx));
442 dex_idx
443 }
444
445 pub fn set_dex_label(&mut self, idx: usize, label: impl Into<String>) {
447 if let Some(slot) = self.dex_labels.get_mut(idx) {
448 *slot = label.into();
449 }
450 }
451
452 pub fn len(&self) -> usize {
453 self.classes.len()
454 }
455
456 pub fn is_empty(&self) -> bool {
457 self.classes.is_empty()
458 }
459
460 pub fn get(&self, internal: &str) -> Option<&PoolClass> {
461 self.get_inner(internal, None)
462 }
463
464 fn get_inner(&self, internal: &str, raws: Option<&[&[u8]]>) -> Option<&PoolClass> {
469 match self.classes.get(internal)? {
470 ClassEntry::Eager(pc) => Some(pc),
471 ClassEntry::Lazy { at, pc } => {
472 if let Some(built) = pc.get() {
473 return Some(built);
474 }
475 let (di, ci) = *at;
476 let dex = self.dexes.get(di)?;
477 let cd = dex.class_defs.get(ci)?;
478 let raw = match raws {
479 Some(r) => r.get(di).copied().unwrap_or(&[]),
480 None => dex.raw(),
481 };
482 let built = pool_class_of(dex, raw, cd, di);
483 Some(pc.get_or_init(move || built))
484 }
485 }
486 }
487
488 pub fn materialize_all(&self, threads: usize) {
494 let keys: Vec<String> = self
495 .classes
496 .iter()
497 .filter(|(_, e)| matches!(e, ClassEntry::Lazy { .. }))
498 .map(|(k, _)| k.clone())
499 .collect();
500 if keys.is_empty() {
501 return;
502 }
503 let raws: Vec<&[u8]> = self.dexes.iter().map(|d| d.raw()).collect();
508 let threads = threads.max(1);
509 let next = std::sync::atomic::AtomicUsize::new(0);
510 std::thread::scope(|scope| {
511 for _ in 0..threads {
512 scope.spawn(|| loop {
513 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
514 let Some(k) = keys.get(i) else { break };
515 let _ = self.get_inner(k, Some(&raws));
516 });
517 }
518 });
519 }
520
521 fn get_if_materialized(&self, internal: &str) -> Option<&PoolClass> {
523 match self.classes.get(internal)? {
524 ClassEntry::Eager(pc) => Some(pc),
525 ClassEntry::Lazy { pc, .. } => pc.get(),
526 }
527 }
528
529 pub fn has_name(&self, internal: &str) -> bool {
531 self.classes.contains_key(internal)
532 }
533
534 pub fn dex(&self, idx: usize) -> Option<std::sync::Arc<DexFile>> {
537 self.dexes.get(idx).cloned()
538 }
539
540 pub fn dex_count(&self) -> usize {
541 self.dexes.len()
542 }
543
544 pub fn class_names(&self) -> impl Iterator<Item = &str> {
545 self.order.iter().map(|s| s.as_str())
546 }
547
548 pub fn is_subtype(&self, sub: &str, sup: &str) -> bool {
551 if sub == sup {
552 return true;
553 }
554 if let (Some(sub_elem), Some(sup_elem)) = (array_elem(sub), array_elem(sup)) {
556 if sub_elem.starts_with('L')
557 && sup_elem.starts_with('L')
558 && sub_elem.ends_with(';')
559 && sup_elem.ends_with(';')
560 {
561 return self.is_subtype(
562 &sub_elem[1..sub_elem.len() - 1],
563 &sup_elem[1..sup_elem.len() - 1],
564 );
565 }
566 return sub_elem == sup_elem;
567 }
568 let mut cur = self.get(sub);
569 let mut hops = 0;
570 while let Some(c) = cur {
571 hops += 1;
572 if hops > 64 {
573 return false;
574 }
575 for i in &c.interfaces {
576 if i == sup || self.is_subtype(i, sup) {
577 return true;
578 }
579 }
580 match &c.super_name {
581 Some(s) if s == sup => return true,
582 Some(s) if s != "java/lang/Object" => {
583 cur = self.get(s);
584 }
585 _ => return false,
586 }
587 }
588 false
589 }
590}
591
592impl Default for DexPool {
593 fn default() -> Self {
594 Self::new()
595 }
596}
597
598fn array_elem(desc: &str) -> Option<&str> {
599 desc.strip_prefix('[')
600}
601
602fn resolve_static_value(v: &EncodedValue, dex: &DexFile) -> StaticValue {
603 match v {
604 EncodedValue::Byte(x) => StaticValue::Int(*x as i64),
605 EncodedValue::Short(x) => StaticValue::Int(*x as i64),
606 EncodedValue::Char(x) => StaticValue::Int(*x as i64),
607 EncodedValue::Int(x) => StaticValue::Int(*x as i64),
608 EncodedValue::Long(x) => StaticValue::Int(*x),
609 EncodedValue::Float(x) => StaticValue::Float(*x),
610 EncodedValue::Double(x) => StaticValue::Double(*x),
611 EncodedValue::String(i) => StaticValue::Str(dex.string(*i).to_string()),
612 EncodedValue::Type(i) => StaticValue::Type(dex.class_name(*i)),
613 EncodedValue::Boolean(b) => StaticValue::Boolean(*b),
614 EncodedValue::Null => StaticValue::Null,
615 EncodedValue::Enum(i) | EncodedValue::Field(i) => {
616 let f = dex.field(*i);
617 StaticValue::Field(
618 dex.class_name(f.class_idx),
619 dex.string(f.name_idx).to_string(),
620 )
621 }
622 _ => StaticValue::Other,
623 }
624}
625
626fn pool_class_of(dex: &DexFile, raw: &[u8], cd: &ClassDef, dex_idx: usize) -> PoolClass {
627 let name = dex.class_name(cd.class_idx);
628 let super_name = if cd.superclass_idx == ddc_dex::NO_INDEX {
629 None
630 } else {
631 let s = dex.class_name(cd.superclass_idx);
632 if s.is_empty() || s == "java/lang/Object" {
633 None
634 } else {
635 Some(s)
636 }
637 };
638 let interfaces = dex
639 .interfaces_of(cd)
640 .into_iter()
641 .map(|t| dex.class_name(t))
642 .collect();
643 let source_file = if cd.source_file_idx == ddc_dex::NO_INDEX {
644 None
645 } else {
646 Some(dex.string(cd.source_file_idx).to_string())
647 };
648
649 let data = dex.class_data(cd);
650 let mk_field = |ef: &ddc_dex::EncodedField, is_static: bool| {
651 let f = dex.field(ef.field_idx);
652 PoolField {
653 name: dex.string(f.name_idx).to_string(),
654 desc: dex.type_name(f.type_idx).to_string(),
655 access: ef.access_flags,
656 is_static,
657 }
658 };
659 let static_fields: Vec<PoolField> = data
660 .static_fields
661 .iter()
662 .map(|f| mk_field(f, true))
663 .collect();
664 let instance_fields: Vec<PoolField> = data
665 .instance_fields
666 .iter()
667 .map(|f| mk_field(f, false))
668 .collect();
669 let mk_method = |em: &ddc_dex::EncodedMethod| {
670 let m = dex.method(em.method_idx);
671 let (code_off, debug_info_off) = match dex.debug_info_off_at(em.code_off) {
672 Some(d) => (em.code_off, d),
673 None => (0, 0),
674 };
675 PoolMethod {
676 name: dex.string_arc(m.name_idx),
677 desc: dex.proto_desc(m.proto_idx),
678 access: em.access_flags,
679 code_off,
680 debug_info_off,
681 dex_idx,
682 }
683 };
684 let direct_methods: Vec<PoolMethod> = data.direct_methods.iter().map(&mk_method).collect();
685 let virtual_methods: Vec<PoolMethod> = data.virtual_methods.iter().map(&mk_method).collect();
686
687 let static_values: Vec<StaticValue> = dex
688 .static_values(cd.static_values_off)
689 .into_iter()
690 .map(|v| resolve_static_value(&v, dex))
691 .collect();
692 let anns = if cd.annotations_off != 0 {
693 annotations::read_class_annotations(raw, cd.annotations_off)
694 } else {
695 Vec::new()
696 };
697 let raw = annotations::nesting_from(&anns, &|t: u32| dex.type_name(t).to_string());
698 let nesting = ResolvedNesting {
699 enclosing_class: raw.enclosing_class.map(|t| dex.class_name(t)),
700 enclosing_method: raw.enclosing_method.map(|m| {
701 let mid = dex.method(m);
702 (
703 dex.class_name(mid.class_idx),
704 dex.string(mid.name_idx).to_string(),
705 )
706 }),
707 member_classes: raw
708 .member_classes
709 .into_iter()
710 .map(|t| dex.class_name(t))
711 .collect(),
712 };
713
714 PoolClass {
715 name,
716 access: cd.access_flags,
717 super_name,
718 interfaces,
719 source_file,
720 static_fields,
721 instance_fields,
722 direct_methods,
723 virtual_methods,
724 static_values,
725 nesting,
726 dex_idx,
727 }
728}
729
730fn parse_proto_desc(
734 dex: &ddc_dex::DexFile,
735 proto_idx: u32,
736) -> jdc_core::types::MethodDescriptor {
737 let proto = dex.proto(proto_idx);
738 let args = dex
739 .proto_params(proto_idx)
740 .iter()
741 .map(|&t| desc_type(dex.type_name(t)))
742 .collect();
743 jdc_core::types::MethodDescriptor {
744 args,
745 ret: desc_type(dex.type_name(proto.return_type_idx)),
746 }
747}
748
749pub fn desc_type(desc: &str) -> JavaType {
750 parse_field_descriptor(desc).unwrap_or(JavaType::Object("java/lang/Object".into()))
751}
752
753pub fn find_outer_name(pool: &DexPool, internal: &str) -> Option<String> {
756 if let Some(pc) = pool.get_if_materialized(internal) {
761 if let Some(enc) = &pc.nesting.enclosing_class {
762 return Some(enc.clone());
763 }
764 }
765 let mut rest = internal;
766 while let Some(d) = rest.rfind('$') {
767 let cand = &rest[..d];
768 if pool.has_name(cand) {
769 return Some(cand.to_string());
770 }
771 rest = cand;
772 }
773 None
774}
775
776fn clean_member_tail(rest: &str) -> bool {
779 if rest.starts_with('-') {
780 return false;
781 }
782 let tail = rest.rsplit('$').next().unwrap_or(rest);
783 if tail.is_empty() || tail.starts_with(|c: char| c.is_ascii_digit()) {
784 return false;
785 }
786 true
787}
788
789impl DexPool {
792 pub fn arm_retirement(&self) {
795 self.retire_armed
796 .store(true, std::sync::atomic::Ordering::Release);
797 let mut counts = self.retire_counts.lock().unwrap();
798 for c in counts.iter_mut() {
799 *c = 0;
800 }
801 for entry in self.classes.values() {
802 let di = match entry {
803 ClassEntry::Eager(pc) => pc.dex_idx,
804 ClassEntry::Lazy { at, .. } => at.0,
805 };
806 if let Some(c) = counts.get_mut(di) {
807 *c += 1;
808 }
809 }
810 }
811
812 pub fn report_class_done(&self, class_name: &str) -> Option<usize> {
815 if !self.retire_armed.load(std::sync::atomic::Ordering::Acquire) {
816 return None;
817 }
818 let entry = self.classes.get(class_name)?;
819 let di = match entry {
820 ClassEntry::Eager(pc) => pc.dex_idx,
821 ClassEntry::Lazy { at, .. } => at.0,
822 };
823 let mut counts = self.retire_counts.lock().unwrap();
824 match counts.get_mut(di) {
825 Some(c) => {
826 *c = c.saturating_sub(1);
827 if *c == 0 {
828 Some(di)
829 } else {
830 None
831 }
832 }
833 None => None,
834 }
835 }
836
837 pub fn release_images(&self, indexes: &[usize]) {
841 let mut counts = self.retire_counts.lock().unwrap();
842 for &i in indexes {
843 if let Some(dex) = self.dexes.get(i) {
844 dex.mark_released();
848 }
849 if let Some(c) = counts.get_mut(i) {
850 *c = u64::MAX; }
852 }
853 }
854}
855
856pub fn top_level_classes(pool: &DexPool) -> Vec<String> {
857 pool.class_names()
858 .filter(|name| match pool.outer_of(name) {
859 None => true,
860 Some(outer) => {
861 if pool.get(outer).is_none() {
862 return true;
863 }
864 let rest = name
871 .strip_prefix(outer)
872 .and_then(|t| t.strip_prefix('$'))
873 .unwrap_or("");
874 !clean_member_tail(rest)
875 }
876 })
877 .map(|n| n.to_string())
878 .collect()
879}
880
881pub fn case_rename_map(pool: &DexPool) -> HashMap<String, String> {
888 use std::collections::HashMap;
889 let mut groups: HashMap<String, Vec<String>> = HashMap::default();
890 for t in top_level_classes(pool) {
891 groups.entry(t.to_lowercase()).or_default().push(t);
892 }
893 let folds: std::collections::HashSet<String> = groups.keys().cloned().collect();
894 let mut map = HashMap::default();
895 for (_, mut members) in groups {
896 members.sort();
897 for (i, m) in members.iter().enumerate() {
898 if i == 0 {
899 map.insert(m.clone(), m.clone());
900 continue;
901 }
902 let cut = m.rfind('/').map(|x| x + 1).unwrap_or(0);
905 let mut n = i + 1;
906 loop {
907 let cand = format!("{}{}_{}", &m[..cut], &m[cut..], n);
908 let fold = cand.to_lowercase();
909 if !folds.contains(&fold) {
910 map.insert(m.clone(), cand);
911 break;
912 }
913 n += 1;
914 }
915 }
916 }
917 map
918}
919
920fn nested_collision_renames(pool: &DexPool, map: &mut HashMap<String, String>) {
957 let mut names: Vec<&String> = pool.order.iter().collect();
960 names.sort_by_key(|n| n.matches('$').count());
961 let mut assigned: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
962 for name in names {
963 if !name.contains('$') || map.contains_key(name) {
964 continue;
965 }
966 let Some(parent) = find_outer_name(pool, name) else {
967 continue;
968 };
969 let Some(rest) = name
972 .strip_prefix(parent.as_str())
973 .and_then(|t| t.strip_prefix('$'))
974 else {
975 continue;
976 };
977 if !clean_member_tail(rest) {
978 continue;
979 }
980 let mut chain: Vec<String> = Vec::new();
982 let mut cur = parent.clone();
983 loop {
984 let disp = map.get(&cur).cloned().unwrap_or_else(|| cur.clone());
985 let simple = disp.rsplit('/').next().unwrap_or(&disp);
986 chain.push(simple.rsplit('$').next().unwrap_or(simple).to_string());
987 match find_outer_name(pool, &cur) {
988 Some(o) if o != cur => cur = o,
989 _ => break,
990 }
991 }
992 let disp_parent = map
993 .get(&parent)
994 .cloned()
995 .unwrap_or_else(|| parent.clone());
996 let tail = rest.rsplit('$').next().unwrap_or(rest);
997 let orphan = rest.contains('$');
998 let clash = chain.iter().any(|c| c == tail);
999 if !orphan && !clash {
1000 continue;
1001 }
1002 let mut k = 0u32;
1003 loop {
1004 k += 1;
1005 let cand_tail = if k == 1 {
1006 tail.to_string()
1007 } else {
1008 format!("{tail}{k}")
1009 };
1010 if chain.contains(&cand_tail) {
1014 continue;
1015 }
1016 let cand = format!("{disp_parent}${cand_tail}");
1017 if pool.has_name(&cand) || assigned.contains(&cand) {
1018 continue;
1019 }
1020 assigned.insert(cand.clone());
1021 map.insert(name.clone(), cand);
1022 break;
1023 }
1024 }
1025}
1026
1027pub fn install_case_renames(pool: &DexPool) {
1029 let mut map = case_rename_map(pool);
1030 pkg_leaf_shadow_renames(pool, &mut map);
1035 nested_collision_renames(pool, &mut map);
1036 jdc_core::rename::set_class_renames(map);
1037 jdc_core::rename::set_field_renames(member_collision_renames(pool));
1038}
1039
1040fn pkg_leaf_shadow_renames(pool: &DexPool, map: &mut HashMap<String, String>) {
1048 let taken: jdc_core::FxHashSet<String> = pool.order.iter().cloned().collect();
1049 for name in &pool.order {
1050 let Some((pkg, simple)) = name.rsplit_once('/') else {
1051 continue;
1052 };
1053 if simple.is_empty() || pkg.is_empty() {
1054 continue;
1055 }
1056 let leaf = pkg.rsplit('/').next().unwrap_or(pkg);
1057 if simple != leaf {
1060 continue;
1061 }
1062 let prefix = format!("{pkg}/");
1063 let siblings = pool
1064 .order
1065 .iter()
1066 .filter(|n| n.starts_with(&prefix) && **n != *name)
1067 .count();
1068 if siblings == 0 || map.get(name).is_some_and(|v| v != name) {
1069 continue; }
1071 let mut k = 1u32;
1072 loop {
1073 k += 1;
1074 let cand = format!("{pkg}/{simple}{k}");
1075 if !taken.contains(&cand) && !map.values().any(|v| *v == cand) {
1076 map.insert(name.clone(), cand);
1077 break;
1078 }
1079 }
1080 }
1081}
1082
1083fn member_collision_renames(
1096 pool: &DexPool,
1097) -> HashMap<std::sync::Arc<str>, Vec<jdc_core::rename::FieldRename>> {
1098 let mut out: HashMap<std::sync::Arc<str>, Vec<jdc_core::rename::FieldRename>> =
1099 HashMap::default();
1100 for name in &pool.order {
1101 let Some(pc) = pool.get_if_materialized(name) else {
1102 continue;
1103 };
1104 let outer_ref_desc = find_outer_name(pool, name)
1105 .filter(|o| o != name)
1106 .map(|o| format!("L{o};"));
1107 let fields: Vec<&PoolField> = pc
1109 .static_fields
1110 .iter()
1111 .chain(pc.instance_fields.iter())
1112 .collect();
1113 let fgroups: HashMap<String, Vec<&PoolField>> = HashMap::default();
1114 let mut fgroups = fgroups;
1115 for f in &fields {
1116 let disp = crate::classdec::java_ident(&f.name).into_owned();
1117 fgroups.entry(disp).or_default().push(f);
1118 }
1119 let f_taken: jdc_core::FxHashSet<String> = fields
1120 .iter()
1121 .map(|f| crate::classdec::java_ident(&f.name).into_owned())
1122 .collect();
1123 let mut f_taken = f_taken;
1124 for group in fgroups.values() {
1125 if group.len() < 2 {
1126 continue;
1127 }
1128 let keeper_pos = group
1129 .iter()
1130 .position(|f| f.access & crate::access::ACC_SYNTHETIC == 0)
1131 .unwrap_or(0);
1132 for (gi, f) in group.iter().enumerate() {
1133 if gi == keeper_pos {
1134 continue;
1135 }
1136 let base = crate::classdec::java_ident(&f.name).into_owned();
1137 let display: String = match &outer_ref_desc {
1138 Some(ord)
1139 if f.access & crate::access::ACC_SYNTHETIC != 0
1140 && !f.is_static
1141 && *ord == f.desc =>
1142 {
1143 let mut k = 0u32;
1144 loop {
1145 let cand = if k == 0 {
1146 "this$0".to_string()
1147 } else {
1148 format!("this$0{k}")
1149 };
1150 if f_taken.insert(cand.clone()) {
1151 break cand;
1152 }
1153 k += 1;
1154 }
1155 }
1156 _ => suffix_unique(&base, &mut f_taken),
1157 };
1158 out.entry(std::sync::Arc::from(name.as_str()))
1159 .or_default()
1160 .push(jdc_core::rename::FieldRename {
1161 name: std::sync::Arc::from(f.name.as_str()),
1162 desc: std::sync::Arc::from(f.desc.as_str()),
1163 display: std::sync::Arc::from(display.as_str()),
1164 });
1165 }
1166 }
1167 let methods: Vec<&PoolMethod> = pc.all_methods().collect();
1169 let mut m_taken: jdc_core::FxHashSet<String> = methods
1170 .iter()
1171 .map(|m| crate::classdec::java_ident(&m.name).into_owned())
1172 .collect();
1173 let mut mgroups: HashMap<(String, String), Vec<&PoolMethod>> = HashMap::default();
1174 for m in &methods {
1175 let d: &str = &m.desc;
1176 let lo = d.find('(').map(|i| i + 1).unwrap_or(0);
1177 let hi = d.find(')').unwrap_or(d.len());
1178 let key = (
1179 crate::classdec::java_ident(&m.name).into_owned(),
1180 d[lo..hi].to_string(),
1181 );
1182 mgroups.entry(key).or_default().push(m);
1183 }
1184 for ((base, _), group) in &mgroups {
1185 if group.len() < 2 {
1186 continue;
1187 }
1188 let covariant = group
1196 .iter()
1197 .any(|m| group.iter().any(|o| o.name == m.name && o.desc != m.desc));
1198 if covariant {
1199 continue;
1200 }
1201 let mut base = base.clone();
1206 let mut seen_orig: jdc_core::FxHashSet<(&str, &str)> =
1207 jdc_core::FxHashSet::default();
1208 for m in group {
1209 if !seen_orig.insert((&m.name, &m.desc)) {
1210 continue; }
1212 let display = suffix_unique(&base, &mut m_taken);
1213 out.entry(std::sync::Arc::from(name.as_str()))
1214 .or_default()
1215 .push(jdc_core::rename::FieldRename {
1216 name: m.name.clone(),
1217 desc: m.desc.clone(),
1218 display: std::sync::Arc::from(display.as_str()),
1219 });
1220 let _ = &mut base;
1223 }
1224 }
1225 }
1226 out
1227}
1228
1229fn suffix_unique(base: &str, taken: &mut jdc_core::FxHashSet<String>) -> String {
1230 let mut k = 1u32;
1231 loop {
1232 k += 1;
1233 let cand = format!("{base}{k}");
1234 if taken.insert(cand.clone()) {
1235 return cand;
1236 }
1237 }
1238}
1239
1240pub fn install_field_renames(pool: &DexPool) {
1243 jdc_core::rename::set_field_renames(member_collision_renames(pool));
1244}
1245pub use jdc_core::rename::apply_class_rename;