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;
17mod refscan;
18
19use jdc_core::FxHashMap as HashMap;
20
21use ddc_dex::annotations::{self, EncodedValue};
22
23#[derive(Debug, Clone)]
25pub enum StaticValue {
26 Int(i64),
27 Float(f32),
28 Double(f64),
29 Str(String),
30 Type(String),
31 Boolean(bool),
32 Null,
33 Field(String, String),
35 Other,
36}
37use ddc_dex::{ClassDef, DexFile};
38use jdc_core::types::{
39 parse_field_descriptor, parse_method_descriptor, JavaType, MethodDescriptor,
40};
41
42pub mod access {
46 pub const ACC_PUBLIC: u32 = 0x1;
47 pub const ACC_PRIVATE: u32 = 0x2;
48 pub const ACC_PROTECTED: u32 = 0x4;
49 pub const ACC_STATIC: u32 = 0x8;
50 pub const ACC_FINAL: u32 = 0x10;
51 pub const ACC_SYNCHRONIZED: u32 = 0x20;
52 pub const ACC_BRIDGE: u32 = 0x40;
53 pub const ACC_VARARGS: u32 = 0x80;
54 pub const ACC_NATIVE: u32 = 0x100;
55 pub const ACC_INTERFACE: u32 = 0x200;
56 pub const ACC_ABSTRACT: u32 = 0x400;
57 pub const ACC_STRICT: u32 = 0x800;
58 pub const ACC_SYNTHETIC: u32 = 0x1000;
59 pub const ACC_ANNOTATION: u32 = 0x2000;
60 pub const ACC_ENUM: u32 = 0x4000;
61 pub const ACC_CONSTRUCTOR: u32 = 0x1_0000;
62 pub const ACC_DECLARED_SYNCHRONIZED: u32 = 0x2_0000;
63}
64
65#[derive(Debug, Clone)]
67pub struct PoolField {
68 pub name: String,
69 pub desc: String,
71 pub access: u32,
72 pub is_static: bool,
73}
74
75#[derive(Debug, Clone)]
77pub struct PoolMethod {
78 pub name: std::sync::Arc<str>,
80 pub desc: std::sync::Arc<str>,
84 pub access: u32,
85 pub code_off: u32,
86 pub debug_info_off: u32,
87 pub dex_idx: usize,
89}
90
91impl PoolMethod {
92 pub fn parsed_desc(&self) -> Option<MethodDescriptor> {
93 parse_method_descriptor(&self.desc)
94 }
95 pub fn is_static(&self) -> bool {
96 self.access & access::ACC_STATIC != 0
97 }
98 pub fn is_abstract_or_native(&self) -> bool {
99 self.access & (access::ACC_ABSTRACT | access::ACC_NATIVE) != 0
100 }
101}
102
103enum ClassEntry {
106 Eager(PoolClass),
107 Lazy {
108 at: (usize, usize),
109 pc: std::sync::OnceLock<PoolClass>,
110 },
111}
112
113#[derive(Debug, Clone)]
116pub struct PoolClass {
117 pub name: String,
118 pub access: u32,
119 pub super_name: Option<String>,
121 pub interfaces: Vec<String>,
122 pub source_file: Option<String>,
123 pub static_fields: Vec<PoolField>,
124 pub instance_fields: Vec<PoolField>,
125 pub direct_methods: Vec<PoolMethod>,
126 pub virtual_methods: Vec<PoolMethod>,
127 pub static_values: Vec<StaticValue>,
129 pub nesting: ResolvedNesting,
131 pub dex_idx: usize,
133}
134
135#[derive(Debug, Clone, Default)]
137pub struct ResolvedNesting {
138 pub enclosing_class: Option<String>,
139 pub enclosing_method: Option<(String, String)>,
141 pub member_classes: Vec<String>,
142}
143
144impl PoolClass {
145 pub fn is_interface(&self) -> bool {
146 self.access & access::ACC_INTERFACE != 0
147 }
148
149 pub fn is_enum(&self) -> bool {
150 self.access & access::ACC_ENUM != 0
151 }
152
153 pub fn is_synthetic(&self) -> bool {
154 self.access & access::ACC_SYNTHETIC != 0
155 }
156
157 pub fn is_static_nested(&self) -> bool {
158 self.access & access::ACC_STATIC != 0
159 }
160
161 pub fn all_methods(&self) -> impl Iterator<Item = &PoolMethod> {
163 self.direct_methods
164 .iter()
165 .chain(self.virtual_methods.iter())
166 }
167
168 pub fn find_method(&self, name: &str, desc: &str) -> Option<&PoolMethod> {
170 self.all_methods()
171 .find(|m| &*m.name == name && &*m.desc == desc)
172 }
173
174 pub fn ctors_by_arity(&self, arity: usize) -> Vec<&PoolMethod> {
176 self.all_methods()
177 .filter(|m| &*m.name == "<init>")
178 .filter(|m| {
179 m.parsed_desc()
180 .map(|d| d.args.len() == arity)
181 .unwrap_or(false)
182 })
183 .collect()
184 }
185
186 pub fn field_flags_of(&self, name: &str) -> Option<u32> {
187 self.static_fields
188 .iter()
189 .chain(self.instance_fields.iter())
190 .find(|f| f.name == name)
191 .map(|f| f.access)
192 }
193}
194
195pub struct DexPool {
197 dexes: Vec<std::sync::Arc<DexFile>>,
203 pub dex_labels: Vec<String>,
206 classes: HashMap<String, ClassEntry>,
211 pub order: Vec<String>,
213 retire_counts: std::sync::Mutex<Vec<u64>>,
215 retire_armed: std::sync::atomic::AtomicBool,
216 outers: std::sync::OnceLock<HashMap<String, Option<String>>>,
218 children: std::sync::OnceLock<HashMap<String, Vec<String>>>,
220 ref_caches: Vec<DexRefCache>,
222 accessor_code: std::sync::OnceLock<AccessorSnapshots>,
232}
233
234type AccessorSnapshots = HashMap<(usize, u32), std::sync::Arc<[u8]>>;
237
238pub(crate) struct DexRefCache {
248 proto_descs: Box<[std::sync::OnceLock<std::sync::Arc<jdc_core::types::MethodDescriptor>>]>,
249 type_names: Box<[std::sync::OnceLock<std::sync::Arc<str>>]>,
250 type_javas: Box<[std::sync::OnceLock<std::sync::Arc<jdc_core::types::JavaType>>]>,
251}
252
253impl DexRefCache {
254 fn new(protos: usize, types: usize) -> Self {
255 DexRefCache {
256 proto_descs: (0..protos).map(|_| std::sync::OnceLock::new()).collect(),
257 type_names: (0..types).map(|_| std::sync::OnceLock::new()).collect(),
258 type_javas: (0..types).map(|_| std::sync::OnceLock::new()).collect(),
259 }
260 }
261}
262
263impl DexPool {
264 pub fn new() -> Self {
265 DexPool {
266 dexes: Vec::new(),
267 retire_armed: std::sync::atomic::AtomicBool::new(false),
268 dex_labels: Vec::new(),
269 classes: HashMap::default(),
270 order: Vec::new(),
271 retire_counts: std::sync::Mutex::new(Vec::new()),
272 outers: std::sync::OnceLock::new(),
273 children: std::sync::OnceLock::new(),
274 ref_caches: Vec::new(),
275 accessor_code: std::sync::OnceLock::new(),
276 }
277 }
278
279 pub fn proto_desc_parsed(
281 &self,
282 di: usize,
283 proto_idx: u32,
284 ) -> std::sync::Arc<jdc_core::types::MethodDescriptor> {
285 let Some(dex) = self.dexes.get(di) else {
286 return std::sync::Arc::new(jdc_core::types::MethodDescriptor {
287 args: Vec::new(),
288 ret: jdc_core::types::JavaType::Void,
289 });
290 };
291 let build = || {
292 std::sync::Arc::new(parse_proto_desc(dex, proto_idx))
293 };
294 match self
295 .ref_caches
296 .get(di)
297 .and_then(|c| c.proto_descs.get(proto_idx as usize))
298 {
299 Some(slot) => slot.get_or_init(build).clone(),
300 None => build(),
301 }
302 }
303
304 pub fn type_name_arc(&self, di: usize, type_idx: u32) -> std::sync::Arc<str> {
306 let Some(dex) = self.dexes.get(di) else {
307 return std::sync::Arc::from("");
308 };
309 let build = || std::sync::Arc::from(dex.class_name(type_idx).as_str());
310 match self
311 .ref_caches
312 .get(di)
313 .and_then(|c| c.type_names.get(type_idx as usize))
314 {
315 Some(slot) => slot.get_or_init(build).clone(),
316 None => build(),
317 }
318 }
319
320 pub fn type_java(&self, di: usize, type_idx: u32) -> std::sync::Arc<jdc_core::types::JavaType> {
322 let Some(dex) = self.dexes.get(di) else {
323 return std::sync::Arc::new(jdc_core::types::JavaType::Object(
324 std::sync::Arc::from("java/lang/Object"),
325 ));
326 };
327 let build = || std::sync::Arc::new(desc_type(dex.type_name(type_idx)));
328 match self
329 .ref_caches
330 .get(di)
331 .and_then(|c| c.type_javas.get(type_idx as usize))
332 {
333 Some(slot) => slot.get_or_init(build).clone(),
334 None => build(),
335 }
336 }
337
338 fn outer_map(&self) -> &HashMap<String, Option<String>> {
340 self.outers.get_or_init(|| {
341 self.order
342 .iter()
343 .map(|n| (n.clone(), find_outer_name(self, n)))
344 .collect()
345 })
346 }
347
348 pub fn outer_of(&self, name: &str) -> Option<&str> {
350 self.outer_map().get(name).and_then(|o| o.as_deref())
351 }
352
353 pub fn children_of(&self, internal: &str) -> &[String] {
357 if self.children.get().is_none() {
358 let mut idx: HashMap<String, Vec<String>> = HashMap::default();
366 for name in &self.order {
367 if let Some(outer) = self.outer_of(name).map(str::to_string) {
368 idx.entry(outer).or_default().push(name.clone());
369 }
370 }
371 let _ = self.children.set(idx);
374 }
375 self.children
376 .get()
377 .and_then(|m| m.get(internal))
378 .map(|v| v.as_slice())
379 .unwrap_or(&[])
380 }
381
382 pub fn add_dex(&mut self, dex: DexFile) -> usize {
386 let dex_idx = self.dexes.len();
387 self.classes.reserve(dex.class_defs.len());
390 for cd in &dex.class_defs {
393 let name = dex.class_name(cd.class_idx);
394 if self.classes.contains_key(&name) {
395 continue;
396 }
397 let pc = pool_class_of(&dex, dex.raw(), cd, dex_idx);
398 self.classes.insert(name.clone(), ClassEntry::Eager(pc));
399 self.order.push(name);
400 }
401 self.ref_caches
402 .push(DexRefCache::new(dex.proto_count(), dex.type_count()));
403 self.dexes.push(std::sync::Arc::new(dex));
404 self.retire_counts.lock().unwrap().push(0);
405 self.dex_labels.push(format!("dex {}", dex_idx));
406 dex_idx
407 }
408
409 pub fn add_dex_lazy(&mut self, dex: DexFile) -> usize {
413 let dex_idx = self.dexes.len();
414 self.classes.reserve(dex.class_defs.len());
415 for (ci, cd) in dex.class_defs.iter().enumerate() {
416 let name = dex.class_name(cd.class_idx);
417 if self.classes.contains_key(&name) {
418 continue;
419 }
420 self.classes.insert(
421 name.clone(),
422 ClassEntry::Lazy {
423 at: (dex_idx, ci),
424 pc: std::sync::OnceLock::new(),
425 },
426 );
427 self.order.push(name);
428 }
429 self.ref_caches
430 .push(DexRefCache::new(dex.proto_count(), dex.type_count()));
431 self.dexes.push(std::sync::Arc::new(dex));
432 self.retire_counts.lock().unwrap().push(0);
433 self.dex_labels.push(format!("dex {}", dex_idx));
434 dex_idx
435 }
436
437 pub fn set_dex_label(&mut self, idx: usize, label: impl Into<String>) {
439 if let Some(slot) = self.dex_labels.get_mut(idx) {
440 *slot = label.into();
441 }
442 }
443
444 pub fn len(&self) -> usize {
445 self.classes.len()
446 }
447
448 pub fn is_empty(&self) -> bool {
449 self.classes.is_empty()
450 }
451
452 pub fn get(&self, internal: &str) -> Option<&PoolClass> {
453 self.get_inner(internal, None)
454 }
455
456 fn get_inner(&self, internal: &str, raws: Option<&[&[u8]]>) -> Option<&PoolClass> {
461 match self.classes.get(internal)? {
462 ClassEntry::Eager(pc) => Some(pc),
463 ClassEntry::Lazy { at, pc } => {
464 if let Some(built) = pc.get() {
465 return Some(built);
466 }
467 let (di, ci) = *at;
468 let dex = self.dexes.get(di)?;
469 let cd = dex.class_defs.get(ci)?;
470 let raw = match raws {
471 Some(r) => r.get(di).copied().unwrap_or(&[]),
472 None => dex.raw(),
473 };
474 let built = pool_class_of(dex, raw, cd, di);
475 Some(pc.get_or_init(move || built))
476 }
477 }
478 }
479
480 pub fn materialize_all(&self, threads: usize) {
486 let keys: Vec<String> = self
487 .classes
488 .iter()
489 .filter(|(_, e)| matches!(e, ClassEntry::Lazy { .. }))
490 .map(|(k, _)| k.clone())
491 .collect();
492 if keys.is_empty() {
493 return;
494 }
495 let raws: Vec<&[u8]> = self.dexes.iter().map(|d| d.raw()).collect();
500 let threads = threads.max(1);
501 let next = std::sync::atomic::AtomicUsize::new(0);
502 std::thread::scope(|scope| {
503 for _ in 0..threads {
504 scope.spawn(|| loop {
505 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
506 let Some(k) = keys.get(i) else { break };
507 let _ = self.get_inner(k, Some(&raws));
508 });
509 }
510 });
511 }
512
513 fn get_if_materialized(&self, internal: &str) -> Option<&PoolClass> {
515 match self.classes.get(internal)? {
516 ClassEntry::Eager(pc) => Some(pc),
517 ClassEntry::Lazy { pc, .. } => pc.get(),
518 }
519 }
520
521 pub fn has_name(&self, internal: &str) -> bool {
523 self.classes.contains_key(internal)
524 }
525
526 pub fn dex(&self, idx: usize) -> Option<std::sync::Arc<DexFile>> {
529 self.dexes.get(idx).cloned()
530 }
531
532 pub fn dex_count(&self) -> usize {
533 self.dexes.len()
534 }
535
536 pub fn class_names(&self) -> impl Iterator<Item = &str> {
537 self.order.iter().map(|s| s.as_str())
538 }
539
540 pub fn is_subtype(&self, sub: &str, sup: &str) -> bool {
543 if sub == sup {
544 return true;
545 }
546 if let (Some(sub_elem), Some(sup_elem)) = (array_elem(sub), array_elem(sup)) {
548 if sub_elem.starts_with('L')
549 && sup_elem.starts_with('L')
550 && sub_elem.ends_with(';')
551 && sup_elem.ends_with(';')
552 {
553 return self.is_subtype(
554 &sub_elem[1..sub_elem.len() - 1],
555 &sup_elem[1..sup_elem.len() - 1],
556 );
557 }
558 return sub_elem == sup_elem;
559 }
560 let mut cur = self.get(sub);
561 let mut hops = 0;
562 while let Some(c) = cur {
563 hops += 1;
564 if hops > 64 {
565 return false;
566 }
567 for i in &c.interfaces {
568 if i == sup || self.is_subtype(i, sup) {
569 return true;
570 }
571 }
572 match &c.super_name {
573 Some(s) if s == sup => return true,
574 Some(s) if s != "java/lang/Object" => {
575 cur = self.get(s);
576 }
577 _ => return false,
578 }
579 }
580 false
581 }
582}
583
584impl Default for DexPool {
585 fn default() -> Self {
586 Self::new()
587 }
588}
589
590fn array_elem(desc: &str) -> Option<&str> {
591 desc.strip_prefix('[')
592}
593
594fn resolve_static_value(v: &EncodedValue, dex: &DexFile) -> StaticValue {
595 match v {
596 EncodedValue::Byte(x) => StaticValue::Int(*x as i64),
597 EncodedValue::Short(x) => StaticValue::Int(*x as i64),
598 EncodedValue::Char(x) => StaticValue::Int(*x as i64),
599 EncodedValue::Int(x) => StaticValue::Int(*x as i64),
600 EncodedValue::Long(x) => StaticValue::Int(*x),
601 EncodedValue::Float(x) => StaticValue::Float(*x),
602 EncodedValue::Double(x) => StaticValue::Double(*x),
603 EncodedValue::String(i) => StaticValue::Str(dex.string(*i).to_string()),
604 EncodedValue::Type(i) => StaticValue::Type(dex.class_name(*i)),
605 EncodedValue::Boolean(b) => StaticValue::Boolean(*b),
606 EncodedValue::Null => StaticValue::Null,
607 EncodedValue::Enum(i) | EncodedValue::Field(i) => {
608 let f = dex.field(*i);
609 StaticValue::Field(
610 dex.class_name(f.class_idx),
611 dex.string(f.name_idx).to_string(),
612 )
613 }
614 _ => StaticValue::Other,
615 }
616}
617
618fn pool_class_of(dex: &DexFile, raw: &[u8], cd: &ClassDef, dex_idx: usize) -> PoolClass {
619 let name = dex.class_name(cd.class_idx);
620 let super_name = if cd.superclass_idx == ddc_dex::NO_INDEX {
621 None
622 } else {
623 let s = dex.class_name(cd.superclass_idx);
624 if s.is_empty() || s == "java/lang/Object" {
625 None
626 } else {
627 Some(s)
628 }
629 };
630 let interfaces = dex
631 .interfaces_of(cd)
632 .into_iter()
633 .map(|t| dex.class_name(t))
634 .collect();
635 let source_file = if cd.source_file_idx == ddc_dex::NO_INDEX {
636 None
637 } else {
638 Some(dex.string(cd.source_file_idx).to_string())
639 };
640
641 let data = dex.class_data(cd);
642 let mk_field = |ef: &ddc_dex::EncodedField, is_static: bool| {
643 let f = dex.field(ef.field_idx);
644 PoolField {
645 name: dex.string(f.name_idx).to_string(),
646 desc: dex.type_name(f.type_idx).to_string(),
647 access: ef.access_flags,
648 is_static,
649 }
650 };
651 let static_fields: Vec<PoolField> = data
652 .static_fields
653 .iter()
654 .map(|f| mk_field(f, true))
655 .collect();
656 let instance_fields: Vec<PoolField> = data
657 .instance_fields
658 .iter()
659 .map(|f| mk_field(f, false))
660 .collect();
661 let mk_method = |em: &ddc_dex::EncodedMethod| {
662 let m = dex.method(em.method_idx);
663 let (code_off, debug_info_off) = match dex.debug_info_off_at(em.code_off) {
664 Some(d) => (em.code_off, d),
665 None => (0, 0),
666 };
667 PoolMethod {
668 name: dex.string_arc(m.name_idx),
669 desc: dex.proto_desc(m.proto_idx),
670 access: em.access_flags,
671 code_off,
672 debug_info_off,
673 dex_idx,
674 }
675 };
676 let direct_methods: Vec<PoolMethod> = data.direct_methods.iter().map(&mk_method).collect();
677 let virtual_methods: Vec<PoolMethod> = data.virtual_methods.iter().map(&mk_method).collect();
678
679 let static_values: Vec<StaticValue> = dex
680 .static_values(cd.static_values_off)
681 .into_iter()
682 .map(|v| resolve_static_value(&v, dex))
683 .collect();
684 let anns = if cd.annotations_off != 0 {
685 annotations::read_class_annotations(raw, cd.annotations_off)
686 } else {
687 Vec::new()
688 };
689 let raw = annotations::nesting_from(&anns, &|t: u32| dex.type_name(t).to_string());
690 let nesting = ResolvedNesting {
691 enclosing_class: raw.enclosing_class.map(|t| dex.class_name(t)),
692 enclosing_method: raw.enclosing_method.map(|m| {
693 let mid = dex.method(m);
694 (
695 dex.class_name(mid.class_idx),
696 dex.string(mid.name_idx).to_string(),
697 )
698 }),
699 member_classes: raw
700 .member_classes
701 .into_iter()
702 .map(|t| dex.class_name(t))
703 .collect(),
704 };
705
706 PoolClass {
707 name,
708 access: cd.access_flags,
709 super_name,
710 interfaces,
711 source_file,
712 static_fields,
713 instance_fields,
714 direct_methods,
715 virtual_methods,
716 static_values,
717 nesting,
718 dex_idx,
719 }
720}
721
722fn parse_proto_desc(
726 dex: &ddc_dex::DexFile,
727 proto_idx: u32,
728) -> jdc_core::types::MethodDescriptor {
729 let proto = dex.proto(proto_idx);
730 let args = dex
731 .proto_params(proto_idx)
732 .iter()
733 .map(|&t| desc_type(dex.type_name(t)))
734 .collect();
735 jdc_core::types::MethodDescriptor {
736 args,
737 ret: desc_type(dex.type_name(proto.return_type_idx)),
738 }
739}
740
741pub fn desc_type(desc: &str) -> JavaType {
742 parse_field_descriptor(desc).unwrap_or(JavaType::Object("java/lang/Object".into()))
743}
744
745pub fn find_outer_name(pool: &DexPool, internal: &str) -> Option<String> {
748 if let Some(pc) = pool.get_if_materialized(internal) {
753 if let Some(enc) = &pc.nesting.enclosing_class {
754 return Some(enc.clone());
755 }
756 }
757 let mut rest = internal;
758 while let Some(d) = rest.rfind('$') {
759 let cand = &rest[..d];
760 if pool.has_name(cand) {
761 return Some(cand.to_string());
762 }
763 rest = cand;
764 }
765 None
766}
767
768fn clean_member_tail(rest: &str) -> bool {
771 if rest.starts_with('-') {
772 return false;
773 }
774 let tail = rest.rsplit('$').next().unwrap_or(rest);
775 if tail.is_empty() || tail.starts_with(|c: char| c.is_ascii_digit()) {
776 return false;
777 }
778 true
779}
780
781impl DexPool {
784 pub fn snapshot_accessor_code(&self) {
797 let mut snap: AccessorSnapshots = HashMap::default();
798 let mut bytes_total = 0usize;
799 for name in &self.order {
800 let Some(pc) = self.get_if_materialized(name) else {
801 continue;
802 };
803 for m in pc.direct_methods.iter().chain(pc.virtual_methods.iter()) {
804 if m.code_off == 0
805 || !m.is_static()
806 || m.access & crate::access::ACC_SYNTHETIC == 0
807 {
808 continue;
809 }
810 if snap.contains_key(&(m.dex_idx, m.code_off)) {
811 continue;
812 }
813 let Some(dex) = self.dex(m.dex_idx) else {
814 continue;
815 };
816 let raw = dex.raw();
817 let off = m.code_off as usize;
818 if off + 16 > raw.len() {
819 continue;
820 }
821 let u2 = |o: usize| u16::from_le_bytes([raw[o], raw[o + 1]]) as usize;
822 let u4 = |o: usize| {
823 u32::from_le_bytes([raw[o], raw[o + 1], raw[o + 2], raw[o + 3]]) as usize
824 };
825 let tries = u2(off + 6);
826 let insns = u4(off + 12);
827 let mut end = off + 16 + 2 * insns;
828 if tries > 0 {
829 end = end.next_multiple_of(4) + 8 * tries + 256 + 64 * tries;
832 }
833 let end = end.min(raw.len());
834 let slice: std::sync::Arc<[u8]> = raw[off..end].into();
835 bytes_total += slice.len();
836 snap.insert((m.dex_idx, m.code_off), slice);
837 }
838 }
839 if std::env::var("DDC_WALL").is_ok() {
840 eprintln!(
841 "[wall] accessor snapshot: {} entries, {} KB",
842 snap.len(),
843 bytes_total / 1024
844 );
845 }
846 let _ = self.accessor_code.set(snap);
847 }
848
849 pub fn accessor_code(&self, dex_idx: usize, code_off: u32) -> Option<std::sync::Arc<[u8]>> {
852 self.accessor_code.get()?.get(&(dex_idx, code_off)).cloned()
853 }
854
855 pub fn arm_retirement(&self) {
856 self.retire_armed
857 .store(true, std::sync::atomic::Ordering::Release);
858 let mut counts = self.retire_counts.lock().unwrap();
859 for c in counts.iter_mut() {
860 *c = 0;
861 }
862 for entry in self.classes.values() {
863 let di = match entry {
864 ClassEntry::Eager(pc) => pc.dex_idx,
865 ClassEntry::Lazy { at, .. } => at.0,
866 };
867 if let Some(c) = counts.get_mut(di) {
868 *c += 1;
869 }
870 }
871 }
872
873 pub fn report_class_done(&self, class_name: &str) -> Option<usize> {
876 if !self.retire_armed.load(std::sync::atomic::Ordering::Acquire) {
877 return None;
878 }
879 let entry = self.classes.get(class_name)?;
880 let di = match entry {
881 ClassEntry::Eager(pc) => pc.dex_idx,
882 ClassEntry::Lazy { at, .. } => at.0,
883 };
884 let mut counts = self.retire_counts.lock().unwrap();
885 match counts.get_mut(di) {
886 Some(c) => {
887 *c = c.saturating_sub(1);
888 if *c == 0 {
889 Some(di)
890 } else {
891 None
892 }
893 }
894 None => None,
895 }
896 }
897
898 pub fn release_images(&self, indexes: &[usize]) {
902 let mut counts = self.retire_counts.lock().unwrap();
903 for &i in indexes {
904 if let Some(dex) = self.dexes.get(i) {
905 dex.mark_released();
909 }
910 if let Some(c) = counts.get_mut(i) {
911 *c = u64::MAX; }
913 }
914 }
915}
916
917pub fn top_level_classes(pool: &DexPool) -> Vec<String> {
918 pool.class_names()
919 .filter(|name| match pool.outer_of(name) {
920 None => true,
921 Some(outer) => {
922 if pool.get(outer).is_none() {
923 return true;
924 }
925 let rest = name
932 .strip_prefix(outer)
933 .and_then(|t| t.strip_prefix('$'))
934 .unwrap_or("");
935 !clean_member_tail(rest)
936 }
937 })
938 .map(|n| n.to_string())
939 .collect()
940}
941
942pub fn case_rename_map(pool: &DexPool) -> HashMap<String, String> {
949 use std::collections::HashMap;
950 let mut groups: HashMap<String, Vec<String>> = HashMap::default();
951 for t in top_level_classes(pool) {
952 groups.entry(t.to_lowercase()).or_default().push(t);
953 }
954 let folds: std::collections::HashSet<String> = groups.keys().cloned().collect();
955 let mut map = HashMap::default();
956 for (_, mut members) in groups {
957 members.sort();
958 for (i, m) in members.iter().enumerate() {
959 if i == 0 {
960 map.insert(m.clone(), m.clone());
961 continue;
962 }
963 let cut = m.rfind('/').map(|x| x + 1).unwrap_or(0);
966 let mut n = i + 1;
967 loop {
968 let cand = format!("{}{}_{}", &m[..cut], &m[cut..], n);
969 let fold = cand.to_lowercase();
970 if !folds.contains(&fold) {
971 map.insert(m.clone(), cand);
972 break;
973 }
974 n += 1;
975 }
976 }
977 }
978 map
979}
980
981fn nested_collision_renames(pool: &DexPool, map: &mut HashMap<String, String>) {
1018 let mut names: Vec<&String> = pool.order.iter().collect();
1021 names.sort_by_key(|n| n.matches('$').count());
1022 let mut assigned: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
1023 let mut pkg_segments: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
1031 for n in &pool.order {
1032 if let Some((seg, _)) = n.split_once('/') {
1033 pkg_segments.insert(seg.to_string());
1034 }
1035 }
1036 let local_ok: jdc_core::FxHashSet<String> = if pool_majority_materialized(pool) {
1048 refscan::local_ok(&pool.dexes, &field_clash_cands(pool, map))
1049 } else {
1050 jdc_core::FxHashSet::default()
1051 };
1052 for name in names {
1053 if !name.contains('$') || map.contains_key(name) {
1054 continue;
1055 }
1056 let Some(parent) = find_outer_name(pool, name) else {
1057 continue;
1058 };
1059 let Some(rest) = name
1062 .strip_prefix(parent.as_str())
1063 .and_then(|t| t.strip_prefix('$'))
1064 else {
1065 continue;
1066 };
1067 if !clean_member_tail(rest) {
1068 continue;
1069 }
1070 let mut chain: Vec<String> = Vec::new();
1074 let mut cur = parent.clone();
1075 loop {
1076 let disp = map.get(&cur).cloned().unwrap_or_else(|| cur.clone());
1077 let simple = disp.rsplit('/').next().unwrap_or(&disp);
1078 chain.push(simple.rsplit('$').next().unwrap_or(simple).to_string());
1079 match find_outer_name(pool, &cur) {
1080 Some(o) if o != cur => cur = o,
1081 _ => break,
1082 }
1083 }
1084 let root: &str = &cur;
1085 let disp_parent = map
1086 .get(&parent)
1087 .cloned()
1088 .unwrap_or_else(|| parent.clone());
1089 let tail = rest.rsplit('$').next().unwrap_or(rest);
1090 let orphan = rest.contains('$');
1091 let field_clash = local_ok.contains(name.as_str());
1104 let clash = chain.iter().any(|c| c == tail) || field_clash;
1105 if !orphan && !clash {
1106 continue;
1107 }
1108 let mut enc_fields: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
1113 if field_clash {
1114 if let Some(pc) = pool.get(&parent) {
1115 for f in pc.static_fields.iter().chain(pc.instance_fields.iter()) {
1116 enc_fields.insert(crate::classdec::java_ident(&f.name).into_owned());
1117 }
1118 }
1119 }
1120 let mut k = 0u32;
1121 loop {
1122 k += 1;
1123 let cand_tail = if k == 1 {
1124 tail.to_string()
1125 } else {
1126 format!("{tail}{k}")
1127 };
1128 if chain.contains(&cand_tail) || enc_fields.contains(&cand_tail) {
1133 continue;
1134 }
1135 if field_clash {
1147 if pkg_segments.contains(&cand_tail) {
1148 continue;
1149 }
1150 if let Some((root_pkg, _)) = root.rsplit_once('/') {
1151 if pool.has_name(&format!("{root_pkg}/{cand_tail}")) {
1152 continue;
1153 }
1154 }
1155 }
1156 let cand = format!("{disp_parent}${cand_tail}");
1157 if pool.has_name(&cand) || assigned.contains(&cand) {
1158 continue;
1159 }
1160 assigned.insert(cand.clone());
1161 map.insert(name.clone(), cand);
1162 break;
1163 }
1164 }
1165}
1166
1167fn field_clash_cands(pool: &DexPool, map: &HashMap<String, String>) -> HashMap<String, String> {
1174 let mut out: HashMap<String, String> = HashMap::default();
1175 for name in &pool.order {
1176 if !name.contains('$') || map.contains_key(name) {
1177 continue;
1178 }
1179 let Some(parent) = find_outer_name(pool, name) else {
1180 continue;
1181 };
1182 let Some(rest) = name
1183 .strip_prefix(parent.as_str())
1184 .and_then(|t| t.strip_prefix('$'))
1185 else {
1186 continue;
1187 };
1188 if !clean_member_tail(rest) {
1189 continue;
1190 }
1191 let tail = rest.rsplit('$').next().unwrap_or(rest);
1192 let Some(pc) = pool.get(&parent) else {
1193 continue;
1194 };
1195 let clash = pc
1196 .static_fields
1197 .iter()
1198 .chain(pc.instance_fields.iter())
1199 .any(|f| crate::classdec::java_ident(&f.name).as_ref() == tail);
1200 if !clash {
1201 continue;
1202 }
1203 let mut root = parent.clone();
1204 loop {
1205 match find_outer_name(pool, &root) {
1206 Some(o) if o != root => root = o,
1207 _ => break,
1208 }
1209 }
1210 out.insert(name.clone(), root);
1211 }
1212 out
1213}
1214
1215fn pool_majority_materialized(pool: &DexPool) -> bool {
1220 let (mut mat, mut lazy) = (0usize, 0usize);
1221 for e in pool.classes.values() {
1222 match e {
1223 ClassEntry::Eager(_) => mat += 1,
1224 ClassEntry::Lazy { pc, .. } => {
1225 if pc.get().is_some() {
1226 mat += 1;
1227 } else {
1228 lazy += 1;
1229 }
1230 }
1231 }
1232 }
1233 lazy == 0 || mat >= lazy
1234}
1235
1236pub fn install_case_renames(pool: &DexPool) {
1238 let mut map = case_rename_map(pool);
1239 pkg_leaf_shadow_renames(pool, &mut map);
1244 nested_collision_renames(pool, &mut map);
1245 jdc_core::rename::set_class_renames(map);
1246 jdc_core::rename::set_field_renames(member_collision_renames(pool));
1247}
1248
1249fn pkg_leaf_shadow_renames(pool: &DexPool, map: &mut HashMap<String, String>) {
1257 let taken: jdc_core::FxHashSet<String> = pool.order.iter().cloned().collect();
1258 for name in &pool.order {
1259 let Some((pkg, simple)) = name.rsplit_once('/') else {
1260 continue;
1261 };
1262 if simple.is_empty() || pkg.is_empty() {
1263 continue;
1264 }
1265 let leaf = pkg.rsplit('/').next().unwrap_or(pkg);
1266 if simple != leaf {
1269 continue;
1270 }
1271 let prefix = format!("{pkg}/");
1272 let siblings = pool
1273 .order
1274 .iter()
1275 .filter(|n| n.starts_with(&prefix) && **n != *name)
1276 .count();
1277 if siblings == 0 || map.get(name).is_some_and(|v| v != name) {
1278 continue; }
1280 let mut k = 1u32;
1281 loop {
1282 k += 1;
1283 let cand = format!("{pkg}/{simple}{k}");
1284 if !taken.contains(&cand) && !map.values().any(|v| *v == cand) {
1285 map.insert(name.clone(), cand);
1286 break;
1287 }
1288 }
1289 }
1290}
1291
1292fn member_collision_renames(
1305 pool: &DexPool,
1306) -> HashMap<std::sync::Arc<str>, Vec<jdc_core::rename::FieldRename>> {
1307 let mut out: HashMap<std::sync::Arc<str>, Vec<jdc_core::rename::FieldRename>> =
1308 HashMap::default();
1309 for name in &pool.order {
1310 let Some(pc) = pool.get_if_materialized(name) else {
1311 continue;
1312 };
1313 let outer_ref_desc = find_outer_name(pool, name)
1314 .filter(|o| o != name)
1315 .map(|o| format!("L{o};"));
1316 let fields: Vec<&PoolField> = pc
1318 .static_fields
1319 .iter()
1320 .chain(pc.instance_fields.iter())
1321 .collect();
1322 let fgroups: HashMap<String, Vec<&PoolField>> = HashMap::default();
1323 let mut fgroups = fgroups;
1324 for f in &fields {
1325 let disp = crate::classdec::java_ident(&f.name).into_owned();
1326 fgroups.entry(disp).or_default().push(f);
1327 }
1328 let f_taken: jdc_core::FxHashSet<String> = fields
1329 .iter()
1330 .map(|f| crate::classdec::java_ident(&f.name).into_owned())
1331 .collect();
1332 let mut f_taken = f_taken;
1333 for group in fgroups.values() {
1334 if group.len() < 2 {
1335 continue;
1336 }
1337 let keeper_pos = group
1338 .iter()
1339 .position(|f| f.access & crate::access::ACC_SYNTHETIC == 0)
1340 .unwrap_or(0);
1341 for (gi, f) in group.iter().enumerate() {
1342 if gi == keeper_pos {
1343 continue;
1344 }
1345 let base = crate::classdec::java_ident(&f.name).into_owned();
1346 let display: String = match &outer_ref_desc {
1347 Some(ord)
1348 if f.access & crate::access::ACC_SYNTHETIC != 0
1349 && !f.is_static
1350 && *ord == f.desc =>
1351 {
1352 let mut k = 0u32;
1353 loop {
1354 let cand = if k == 0 {
1355 "this$0".to_string()
1356 } else {
1357 format!("this$0{k}")
1358 };
1359 if f_taken.insert(cand.clone()) {
1360 break cand;
1361 }
1362 k += 1;
1363 }
1364 }
1365 _ => suffix_unique(&base, &mut f_taken),
1366 };
1367 out.entry(std::sync::Arc::from(name.as_str()))
1368 .or_default()
1369 .push(jdc_core::rename::FieldRename {
1370 name: std::sync::Arc::from(f.name.as_str()),
1371 desc: std::sync::Arc::from(f.desc.as_str()),
1372 display: std::sync::Arc::from(display.as_str()),
1373 });
1374 }
1375 }
1376 let methods: Vec<&PoolMethod> = pc.all_methods().collect();
1378 let mut m_taken: jdc_core::FxHashSet<String> = methods
1379 .iter()
1380 .map(|m| crate::classdec::java_ident(&m.name).into_owned())
1381 .collect();
1382 let mut mgroups: HashMap<(String, String), Vec<&PoolMethod>> = HashMap::default();
1383 for m in &methods {
1384 let d: &str = &m.desc;
1385 let lo = d.find('(').map(|i| i + 1).unwrap_or(0);
1386 let hi = d.find(')').unwrap_or(d.len());
1387 let key = (
1388 crate::classdec::java_ident(&m.name).into_owned(),
1389 d[lo..hi].to_string(),
1390 );
1391 mgroups.entry(key).or_default().push(m);
1392 }
1393 for ((base, _), group) in &mgroups {
1394 if group.len() < 2 {
1395 continue;
1396 }
1397 let covariant = group
1405 .iter()
1406 .any(|m| group.iter().any(|o| o.name == m.name && o.desc != m.desc));
1407 if covariant {
1408 continue;
1409 }
1410 let mut base = base.clone();
1415 let mut seen_orig: jdc_core::FxHashSet<(&str, &str)> =
1416 jdc_core::FxHashSet::default();
1417 for m in group {
1418 if !seen_orig.insert((&m.name, &m.desc)) {
1419 continue; }
1421 let display = suffix_unique(&base, &mut m_taken);
1422 out.entry(std::sync::Arc::from(name.as_str()))
1423 .or_default()
1424 .push(jdc_core::rename::FieldRename {
1425 name: m.name.clone(),
1426 desc: m.desc.clone(),
1427 display: std::sync::Arc::from(display.as_str()),
1428 });
1429 let _ = &mut base;
1432 }
1433 }
1434 }
1435 out
1436}
1437
1438fn suffix_unique(base: &str, taken: &mut jdc_core::FxHashSet<String>) -> String {
1439 let mut k = 1u32;
1440 loop {
1441 k += 1;
1442 let cand = format!("{base}{k}");
1443 if taken.insert(cand.clone()) {
1444 return cand;
1445 }
1446 }
1447}
1448
1449pub fn install_field_renames(pool: &DexPool) {
1452 jdc_core::rename::set_field_renames(member_collision_renames(pool));
1453}
1454pub use jdc_core::rename::apply_class_rename;