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: String,
77 pub desc: String,
79 pub access: u32,
80 pub code_off: u32,
81 pub debug_info_off: u32,
82 pub dex_idx: usize,
84}
85
86impl PoolMethod {
87 pub fn parsed_desc(&self) -> Option<MethodDescriptor> {
88 parse_method_descriptor(&self.desc)
89 }
90 pub fn is_static(&self) -> bool {
91 self.access & access::ACC_STATIC != 0
92 }
93 pub fn is_abstract_or_native(&self) -> bool {
94 self.access & (access::ACC_ABSTRACT | access::ACC_NATIVE) != 0
95 }
96}
97
98enum ClassEntry {
101 Eager(PoolClass),
102 Lazy {
103 at: (usize, usize),
104 pc: std::sync::OnceLock<PoolClass>,
105 },
106}
107
108#[derive(Debug, Clone)]
111pub struct PoolClass {
112 pub name: String,
113 pub access: u32,
114 pub super_name: Option<String>,
116 pub interfaces: Vec<String>,
117 pub source_file: Option<String>,
118 pub static_fields: Vec<PoolField>,
119 pub instance_fields: Vec<PoolField>,
120 pub direct_methods: Vec<PoolMethod>,
121 pub virtual_methods: Vec<PoolMethod>,
122 pub static_values: Vec<StaticValue>,
124 pub nesting: ResolvedNesting,
126 pub dex_idx: usize,
128}
129
130#[derive(Debug, Clone, Default)]
132pub struct ResolvedNesting {
133 pub enclosing_class: Option<String>,
134 pub enclosing_method: Option<(String, String)>,
136 pub member_classes: Vec<String>,
137}
138
139impl PoolClass {
140 pub fn is_interface(&self) -> bool {
141 self.access & access::ACC_INTERFACE != 0
142 }
143
144 pub fn is_enum(&self) -> bool {
145 self.access & access::ACC_ENUM != 0
146 }
147
148 pub fn is_synthetic(&self) -> bool {
149 self.access & access::ACC_SYNTHETIC != 0
150 }
151
152 pub fn is_static_nested(&self) -> bool {
153 self.access & access::ACC_STATIC != 0
154 }
155
156 pub fn all_methods(&self) -> impl Iterator<Item = &PoolMethod> {
158 self.direct_methods
159 .iter()
160 .chain(self.virtual_methods.iter())
161 }
162
163 pub fn find_method(&self, name: &str, desc: &str) -> Option<&PoolMethod> {
165 self.all_methods()
166 .find(|m| m.name == name && m.desc == desc)
167 }
168
169 pub fn ctors_by_arity(&self, arity: usize) -> Vec<&PoolMethod> {
171 self.all_methods()
172 .filter(|m| m.name == "<init>")
173 .filter(|m| {
174 m.parsed_desc()
175 .map(|d| d.args.len() == arity)
176 .unwrap_or(false)
177 })
178 .collect()
179 }
180
181 pub fn field_flags_of(&self, name: &str) -> Option<u32> {
182 self.static_fields
183 .iter()
184 .chain(self.instance_fields.iter())
185 .find(|f| f.name == name)
186 .map(|f| f.access)
187 }
188}
189
190pub struct DexPool {
192 dexes: Vec<std::sync::Arc<DexFile>>,
198 pub dex_labels: Vec<String>,
201 classes: HashMap<String, ClassEntry>,
206 pub order: Vec<String>,
208 retire_counts: std::sync::Mutex<Vec<u64>>,
210 retire_armed: std::sync::atomic::AtomicBool,
211 outers: std::sync::OnceLock<HashMap<String, Option<String>>>,
213 children: std::sync::OnceLock<HashMap<String, Vec<String>>>,
215}
216
217impl DexPool {
218 pub fn new() -> Self {
219 DexPool {
220 dexes: Vec::new(),
221 retire_armed: std::sync::atomic::AtomicBool::new(false),
222 dex_labels: Vec::new(),
223 classes: HashMap::new(),
224 order: Vec::new(),
225 retire_counts: std::sync::Mutex::new(Vec::new()),
226 outers: std::sync::OnceLock::new(),
227 children: std::sync::OnceLock::new(),
228 }
229 }
230
231 fn outer_map(&self) -> &HashMap<String, Option<String>> {
233 self.outers.get_or_init(|| {
234 self.order
235 .iter()
236 .map(|n| (n.clone(), find_outer_name(self, n)))
237 .collect()
238 })
239 }
240
241 pub fn outer_of(&self, name: &str) -> Option<&str> {
243 self.outer_map().get(name).and_then(|o| o.as_deref())
244 }
245
246 pub fn children_of(&self, internal: &str) -> &[String] {
250 if self.children.get().is_none() {
251 let idx: HashMap<String, Vec<String>> = HashMap::new();
252 let _ = self.children.set(idx);
253 let mut idx: HashMap<String, Vec<String>> = HashMap::new();
254 for name in &self.order {
255 if let Some(outer) = self.outer_of(name).map(str::to_string) {
256 idx.entry(outer).or_default().push(name.clone());
257 }
258 }
259 let _ = self.children.set(idx);
260 }
261 self.children
262 .get()
263 .and_then(|m| m.get(internal))
264 .map(|v| v.as_slice())
265 .unwrap_or(&[])
266 }
267
268 pub fn add_dex(&mut self, dex: DexFile) -> usize {
272 let dex_idx = self.dexes.len();
273 for cd in &dex.class_defs {
276 let name = dex.class_name(cd.class_idx);
277 if self.classes.contains_key(&name) {
278 continue;
279 }
280 let pc = pool_class_of(&dex, dex.raw(), cd, dex_idx);
281 self.classes.insert(name.clone(), ClassEntry::Eager(pc));
282 self.order.push(name);
283 }
284 self.dexes.push(std::sync::Arc::new(dex));
285 self.retire_counts.lock().unwrap().push(0);
286 self.dex_labels.push(format!("dex {}", dex_idx));
287 dex_idx
288 }
289
290 pub fn add_dex_lazy(&mut self, dex: DexFile) -> usize {
294 let dex_idx = self.dexes.len();
295 for (ci, cd) in dex.class_defs.iter().enumerate() {
296 let name = dex.class_name(cd.class_idx);
297 if self.classes.contains_key(&name) {
298 continue;
299 }
300 self.classes.insert(
301 name.clone(),
302 ClassEntry::Lazy {
303 at: (dex_idx, ci),
304 pc: std::sync::OnceLock::new(),
305 },
306 );
307 self.order.push(name);
308 }
309 self.dexes.push(std::sync::Arc::new(dex));
310 self.retire_counts.lock().unwrap().push(0);
311 self.dex_labels.push(format!("dex {}", dex_idx));
312 dex_idx
313 }
314
315 pub fn set_dex_label(&mut self, idx: usize, label: impl Into<String>) {
317 if let Some(slot) = self.dex_labels.get_mut(idx) {
318 *slot = label.into();
319 }
320 }
321
322 pub fn len(&self) -> usize {
323 self.classes.len()
324 }
325
326 pub fn is_empty(&self) -> bool {
327 self.classes.is_empty()
328 }
329
330 pub fn get(&self, internal: &str) -> Option<&PoolClass> {
331 match self.classes.get(internal)? {
332 ClassEntry::Eager(pc) => Some(pc),
333 ClassEntry::Lazy { at, pc } => {
334 if let Some(built) = pc.get() {
335 return Some(built);
336 }
337 let (di, ci) = *at;
338 let dex = self.dexes.get(di)?;
339 let cd = dex.class_defs.get(ci)?;
340 let built = pool_class_of(dex, dex.raw(), cd, di);
341 Some(pc.get_or_init(move || built))
342 }
343 }
344 }
345
346 pub fn materialize_all(&self, threads: usize) {
352 let keys: Vec<String> = self
353 .classes
354 .iter()
355 .filter(|(_, e)| matches!(e, ClassEntry::Lazy { .. }))
356 .map(|(k, _)| k.clone())
357 .collect();
358 let threads = threads.max(1);
359 let next = std::sync::atomic::AtomicUsize::new(0);
360 std::thread::scope(|scope| {
361 for _ in 0..threads {
362 scope.spawn(|| loop {
363 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
364 let Some(k) = keys.get(i) else { break };
365 let _ = self.get(k);
366 });
367 }
368 });
369 }
370
371 fn get_if_materialized(&self, internal: &str) -> Option<&PoolClass> {
373 match self.classes.get(internal)? {
374 ClassEntry::Eager(pc) => Some(pc),
375 ClassEntry::Lazy { pc, .. } => pc.get(),
376 }
377 }
378
379 pub fn has_name(&self, internal: &str) -> bool {
381 self.classes.contains_key(internal)
382 }
383
384 pub fn dex(&self, idx: usize) -> Option<std::sync::Arc<DexFile>> {
387 self.dexes.get(idx).cloned()
388 }
389
390 pub fn dex_count(&self) -> usize {
391 self.dexes.len()
392 }
393
394 pub fn class_names(&self) -> impl Iterator<Item = &str> {
395 self.order.iter().map(|s| s.as_str())
396 }
397
398 pub fn is_subtype(&self, sub: &str, sup: &str) -> bool {
401 if sub == sup {
402 return true;
403 }
404 if let (Some(sub_elem), Some(sup_elem)) = (array_elem(sub), array_elem(sup)) {
406 if sub_elem.starts_with('L')
407 && sup_elem.starts_with('L')
408 && sub_elem.ends_with(';')
409 && sup_elem.ends_with(';')
410 {
411 return self.is_subtype(
412 &sub_elem[1..sub_elem.len() - 1],
413 &sup_elem[1..sup_elem.len() - 1],
414 );
415 }
416 return sub_elem == sup_elem;
417 }
418 let mut cur = self.get(sub);
419 let mut hops = 0;
420 while let Some(c) = cur {
421 hops += 1;
422 if hops > 64 {
423 return false;
424 }
425 for i in &c.interfaces {
426 if i == sup || self.is_subtype(i, sup) {
427 return true;
428 }
429 }
430 match &c.super_name {
431 Some(s) if s == sup => return true,
432 Some(s) if s != "java/lang/Object" => {
433 cur = self.get(s);
434 }
435 _ => return false,
436 }
437 }
438 false
439 }
440}
441
442impl Default for DexPool {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448fn array_elem(desc: &str) -> Option<&str> {
449 if desc.starts_with('[') {
450 Some(&desc[1..])
451 } else {
452 None
453 }
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 proto = dex.proto(m.proto_idx);
526 let params: Vec<&str> = dex
527 .proto_params(m.proto_idx)
528 .into_iter()
529 .map(|t| dex.type_name(t))
530 .collect();
531 let ret = dex.type_name(proto.return_type_idx);
532 let desc = format!("({}){}", params.join(""), ret);
533 let (code_off, debug_info_off) = match dex.debug_info_off_at(em.code_off) {
534 Some(d) => (em.code_off, d),
535 None => (0, 0),
536 };
537 PoolMethod {
538 name: dex.string(m.name_idx).to_string(),
539 desc,
540 access: em.access_flags,
541 code_off,
542 debug_info_off,
543 dex_idx,
544 }
545 };
546 let direct_methods: Vec<PoolMethod> = data.direct_methods.iter().map(&mk_method).collect();
547 let virtual_methods: Vec<PoolMethod> = data.virtual_methods.iter().map(&mk_method).collect();
548
549 let static_values: Vec<StaticValue> = dex
550 .static_values(cd.static_values_off)
551 .into_iter()
552 .map(|v| resolve_static_value(&v, dex))
553 .collect();
554 let anns = if cd.annotations_off != 0 {
555 annotations::read_class_annotations(raw, cd.annotations_off)
556 } else {
557 Vec::new()
558 };
559 let raw = annotations::nesting_from(&anns, &|t: u32| dex.type_name(t).to_string());
560 let nesting = ResolvedNesting {
561 enclosing_class: raw.enclosing_class.map(|t| dex.class_name(t)),
562 enclosing_method: raw.enclosing_method.map(|m| {
563 let mid = dex.method(m);
564 (
565 dex.class_name(mid.class_idx),
566 dex.string(mid.name_idx).to_string(),
567 )
568 }),
569 member_classes: raw
570 .member_classes
571 .into_iter()
572 .map(|t| dex.class_name(t))
573 .collect(),
574 };
575
576 PoolClass {
577 name,
578 access: cd.access_flags,
579 super_name,
580 interfaces,
581 source_file,
582 static_fields,
583 instance_fields,
584 direct_methods,
585 virtual_methods,
586 static_values,
587 nesting,
588 dex_idx,
589 }
590}
591
592pub fn desc_type(desc: &str) -> JavaType {
594 parse_field_descriptor(desc).unwrap_or(JavaType::Object("java/lang/Object".into()))
595}
596
597pub fn find_outer_name(pool: &DexPool, internal: &str) -> Option<String> {
600 if let Some(pc) = pool.get_if_materialized(internal) {
605 if let Some(enc) = &pc.nesting.enclosing_class {
606 return Some(enc.clone());
607 }
608 }
609 let mut rest = internal;
610 while let Some(d) = rest.rfind('$') {
611 let cand = &rest[..d];
612 if pool.has_name(cand) {
613 return Some(cand.to_string());
614 }
615 rest = cand;
616 }
617 None
618}
619
620fn clean_member_tail(rest: &str) -> bool {
623 if rest.starts_with('-') {
624 return false;
625 }
626 let tail = rest.rsplit('$').next().unwrap_or(rest);
627 if tail.is_empty() || tail.starts_with(|c: char| c.is_ascii_digit()) {
628 return false;
629 }
630 true
631}
632
633impl DexPool {
636 pub fn arm_retirement(&self) {
639 self.retire_armed
640 .store(true, std::sync::atomic::Ordering::Release);
641 let mut counts = self.retire_counts.lock().unwrap();
642 for c in counts.iter_mut() {
643 *c = 0;
644 }
645 for entry in self.classes.values() {
646 let di = match entry {
647 ClassEntry::Eager(pc) => pc.dex_idx,
648 ClassEntry::Lazy { at, .. } => at.0,
649 };
650 if let Some(c) = counts.get_mut(di) {
651 *c += 1;
652 }
653 }
654 }
655
656 pub fn report_class_done(&self, class_name: &str) -> Option<usize> {
659 if !self.retire_armed.load(std::sync::atomic::Ordering::Acquire) {
660 return None;
661 }
662 let entry = self.classes.get(class_name)?;
663 let di = match entry {
664 ClassEntry::Eager(pc) => pc.dex_idx,
665 ClassEntry::Lazy { at, .. } => at.0,
666 };
667 let mut counts = self.retire_counts.lock().unwrap();
668 match counts.get_mut(di) {
669 Some(c) => {
670 *c = c.saturating_sub(1);
671 if *c == 0 {
672 Some(di)
673 } else {
674 None
675 }
676 }
677 None => None,
678 }
679 }
680
681 pub fn release_images(&self, indexes: &[usize]) {
685 let mut counts = self.retire_counts.lock().unwrap();
686 for &i in indexes {
687 if let Some(dex) = self.dexes.get(i) {
688 dex.mark_released();
692 }
693 if let Some(c) = counts.get_mut(i) {
694 *c = u64::MAX; }
696 }
697 }
698}
699
700pub fn top_level_classes(pool: &DexPool) -> Vec<String> {
701 pool.class_names()
702 .filter(|name| match pool.outer_of(name) {
703 None => true,
704 Some(outer) => {
705 if pool.get(&outer).is_none() {
706 return true;
707 }
708 let rest = name
715 .strip_prefix(&outer[..])
716 .and_then(|t| t.strip_prefix('$'))
717 .unwrap_or("");
718 !clean_member_tail(rest)
719 }
720 })
721 .map(|n| n.to_string())
722 .collect()
723}