1pub mod cfg;
9pub mod classdec;
10
11pub use classdec::ClassOptions;
12pub mod ctx;
13pub mod lift;
14pub mod method;
15pub mod passes;
16
17use std::collections::HashMap;
18
19use ddc_dex::annotations::{self, EncodedValue};
20
21#[derive(Debug, Clone)]
23pub enum StaticValue {
24 Int(i64),
25 Float(f32),
26 Double(f64),
27 Str(String),
28 Type(String),
29 Boolean(bool),
30 Null,
31 Field(String, String),
33 Other,
34}
35use ddc_dex::{ClassDef, DexFile};
36use jdc_core::types::{
37 parse_field_descriptor, parse_method_descriptor, JavaType, MethodDescriptor,
38};
39
40pub mod access {
44 pub const ACC_PUBLIC: u32 = 0x1;
45 pub const ACC_PRIVATE: u32 = 0x2;
46 pub const ACC_PROTECTED: u32 = 0x4;
47 pub const ACC_STATIC: u32 = 0x8;
48 pub const ACC_FINAL: u32 = 0x10;
49 pub const ACC_SYNCHRONIZED: u32 = 0x20;
50 pub const ACC_BRIDGE: u32 = 0x40;
51 pub const ACC_VARARGS: u32 = 0x80;
52 pub const ACC_NATIVE: u32 = 0x100;
53 pub const ACC_INTERFACE: u32 = 0x200;
54 pub const ACC_ABSTRACT: u32 = 0x400;
55 pub const ACC_STRICT: u32 = 0x800;
56 pub const ACC_SYNTHETIC: u32 = 0x1000;
57 pub const ACC_ANNOTATION: u32 = 0x2000;
58 pub const ACC_ENUM: u32 = 0x4000;
59 pub const ACC_CONSTRUCTOR: u32 = 0x1_0000;
60 pub const ACC_DECLARED_SYNCHRONIZED: u32 = 0x2_0000;
61}
62
63#[derive(Debug, Clone)]
65pub struct PoolField {
66 pub name: String,
67 pub desc: String,
69 pub access: u32,
70 pub is_static: bool,
71}
72
73#[derive(Debug, Clone)]
75pub struct PoolMethod {
76 pub name: std::sync::Arc<str>,
78 pub desc: std::sync::Arc<str>,
82 pub access: u32,
83 pub code_off: u32,
84 pub debug_info_off: u32,
85 pub dex_idx: usize,
87}
88
89impl PoolMethod {
90 pub fn parsed_desc(&self) -> Option<MethodDescriptor> {
91 parse_method_descriptor(&self.desc)
92 }
93 pub fn is_static(&self) -> bool {
94 self.access & access::ACC_STATIC != 0
95 }
96 pub fn is_abstract_or_native(&self) -> bool {
97 self.access & (access::ACC_ABSTRACT | access::ACC_NATIVE) != 0
98 }
99}
100
101enum ClassEntry {
104 Eager(PoolClass),
105 Lazy {
106 at: (usize, usize),
107 pc: std::sync::OnceLock<PoolClass>,
108 },
109}
110
111#[derive(Debug, Clone)]
114pub struct PoolClass {
115 pub name: String,
116 pub access: u32,
117 pub super_name: Option<String>,
119 pub interfaces: Vec<String>,
120 pub source_file: Option<String>,
121 pub static_fields: Vec<PoolField>,
122 pub instance_fields: Vec<PoolField>,
123 pub direct_methods: Vec<PoolMethod>,
124 pub virtual_methods: Vec<PoolMethod>,
125 pub static_values: Vec<StaticValue>,
127 pub nesting: ResolvedNesting,
129 pub dex_idx: usize,
131}
132
133#[derive(Debug, Clone, Default)]
135pub struct ResolvedNesting {
136 pub enclosing_class: Option<String>,
137 pub enclosing_method: Option<(String, String)>,
139 pub member_classes: Vec<String>,
140}
141
142impl PoolClass {
143 pub fn is_interface(&self) -> bool {
144 self.access & access::ACC_INTERFACE != 0
145 }
146
147 pub fn is_enum(&self) -> bool {
148 self.access & access::ACC_ENUM != 0
149 }
150
151 pub fn is_synthetic(&self) -> bool {
152 self.access & access::ACC_SYNTHETIC != 0
153 }
154
155 pub fn is_static_nested(&self) -> bool {
156 self.access & access::ACC_STATIC != 0
157 }
158
159 pub fn all_methods(&self) -> impl Iterator<Item = &PoolMethod> {
161 self.direct_methods
162 .iter()
163 .chain(self.virtual_methods.iter())
164 }
165
166 pub fn find_method(&self, name: &str, desc: &str) -> Option<&PoolMethod> {
168 self.all_methods()
169 .find(|m| &*m.name == name && &*m.desc == desc)
170 }
171
172 pub fn ctors_by_arity(&self, arity: usize) -> Vec<&PoolMethod> {
174 self.all_methods()
175 .filter(|m| &*m.name == "<init>")
176 .filter(|m| {
177 m.parsed_desc()
178 .map(|d| d.args.len() == arity)
179 .unwrap_or(false)
180 })
181 .collect()
182 }
183
184 pub fn field_flags_of(&self, name: &str) -> Option<u32> {
185 self.static_fields
186 .iter()
187 .chain(self.instance_fields.iter())
188 .find(|f| f.name == name)
189 .map(|f| f.access)
190 }
191}
192
193pub struct DexPool {
195 dexes: Vec<std::sync::Arc<DexFile>>,
201 pub dex_labels: Vec<String>,
204 classes: HashMap<String, ClassEntry>,
209 pub order: Vec<String>,
211 retire_counts: std::sync::Mutex<Vec<u64>>,
213 retire_armed: std::sync::atomic::AtomicBool,
214 outers: std::sync::OnceLock<HashMap<String, Option<String>>>,
216 children: std::sync::OnceLock<HashMap<String, Vec<String>>>,
218}
219
220impl DexPool {
221 pub fn new() -> Self {
222 DexPool {
223 dexes: Vec::new(),
224 retire_armed: std::sync::atomic::AtomicBool::new(false),
225 dex_labels: Vec::new(),
226 classes: HashMap::new(),
227 order: Vec::new(),
228 retire_counts: std::sync::Mutex::new(Vec::new()),
229 outers: std::sync::OnceLock::new(),
230 children: std::sync::OnceLock::new(),
231 }
232 }
233
234 fn outer_map(&self) -> &HashMap<String, Option<String>> {
236 self.outers.get_or_init(|| {
237 self.order
238 .iter()
239 .map(|n| (n.clone(), find_outer_name(self, n)))
240 .collect()
241 })
242 }
243
244 pub fn outer_of(&self, name: &str) -> Option<&str> {
246 self.outer_map().get(name).and_then(|o| o.as_deref())
247 }
248
249 pub fn children_of(&self, internal: &str) -> &[String] {
253 if self.children.get().is_none() {
254 let idx: HashMap<String, Vec<String>> = HashMap::new();
255 let _ = self.children.set(idx);
256 let mut idx: HashMap<String, Vec<String>> = HashMap::new();
257 for name in &self.order {
258 if let Some(outer) = self.outer_of(name).map(str::to_string) {
259 idx.entry(outer).or_default().push(name.clone());
260 }
261 }
262 let _ = self.children.set(idx);
263 }
264 self.children
265 .get()
266 .and_then(|m| m.get(internal))
267 .map(|v| v.as_slice())
268 .unwrap_or(&[])
269 }
270
271 pub fn add_dex(&mut self, dex: DexFile) -> usize {
275 let dex_idx = self.dexes.len();
276 for cd in &dex.class_defs {
279 let name = dex.class_name(cd.class_idx);
280 if self.classes.contains_key(&name) {
281 continue;
282 }
283 let pc = pool_class_of(&dex, dex.raw(), cd, dex_idx);
284 self.classes.insert(name.clone(), ClassEntry::Eager(pc));
285 self.order.push(name);
286 }
287 self.dexes.push(std::sync::Arc::new(dex));
288 self.retire_counts.lock().unwrap().push(0);
289 self.dex_labels.push(format!("dex {}", dex_idx));
290 dex_idx
291 }
292
293 pub fn add_dex_lazy(&mut self, dex: DexFile) -> usize {
297 let dex_idx = self.dexes.len();
298 for (ci, cd) in dex.class_defs.iter().enumerate() {
299 let name = dex.class_name(cd.class_idx);
300 if self.classes.contains_key(&name) {
301 continue;
302 }
303 self.classes.insert(
304 name.clone(),
305 ClassEntry::Lazy {
306 at: (dex_idx, ci),
307 pc: std::sync::OnceLock::new(),
308 },
309 );
310 self.order.push(name);
311 }
312 self.dexes.push(std::sync::Arc::new(dex));
313 self.retire_counts.lock().unwrap().push(0);
314 self.dex_labels.push(format!("dex {}", dex_idx));
315 dex_idx
316 }
317
318 pub fn set_dex_label(&mut self, idx: usize, label: impl Into<String>) {
320 if let Some(slot) = self.dex_labels.get_mut(idx) {
321 *slot = label.into();
322 }
323 }
324
325 pub fn len(&self) -> usize {
326 self.classes.len()
327 }
328
329 pub fn is_empty(&self) -> bool {
330 self.classes.is_empty()
331 }
332
333 pub fn get(&self, internal: &str) -> Option<&PoolClass> {
334 match self.classes.get(internal)? {
335 ClassEntry::Eager(pc) => Some(pc),
336 ClassEntry::Lazy { at, pc } => {
337 if let Some(built) = pc.get() {
338 return Some(built);
339 }
340 let (di, ci) = *at;
341 let dex = self.dexes.get(di)?;
342 let cd = dex.class_defs.get(ci)?;
343 let built = pool_class_of(dex, dex.raw(), cd, di);
344 Some(pc.get_or_init(move || built))
345 }
346 }
347 }
348
349 pub fn materialize_all(&self, threads: usize) {
355 let keys: Vec<String> = self
356 .classes
357 .iter()
358 .filter(|(_, e)| matches!(e, ClassEntry::Lazy { .. }))
359 .map(|(k, _)| k.clone())
360 .collect();
361 let threads = threads.max(1);
362 let next = std::sync::atomic::AtomicUsize::new(0);
363 std::thread::scope(|scope| {
364 for _ in 0..threads {
365 scope.spawn(|| loop {
366 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
367 let Some(k) = keys.get(i) else { break };
368 let _ = self.get(k);
369 });
370 }
371 });
372 }
373
374 fn get_if_materialized(&self, internal: &str) -> Option<&PoolClass> {
376 match self.classes.get(internal)? {
377 ClassEntry::Eager(pc) => Some(pc),
378 ClassEntry::Lazy { pc, .. } => pc.get(),
379 }
380 }
381
382 pub fn has_name(&self, internal: &str) -> bool {
384 self.classes.contains_key(internal)
385 }
386
387 pub fn dex(&self, idx: usize) -> Option<std::sync::Arc<DexFile>> {
390 self.dexes.get(idx).cloned()
391 }
392
393 pub fn dex_count(&self) -> usize {
394 self.dexes.len()
395 }
396
397 pub fn class_names(&self) -> impl Iterator<Item = &str> {
398 self.order.iter().map(|s| s.as_str())
399 }
400
401 pub fn is_subtype(&self, sub: &str, sup: &str) -> bool {
404 if sub == sup {
405 return true;
406 }
407 if let (Some(sub_elem), Some(sup_elem)) = (array_elem(sub), array_elem(sup)) {
409 if sub_elem.starts_with('L')
410 && sup_elem.starts_with('L')
411 && sub_elem.ends_with(';')
412 && sup_elem.ends_with(';')
413 {
414 return self.is_subtype(
415 &sub_elem[1..sub_elem.len() - 1],
416 &sup_elem[1..sup_elem.len() - 1],
417 );
418 }
419 return sub_elem == sup_elem;
420 }
421 let mut cur = self.get(sub);
422 let mut hops = 0;
423 while let Some(c) = cur {
424 hops += 1;
425 if hops > 64 {
426 return false;
427 }
428 for i in &c.interfaces {
429 if i == sup || self.is_subtype(i, sup) {
430 return true;
431 }
432 }
433 match &c.super_name {
434 Some(s) if s == sup => return true,
435 Some(s) if s != "java/lang/Object" => {
436 cur = self.get(s);
437 }
438 _ => return false,
439 }
440 }
441 false
442 }
443}
444
445impl Default for DexPool {
446 fn default() -> Self {
447 Self::new()
448 }
449}
450
451fn array_elem(desc: &str) -> Option<&str> {
452 desc.strip_prefix('[')
453}
454
455fn resolve_static_value(v: &EncodedValue, dex: &DexFile) -> StaticValue {
456 match v {
457 EncodedValue::Byte(x) => StaticValue::Int(*x as i64),
458 EncodedValue::Short(x) => StaticValue::Int(*x as i64),
459 EncodedValue::Char(x) => StaticValue::Int(*x as i64),
460 EncodedValue::Int(x) => StaticValue::Int(*x as i64),
461 EncodedValue::Long(x) => StaticValue::Int(*x),
462 EncodedValue::Float(x) => StaticValue::Float(*x),
463 EncodedValue::Double(x) => StaticValue::Double(*x),
464 EncodedValue::String(i) => StaticValue::Str(dex.string(*i).to_string()),
465 EncodedValue::Type(i) => StaticValue::Type(dex.class_name(*i)),
466 EncodedValue::Boolean(b) => StaticValue::Boolean(*b),
467 EncodedValue::Null => StaticValue::Null,
468 EncodedValue::Enum(i) | EncodedValue::Field(i) => {
469 let f = dex.field(*i);
470 StaticValue::Field(
471 dex.class_name(f.class_idx),
472 dex.string(f.name_idx).to_string(),
473 )
474 }
475 _ => StaticValue::Other,
476 }
477}
478
479fn pool_class_of(dex: &DexFile, raw: &[u8], cd: &ClassDef, dex_idx: usize) -> PoolClass {
480 let name = dex.class_name(cd.class_idx);
481 let super_name = if cd.superclass_idx == ddc_dex::NO_INDEX {
482 None
483 } else {
484 let s = dex.class_name(cd.superclass_idx);
485 if s.is_empty() || s == "java/lang/Object" {
486 None
487 } else {
488 Some(s)
489 }
490 };
491 let interfaces = dex
492 .interfaces_of(cd)
493 .into_iter()
494 .map(|t| dex.class_name(t))
495 .collect();
496 let source_file = if cd.source_file_idx == ddc_dex::NO_INDEX {
497 None
498 } else {
499 Some(dex.string(cd.source_file_idx).to_string())
500 };
501
502 let data = dex.class_data(cd);
503 let mk_field = |ef: &ddc_dex::EncodedField, is_static: bool| {
504 let f = dex.field(ef.field_idx);
505 PoolField {
506 name: dex.string(f.name_idx).to_string(),
507 desc: dex.type_name(f.type_idx).to_string(),
508 access: ef.access_flags,
509 is_static,
510 }
511 };
512 let static_fields: Vec<PoolField> = data
513 .static_fields
514 .iter()
515 .map(|f| mk_field(f, true))
516 .collect();
517 let instance_fields: Vec<PoolField> = data
518 .instance_fields
519 .iter()
520 .map(|f| mk_field(f, false))
521 .collect();
522 let mk_method = |em: &ddc_dex::EncodedMethod| {
523 let m = dex.method(em.method_idx);
524 let (code_off, debug_info_off) = match dex.debug_info_off_at(em.code_off) {
525 Some(d) => (em.code_off, d),
526 None => (0, 0),
527 };
528 PoolMethod {
529 name: dex.string_arc(m.name_idx),
530 desc: dex.proto_desc(m.proto_idx),
531 access: em.access_flags,
532 code_off,
533 debug_info_off,
534 dex_idx,
535 }
536 };
537 let direct_methods: Vec<PoolMethod> = data.direct_methods.iter().map(&mk_method).collect();
538 let virtual_methods: Vec<PoolMethod> = data.virtual_methods.iter().map(&mk_method).collect();
539
540 let static_values: Vec<StaticValue> = dex
541 .static_values(cd.static_values_off)
542 .into_iter()
543 .map(|v| resolve_static_value(&v, dex))
544 .collect();
545 let anns = if cd.annotations_off != 0 {
546 annotations::read_class_annotations(raw, cd.annotations_off)
547 } else {
548 Vec::new()
549 };
550 let raw = annotations::nesting_from(&anns, &|t: u32| dex.type_name(t).to_string());
551 let nesting = ResolvedNesting {
552 enclosing_class: raw.enclosing_class.map(|t| dex.class_name(t)),
553 enclosing_method: raw.enclosing_method.map(|m| {
554 let mid = dex.method(m);
555 (
556 dex.class_name(mid.class_idx),
557 dex.string(mid.name_idx).to_string(),
558 )
559 }),
560 member_classes: raw
561 .member_classes
562 .into_iter()
563 .map(|t| dex.class_name(t))
564 .collect(),
565 };
566
567 PoolClass {
568 name,
569 access: cd.access_flags,
570 super_name,
571 interfaces,
572 source_file,
573 static_fields,
574 instance_fields,
575 direct_methods,
576 virtual_methods,
577 static_values,
578 nesting,
579 dex_idx,
580 }
581}
582
583pub fn desc_type(desc: &str) -> JavaType {
585 parse_field_descriptor(desc).unwrap_or(JavaType::Object("java/lang/Object".into()))
586}
587
588pub fn find_outer_name(pool: &DexPool, internal: &str) -> Option<String> {
591 if let Some(pc) = pool.get_if_materialized(internal) {
596 if let Some(enc) = &pc.nesting.enclosing_class {
597 return Some(enc.clone());
598 }
599 }
600 let mut rest = internal;
601 while let Some(d) = rest.rfind('$') {
602 let cand = &rest[..d];
603 if pool.has_name(cand) {
604 return Some(cand.to_string());
605 }
606 rest = cand;
607 }
608 None
609}
610
611fn clean_member_tail(rest: &str) -> bool {
614 if rest.starts_with('-') {
615 return false;
616 }
617 let tail = rest.rsplit('$').next().unwrap_or(rest);
618 if tail.is_empty() || tail.starts_with(|c: char| c.is_ascii_digit()) {
619 return false;
620 }
621 true
622}
623
624impl DexPool {
627 pub fn arm_retirement(&self) {
630 self.retire_armed
631 .store(true, std::sync::atomic::Ordering::Release);
632 let mut counts = self.retire_counts.lock().unwrap();
633 for c in counts.iter_mut() {
634 *c = 0;
635 }
636 for entry in self.classes.values() {
637 let di = match entry {
638 ClassEntry::Eager(pc) => pc.dex_idx,
639 ClassEntry::Lazy { at, .. } => at.0,
640 };
641 if let Some(c) = counts.get_mut(di) {
642 *c += 1;
643 }
644 }
645 }
646
647 pub fn report_class_done(&self, class_name: &str) -> Option<usize> {
650 if !self.retire_armed.load(std::sync::atomic::Ordering::Acquire) {
651 return None;
652 }
653 let entry = self.classes.get(class_name)?;
654 let di = match entry {
655 ClassEntry::Eager(pc) => pc.dex_idx,
656 ClassEntry::Lazy { at, .. } => at.0,
657 };
658 let mut counts = self.retire_counts.lock().unwrap();
659 match counts.get_mut(di) {
660 Some(c) => {
661 *c = c.saturating_sub(1);
662 if *c == 0 {
663 Some(di)
664 } else {
665 None
666 }
667 }
668 None => None,
669 }
670 }
671
672 pub fn release_images(&self, indexes: &[usize]) {
676 let mut counts = self.retire_counts.lock().unwrap();
677 for &i in indexes {
678 if let Some(dex) = self.dexes.get(i) {
679 dex.mark_released();
683 }
684 if let Some(c) = counts.get_mut(i) {
685 *c = u64::MAX; }
687 }
688 }
689}
690
691pub fn top_level_classes(pool: &DexPool) -> Vec<String> {
692 pool.class_names()
693 .filter(|name| match pool.outer_of(name) {
694 None => true,
695 Some(outer) => {
696 if pool.get(outer).is_none() {
697 return true;
698 }
699 let rest = name
706 .strip_prefix(outer)
707 .and_then(|t| t.strip_prefix('$'))
708 .unwrap_or("");
709 !clean_member_tail(rest)
710 }
711 })
712 .map(|n| n.to_string())
713 .collect()
714}
715
716pub fn case_rename_map(pool: &DexPool) -> std::collections::HashMap<String, String> {
723 use std::collections::HashMap;
724 let mut groups: HashMap<String, Vec<String>> = HashMap::new();
725 for t in top_level_classes(pool) {
726 groups.entry(t.to_lowercase()).or_default().push(t);
727 }
728 let folds: std::collections::HashSet<String> = groups.keys().cloned().collect();
729 let mut map = HashMap::new();
730 for (_, mut members) in groups {
731 members.sort();
732 for (i, m) in members.iter().enumerate() {
733 if i == 0 {
734 map.insert(m.clone(), m.clone());
735 continue;
736 }
737 let cut = m.rfind('/').map(|x| x + 1).unwrap_or(0);
740 let mut n = i + 1;
741 loop {
742 let cand = format!("{}{}_{}", &m[..cut], &m[cut..], n);
743 let fold = cand.to_lowercase();
744 if !folds.contains(&fold) {
745 map.insert(m.clone(), cand);
746 break;
747 }
748 n += 1;
749 }
750 }
751 }
752 map
753}
754
755pub fn install_case_renames(pool: &DexPool) {
757 jdc_core::rename::set_class_renames(case_rename_map(pool));
758}
759
760pub use jdc_core::rename::apply_class_rename;