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 std::collections::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 children: std::sync::OnceLock<HashMap<String, Vec<String>>>,
219}
220
221impl DexPool {
222 pub fn new() -> Self {
223 DexPool {
224 dexes: Vec::new(),
225 retire_armed: std::sync::atomic::AtomicBool::new(false),
226 dex_labels: Vec::new(),
227 classes: HashMap::new(),
228 order: Vec::new(),
229 retire_counts: std::sync::Mutex::new(Vec::new()),
230 outers: std::sync::OnceLock::new(),
231 children: std::sync::OnceLock::new(),
232 }
233 }
234
235 fn outer_map(&self) -> &HashMap<String, Option<String>> {
237 self.outers.get_or_init(|| {
238 self.order
239 .iter()
240 .map(|n| (n.clone(), find_outer_name(self, n)))
241 .collect()
242 })
243 }
244
245 pub fn outer_of(&self, name: &str) -> Option<&str> {
247 self.outer_map().get(name).and_then(|o| o.as_deref())
248 }
249
250 pub fn children_of(&self, internal: &str) -> &[String] {
254 if self.children.get().is_none() {
255 let idx: HashMap<String, Vec<String>> = HashMap::new();
256 let _ = self.children.set(idx);
257 let mut idx: HashMap<String, Vec<String>> = HashMap::new();
258 for name in &self.order {
259 if let Some(outer) = self.outer_of(name).map(str::to_string) {
260 idx.entry(outer).or_default().push(name.clone());
261 }
262 }
263 let _ = self.children.set(idx);
264 }
265 self.children
266 .get()
267 .and_then(|m| m.get(internal))
268 .map(|v| v.as_slice())
269 .unwrap_or(&[])
270 }
271
272 pub fn add_dex(&mut self, dex: DexFile) -> usize {
276 let dex_idx = self.dexes.len();
277 for cd in &dex.class_defs {
280 let name = dex.class_name(cd.class_idx);
281 if self.classes.contains_key(&name) {
282 continue;
283 }
284 let pc = pool_class_of(&dex, dex.raw(), cd, dex_idx);
285 self.classes.insert(name.clone(), ClassEntry::Eager(pc));
286 self.order.push(name);
287 }
288 self.dexes.push(std::sync::Arc::new(dex));
289 self.retire_counts.lock().unwrap().push(0);
290 self.dex_labels.push(format!("dex {}", dex_idx));
291 dex_idx
292 }
293
294 pub fn add_dex_lazy(&mut self, dex: DexFile) -> usize {
298 let dex_idx = self.dexes.len();
299 for (ci, cd) in dex.class_defs.iter().enumerate() {
300 let name = dex.class_name(cd.class_idx);
301 if self.classes.contains_key(&name) {
302 continue;
303 }
304 self.classes.insert(
305 name.clone(),
306 ClassEntry::Lazy {
307 at: (dex_idx, ci),
308 pc: std::sync::OnceLock::new(),
309 },
310 );
311 self.order.push(name);
312 }
313 self.dexes.push(std::sync::Arc::new(dex));
314 self.retire_counts.lock().unwrap().push(0);
315 self.dex_labels.push(format!("dex {}", dex_idx));
316 dex_idx
317 }
318
319 pub fn set_dex_label(&mut self, idx: usize, label: impl Into<String>) {
321 if let Some(slot) = self.dex_labels.get_mut(idx) {
322 *slot = label.into();
323 }
324 }
325
326 pub fn len(&self) -> usize {
327 self.classes.len()
328 }
329
330 pub fn is_empty(&self) -> bool {
331 self.classes.is_empty()
332 }
333
334 pub fn get(&self, internal: &str) -> Option<&PoolClass> {
335 match self.classes.get(internal)? {
336 ClassEntry::Eager(pc) => Some(pc),
337 ClassEntry::Lazy { at, pc } => {
338 if let Some(built) = pc.get() {
339 return Some(built);
340 }
341 let (di, ci) = *at;
342 let dex = self.dexes.get(di)?;
343 let cd = dex.class_defs.get(ci)?;
344 let built = pool_class_of(dex, dex.raw(), cd, di);
345 Some(pc.get_or_init(move || built))
346 }
347 }
348 }
349
350 pub fn materialize_all(&self, threads: usize) {
356 let keys: Vec<String> = self
357 .classes
358 .iter()
359 .filter(|(_, e)| matches!(e, ClassEntry::Lazy { .. }))
360 .map(|(k, _)| k.clone())
361 .collect();
362 let threads = threads.max(1);
363 let next = std::sync::atomic::AtomicUsize::new(0);
364 std::thread::scope(|scope| {
365 for _ in 0..threads {
366 scope.spawn(|| loop {
367 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
368 let Some(k) = keys.get(i) else { break };
369 let _ = self.get(k);
370 });
371 }
372 });
373 }
374
375 fn get_if_materialized(&self, internal: &str) -> Option<&PoolClass> {
377 match self.classes.get(internal)? {
378 ClassEntry::Eager(pc) => Some(pc),
379 ClassEntry::Lazy { pc, .. } => pc.get(),
380 }
381 }
382
383 pub fn has_name(&self, internal: &str) -> bool {
385 self.classes.contains_key(internal)
386 }
387
388 pub fn dex(&self, idx: usize) -> Option<std::sync::Arc<DexFile>> {
391 self.dexes.get(idx).cloned()
392 }
393
394 pub fn dex_count(&self) -> usize {
395 self.dexes.len()
396 }
397
398 pub fn class_names(&self) -> impl Iterator<Item = &str> {
399 self.order.iter().map(|s| s.as_str())
400 }
401
402 pub fn is_subtype(&self, sub: &str, sup: &str) -> bool {
405 if sub == sup {
406 return true;
407 }
408 if let (Some(sub_elem), Some(sup_elem)) = (array_elem(sub), array_elem(sup)) {
410 if sub_elem.starts_with('L')
411 && sup_elem.starts_with('L')
412 && sub_elem.ends_with(';')
413 && sup_elem.ends_with(';')
414 {
415 return self.is_subtype(
416 &sub_elem[1..sub_elem.len() - 1],
417 &sup_elem[1..sup_elem.len() - 1],
418 );
419 }
420 return sub_elem == sup_elem;
421 }
422 let mut cur = self.get(sub);
423 let mut hops = 0;
424 while let Some(c) = cur {
425 hops += 1;
426 if hops > 64 {
427 return false;
428 }
429 for i in &c.interfaces {
430 if i == sup || self.is_subtype(i, sup) {
431 return true;
432 }
433 }
434 match &c.super_name {
435 Some(s) if s == sup => return true,
436 Some(s) if s != "java/lang/Object" => {
437 cur = self.get(s);
438 }
439 _ => return false,
440 }
441 }
442 false
443 }
444}
445
446impl Default for DexPool {
447 fn default() -> Self {
448 Self::new()
449 }
450}
451
452fn array_elem(desc: &str) -> Option<&str> {
453 desc.strip_prefix('[')
454}
455
456fn resolve_static_value(v: &EncodedValue, dex: &DexFile) -> StaticValue {
457 match v {
458 EncodedValue::Byte(x) => StaticValue::Int(*x as i64),
459 EncodedValue::Short(x) => StaticValue::Int(*x as i64),
460 EncodedValue::Char(x) => StaticValue::Int(*x as i64),
461 EncodedValue::Int(x) => StaticValue::Int(*x as i64),
462 EncodedValue::Long(x) => StaticValue::Int(*x),
463 EncodedValue::Float(x) => StaticValue::Float(*x),
464 EncodedValue::Double(x) => StaticValue::Double(*x),
465 EncodedValue::String(i) => StaticValue::Str(dex.string(*i).to_string()),
466 EncodedValue::Type(i) => StaticValue::Type(dex.class_name(*i)),
467 EncodedValue::Boolean(b) => StaticValue::Boolean(*b),
468 EncodedValue::Null => StaticValue::Null,
469 EncodedValue::Enum(i) | EncodedValue::Field(i) => {
470 let f = dex.field(*i);
471 StaticValue::Field(
472 dex.class_name(f.class_idx),
473 dex.string(f.name_idx).to_string(),
474 )
475 }
476 _ => StaticValue::Other,
477 }
478}
479
480fn pool_class_of(dex: &DexFile, raw: &[u8], cd: &ClassDef, dex_idx: usize) -> PoolClass {
481 let name = dex.class_name(cd.class_idx);
482 let super_name = if cd.superclass_idx == ddc_dex::NO_INDEX {
483 None
484 } else {
485 let s = dex.class_name(cd.superclass_idx);
486 if s.is_empty() || s == "java/lang/Object" {
487 None
488 } else {
489 Some(s)
490 }
491 };
492 let interfaces = dex
493 .interfaces_of(cd)
494 .into_iter()
495 .map(|t| dex.class_name(t))
496 .collect();
497 let source_file = if cd.source_file_idx == ddc_dex::NO_INDEX {
498 None
499 } else {
500 Some(dex.string(cd.source_file_idx).to_string())
501 };
502
503 let data = dex.class_data(cd);
504 let mk_field = |ef: &ddc_dex::EncodedField, is_static: bool| {
505 let f = dex.field(ef.field_idx);
506 PoolField {
507 name: dex.string(f.name_idx).to_string(),
508 desc: dex.type_name(f.type_idx).to_string(),
509 access: ef.access_flags,
510 is_static,
511 }
512 };
513 let static_fields: Vec<PoolField> = data
514 .static_fields
515 .iter()
516 .map(|f| mk_field(f, true))
517 .collect();
518 let instance_fields: Vec<PoolField> = data
519 .instance_fields
520 .iter()
521 .map(|f| mk_field(f, false))
522 .collect();
523 let mk_method = |em: &ddc_dex::EncodedMethod| {
524 let m = dex.method(em.method_idx);
525 let (code_off, debug_info_off) = match dex.debug_info_off_at(em.code_off) {
526 Some(d) => (em.code_off, d),
527 None => (0, 0),
528 };
529 PoolMethod {
530 name: dex.string_arc(m.name_idx),
531 desc: dex.proto_desc(m.proto_idx),
532 access: em.access_flags,
533 code_off,
534 debug_info_off,
535 dex_idx,
536 }
537 };
538 let direct_methods: Vec<PoolMethod> = data.direct_methods.iter().map(&mk_method).collect();
539 let virtual_methods: Vec<PoolMethod> = data.virtual_methods.iter().map(&mk_method).collect();
540
541 let static_values: Vec<StaticValue> = dex
542 .static_values(cd.static_values_off)
543 .into_iter()
544 .map(|v| resolve_static_value(&v, dex))
545 .collect();
546 let anns = if cd.annotations_off != 0 {
547 annotations::read_class_annotations(raw, cd.annotations_off)
548 } else {
549 Vec::new()
550 };
551 let raw = annotations::nesting_from(&anns, &|t: u32| dex.type_name(t).to_string());
552 let nesting = ResolvedNesting {
553 enclosing_class: raw.enclosing_class.map(|t| dex.class_name(t)),
554 enclosing_method: raw.enclosing_method.map(|m| {
555 let mid = dex.method(m);
556 (
557 dex.class_name(mid.class_idx),
558 dex.string(mid.name_idx).to_string(),
559 )
560 }),
561 member_classes: raw
562 .member_classes
563 .into_iter()
564 .map(|t| dex.class_name(t))
565 .collect(),
566 };
567
568 PoolClass {
569 name,
570 access: cd.access_flags,
571 super_name,
572 interfaces,
573 source_file,
574 static_fields,
575 instance_fields,
576 direct_methods,
577 virtual_methods,
578 static_values,
579 nesting,
580 dex_idx,
581 }
582}
583
584pub fn desc_type(desc: &str) -> JavaType {
586 parse_field_descriptor(desc).unwrap_or(JavaType::Object("java/lang/Object".into()))
587}
588
589pub fn find_outer_name(pool: &DexPool, internal: &str) -> Option<String> {
592 if let Some(pc) = pool.get_if_materialized(internal) {
597 if let Some(enc) = &pc.nesting.enclosing_class {
598 return Some(enc.clone());
599 }
600 }
601 let mut rest = internal;
602 while let Some(d) = rest.rfind('$') {
603 let cand = &rest[..d];
604 if pool.has_name(cand) {
605 return Some(cand.to_string());
606 }
607 rest = cand;
608 }
609 None
610}
611
612fn clean_member_tail(rest: &str) -> bool {
615 if rest.starts_with('-') {
616 return false;
617 }
618 let tail = rest.rsplit('$').next().unwrap_or(rest);
619 if tail.is_empty() || tail.starts_with(|c: char| c.is_ascii_digit()) {
620 return false;
621 }
622 true
623}
624
625impl DexPool {
628 pub fn arm_retirement(&self) {
631 self.retire_armed
632 .store(true, std::sync::atomic::Ordering::Release);
633 let mut counts = self.retire_counts.lock().unwrap();
634 for c in counts.iter_mut() {
635 *c = 0;
636 }
637 for entry in self.classes.values() {
638 let di = match entry {
639 ClassEntry::Eager(pc) => pc.dex_idx,
640 ClassEntry::Lazy { at, .. } => at.0,
641 };
642 if let Some(c) = counts.get_mut(di) {
643 *c += 1;
644 }
645 }
646 }
647
648 pub fn report_class_done(&self, class_name: &str) -> Option<usize> {
651 if !self.retire_armed.load(std::sync::atomic::Ordering::Acquire) {
652 return None;
653 }
654 let entry = self.classes.get(class_name)?;
655 let di = match entry {
656 ClassEntry::Eager(pc) => pc.dex_idx,
657 ClassEntry::Lazy { at, .. } => at.0,
658 };
659 let mut counts = self.retire_counts.lock().unwrap();
660 match counts.get_mut(di) {
661 Some(c) => {
662 *c = c.saturating_sub(1);
663 if *c == 0 {
664 Some(di)
665 } else {
666 None
667 }
668 }
669 None => None,
670 }
671 }
672
673 pub fn release_images(&self, indexes: &[usize]) {
677 let mut counts = self.retire_counts.lock().unwrap();
678 for &i in indexes {
679 if let Some(dex) = self.dexes.get(i) {
680 dex.mark_released();
684 }
685 if let Some(c) = counts.get_mut(i) {
686 *c = u64::MAX; }
688 }
689 }
690}
691
692pub fn top_level_classes(pool: &DexPool) -> Vec<String> {
693 pool.class_names()
694 .filter(|name| match pool.outer_of(name) {
695 None => true,
696 Some(outer) => {
697 if pool.get(outer).is_none() {
698 return true;
699 }
700 let rest = name
707 .strip_prefix(outer)
708 .and_then(|t| t.strip_prefix('$'))
709 .unwrap_or("");
710 !clean_member_tail(rest)
711 }
712 })
713 .map(|n| n.to_string())
714 .collect()
715}
716
717pub fn case_rename_map(pool: &DexPool) -> std::collections::HashMap<String, String> {
724 use std::collections::HashMap;
725 let mut groups: HashMap<String, Vec<String>> = HashMap::new();
726 for t in top_level_classes(pool) {
727 groups.entry(t.to_lowercase()).or_default().push(t);
728 }
729 let folds: std::collections::HashSet<String> = groups.keys().cloned().collect();
730 let mut map = HashMap::new();
731 for (_, mut members) in groups {
732 members.sort();
733 for (i, m) in members.iter().enumerate() {
734 if i == 0 {
735 map.insert(m.clone(), m.clone());
736 continue;
737 }
738 let cut = m.rfind('/').map(|x| x + 1).unwrap_or(0);
741 let mut n = i + 1;
742 loop {
743 let cand = format!("{}{}_{}", &m[..cut], &m[cut..], n);
744 let fold = cand.to_lowercase();
745 if !folds.contains(&fold) {
746 map.insert(m.clone(), cand);
747 break;
748 }
749 n += 1;
750 }
751 }
752 }
753 map
754}
755
756pub fn install_case_renames(pool: &DexPool) {
758 jdc_core::rename::set_class_renames(case_rename_map(pool));
759}
760
761pub use jdc_core::rename::apply_class_rename;