pub mod cfg;
pub mod classdec;
pub use classdec::ClassOptions;
pub mod ctx;
pub mod lift;
pub mod method;
pub mod passes;
use std::collections::HashMap;
use ddc_dex::annotations::{self, EncodedValue};
#[derive(Debug, Clone)]
pub enum StaticValue {
Int(i64),
Float(f32),
Double(f64),
Str(String),
Type(String),
Boolean(bool),
Null,
Field(String, String),
Other,
}
use ddc_dex::{ClassDef, DexFile};
use jdc_core::types::{
parse_field_descriptor, parse_method_descriptor, JavaType, MethodDescriptor,
};
pub mod access {
pub const ACC_PUBLIC: u32 = 0x1;
pub const ACC_PRIVATE: u32 = 0x2;
pub const ACC_PROTECTED: u32 = 0x4;
pub const ACC_STATIC: u32 = 0x8;
pub const ACC_FINAL: u32 = 0x10;
pub const ACC_SYNCHRONIZED: u32 = 0x20;
pub const ACC_BRIDGE: u32 = 0x40;
pub const ACC_VARARGS: u32 = 0x80;
pub const ACC_NATIVE: u32 = 0x100;
pub const ACC_INTERFACE: u32 = 0x200;
pub const ACC_ABSTRACT: u32 = 0x400;
pub const ACC_STRICT: u32 = 0x800;
pub const ACC_SYNTHETIC: u32 = 0x1000;
pub const ACC_ANNOTATION: u32 = 0x2000;
pub const ACC_ENUM: u32 = 0x4000;
pub const ACC_CONSTRUCTOR: u32 = 0x1_0000;
pub const ACC_DECLARED_SYNCHRONIZED: u32 = 0x2_0000;
}
#[derive(Debug, Clone)]
pub struct PoolField {
pub name: String,
pub desc: String,
pub access: u32,
pub is_static: bool,
}
#[derive(Debug, Clone)]
pub struct PoolMethod {
pub name: std::sync::Arc<str>,
pub desc: std::sync::Arc<str>,
pub access: u32,
pub code_off: u32,
pub debug_info_off: u32,
pub dex_idx: usize,
}
impl PoolMethod {
pub fn parsed_desc(&self) -> Option<MethodDescriptor> {
parse_method_descriptor(&self.desc)
}
pub fn is_static(&self) -> bool {
self.access & access::ACC_STATIC != 0
}
pub fn is_abstract_or_native(&self) -> bool {
self.access & (access::ACC_ABSTRACT | access::ACC_NATIVE) != 0
}
}
enum ClassEntry {
Eager(PoolClass),
Lazy {
at: (usize, usize),
pc: std::sync::OnceLock<PoolClass>,
},
}
#[derive(Debug, Clone)]
pub struct PoolClass {
pub name: String,
pub access: u32,
pub super_name: Option<String>,
pub interfaces: Vec<String>,
pub source_file: Option<String>,
pub static_fields: Vec<PoolField>,
pub instance_fields: Vec<PoolField>,
pub direct_methods: Vec<PoolMethod>,
pub virtual_methods: Vec<PoolMethod>,
pub static_values: Vec<StaticValue>,
pub nesting: ResolvedNesting,
pub dex_idx: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ResolvedNesting {
pub enclosing_class: Option<String>,
pub enclosing_method: Option<(String, String)>,
pub member_classes: Vec<String>,
}
impl PoolClass {
pub fn is_interface(&self) -> bool {
self.access & access::ACC_INTERFACE != 0
}
pub fn is_enum(&self) -> bool {
self.access & access::ACC_ENUM != 0
}
pub fn is_synthetic(&self) -> bool {
self.access & access::ACC_SYNTHETIC != 0
}
pub fn is_static_nested(&self) -> bool {
self.access & access::ACC_STATIC != 0
}
pub fn all_methods(&self) -> impl Iterator<Item = &PoolMethod> {
self.direct_methods
.iter()
.chain(self.virtual_methods.iter())
}
pub fn find_method(&self, name: &str, desc: &str) -> Option<&PoolMethod> {
self.all_methods()
.find(|m| &*m.name == name && &*m.desc == desc)
}
pub fn ctors_by_arity(&self, arity: usize) -> Vec<&PoolMethod> {
self.all_methods()
.filter(|m| &*m.name == "<init>")
.filter(|m| {
m.parsed_desc()
.map(|d| d.args.len() == arity)
.unwrap_or(false)
})
.collect()
}
pub fn field_flags_of(&self, name: &str) -> Option<u32> {
self.static_fields
.iter()
.chain(self.instance_fields.iter())
.find(|f| f.name == name)
.map(|f| f.access)
}
}
pub struct DexPool {
dexes: Vec<std::sync::Arc<DexFile>>,
pub dex_labels: Vec<String>,
classes: HashMap<String, ClassEntry>,
pub order: Vec<String>,
retire_counts: std::sync::Mutex<Vec<u64>>,
retire_armed: std::sync::atomic::AtomicBool,
outers: std::sync::OnceLock<HashMap<String, Option<String>>>,
children: std::sync::OnceLock<HashMap<String, Vec<String>>>,
}
impl DexPool {
pub fn new() -> Self {
DexPool {
dexes: Vec::new(),
retire_armed: std::sync::atomic::AtomicBool::new(false),
dex_labels: Vec::new(),
classes: HashMap::new(),
order: Vec::new(),
retire_counts: std::sync::Mutex::new(Vec::new()),
outers: std::sync::OnceLock::new(),
children: std::sync::OnceLock::new(),
}
}
fn outer_map(&self) -> &HashMap<String, Option<String>> {
self.outers.get_or_init(|| {
self.order
.iter()
.map(|n| (n.clone(), find_outer_name(self, n)))
.collect()
})
}
pub fn outer_of(&self, name: &str) -> Option<&str> {
self.outer_map().get(name).and_then(|o| o.as_deref())
}
pub fn children_of(&self, internal: &str) -> &[String] {
if self.children.get().is_none() {
let idx: HashMap<String, Vec<String>> = HashMap::new();
let _ = self.children.set(idx);
let mut idx: HashMap<String, Vec<String>> = HashMap::new();
for name in &self.order {
if let Some(outer) = self.outer_of(name).map(str::to_string) {
idx.entry(outer).or_default().push(name.clone());
}
}
let _ = self.children.set(idx);
}
self.children
.get()
.and_then(|m| m.get(internal))
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn add_dex(&mut self, dex: DexFile) -> usize {
let dex_idx = self.dexes.len();
for cd in &dex.class_defs {
let name = dex.class_name(cd.class_idx);
if self.classes.contains_key(&name) {
continue;
}
let pc = pool_class_of(&dex, dex.raw(), cd, dex_idx);
self.classes.insert(name.clone(), ClassEntry::Eager(pc));
self.order.push(name);
}
self.dexes.push(std::sync::Arc::new(dex));
self.retire_counts.lock().unwrap().push(0);
self.dex_labels.push(format!("dex {}", dex_idx));
dex_idx
}
pub fn add_dex_lazy(&mut self, dex: DexFile) -> usize {
let dex_idx = self.dexes.len();
for (ci, cd) in dex.class_defs.iter().enumerate() {
let name = dex.class_name(cd.class_idx);
if self.classes.contains_key(&name) {
continue;
}
self.classes.insert(
name.clone(),
ClassEntry::Lazy {
at: (dex_idx, ci),
pc: std::sync::OnceLock::new(),
},
);
self.order.push(name);
}
self.dexes.push(std::sync::Arc::new(dex));
self.retire_counts.lock().unwrap().push(0);
self.dex_labels.push(format!("dex {}", dex_idx));
dex_idx
}
pub fn set_dex_label(&mut self, idx: usize, label: impl Into<String>) {
if let Some(slot) = self.dex_labels.get_mut(idx) {
*slot = label.into();
}
}
pub fn len(&self) -> usize {
self.classes.len()
}
pub fn is_empty(&self) -> bool {
self.classes.is_empty()
}
pub fn get(&self, internal: &str) -> Option<&PoolClass> {
match self.classes.get(internal)? {
ClassEntry::Eager(pc) => Some(pc),
ClassEntry::Lazy { at, pc } => {
if let Some(built) = pc.get() {
return Some(built);
}
let (di, ci) = *at;
let dex = self.dexes.get(di)?;
let cd = dex.class_defs.get(ci)?;
let built = pool_class_of(dex, dex.raw(), cd, di);
Some(pc.get_or_init(move || built))
}
}
}
pub fn materialize_all(&self, threads: usize) {
let keys: Vec<String> = self
.classes
.iter()
.filter(|(_, e)| matches!(e, ClassEntry::Lazy { .. }))
.map(|(k, _)| k.clone())
.collect();
let threads = threads.max(1);
let next = std::sync::atomic::AtomicUsize::new(0);
std::thread::scope(|scope| {
for _ in 0..threads {
scope.spawn(|| loop {
let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let Some(k) = keys.get(i) else { break };
let _ = self.get(k);
});
}
});
}
fn get_if_materialized(&self, internal: &str) -> Option<&PoolClass> {
match self.classes.get(internal)? {
ClassEntry::Eager(pc) => Some(pc),
ClassEntry::Lazy { pc, .. } => pc.get(),
}
}
pub fn has_name(&self, internal: &str) -> bool {
self.classes.contains_key(internal)
}
pub fn dex(&self, idx: usize) -> Option<std::sync::Arc<DexFile>> {
self.dexes.get(idx).cloned()
}
pub fn dex_count(&self) -> usize {
self.dexes.len()
}
pub fn class_names(&self) -> impl Iterator<Item = &str> {
self.order.iter().map(|s| s.as_str())
}
pub fn is_subtype(&self, sub: &str, sup: &str) -> bool {
if sub == sup {
return true;
}
if let (Some(sub_elem), Some(sup_elem)) = (array_elem(sub), array_elem(sup)) {
if sub_elem.starts_with('L')
&& sup_elem.starts_with('L')
&& sub_elem.ends_with(';')
&& sup_elem.ends_with(';')
{
return self.is_subtype(
&sub_elem[1..sub_elem.len() - 1],
&sup_elem[1..sup_elem.len() - 1],
);
}
return sub_elem == sup_elem;
}
let mut cur = self.get(sub);
let mut hops = 0;
while let Some(c) = cur {
hops += 1;
if hops > 64 {
return false;
}
for i in &c.interfaces {
if i == sup || self.is_subtype(i, sup) {
return true;
}
}
match &c.super_name {
Some(s) if s == sup => return true,
Some(s) if s != "java/lang/Object" => {
cur = self.get(s);
}
_ => return false,
}
}
false
}
}
impl Default for DexPool {
fn default() -> Self {
Self::new()
}
}
fn array_elem(desc: &str) -> Option<&str> {
desc.strip_prefix('[')
}
fn resolve_static_value(v: &EncodedValue, dex: &DexFile) -> StaticValue {
match v {
EncodedValue::Byte(x) => StaticValue::Int(*x as i64),
EncodedValue::Short(x) => StaticValue::Int(*x as i64),
EncodedValue::Char(x) => StaticValue::Int(*x as i64),
EncodedValue::Int(x) => StaticValue::Int(*x as i64),
EncodedValue::Long(x) => StaticValue::Int(*x),
EncodedValue::Float(x) => StaticValue::Float(*x),
EncodedValue::Double(x) => StaticValue::Double(*x),
EncodedValue::String(i) => StaticValue::Str(dex.string(*i).to_string()),
EncodedValue::Type(i) => StaticValue::Type(dex.class_name(*i)),
EncodedValue::Boolean(b) => StaticValue::Boolean(*b),
EncodedValue::Null => StaticValue::Null,
EncodedValue::Enum(i) | EncodedValue::Field(i) => {
let f = dex.field(*i);
StaticValue::Field(
dex.class_name(f.class_idx),
dex.string(f.name_idx).to_string(),
)
}
_ => StaticValue::Other,
}
}
fn pool_class_of(dex: &DexFile, raw: &[u8], cd: &ClassDef, dex_idx: usize) -> PoolClass {
let name = dex.class_name(cd.class_idx);
let super_name = if cd.superclass_idx == ddc_dex::NO_INDEX {
None
} else {
let s = dex.class_name(cd.superclass_idx);
if s.is_empty() || s == "java/lang/Object" {
None
} else {
Some(s)
}
};
let interfaces = dex
.interfaces_of(cd)
.into_iter()
.map(|t| dex.class_name(t))
.collect();
let source_file = if cd.source_file_idx == ddc_dex::NO_INDEX {
None
} else {
Some(dex.string(cd.source_file_idx).to_string())
};
let data = dex.class_data(cd);
let mk_field = |ef: &ddc_dex::EncodedField, is_static: bool| {
let f = dex.field(ef.field_idx);
PoolField {
name: dex.string(f.name_idx).to_string(),
desc: dex.type_name(f.type_idx).to_string(),
access: ef.access_flags,
is_static,
}
};
let static_fields: Vec<PoolField> = data
.static_fields
.iter()
.map(|f| mk_field(f, true))
.collect();
let instance_fields: Vec<PoolField> = data
.instance_fields
.iter()
.map(|f| mk_field(f, false))
.collect();
let mk_method = |em: &ddc_dex::EncodedMethod| {
let m = dex.method(em.method_idx);
let (code_off, debug_info_off) = match dex.debug_info_off_at(em.code_off) {
Some(d) => (em.code_off, d),
None => (0, 0),
};
PoolMethod {
name: dex.string_arc(m.name_idx),
desc: dex.proto_desc(m.proto_idx),
access: em.access_flags,
code_off,
debug_info_off,
dex_idx,
}
};
let direct_methods: Vec<PoolMethod> = data.direct_methods.iter().map(&mk_method).collect();
let virtual_methods: Vec<PoolMethod> = data.virtual_methods.iter().map(&mk_method).collect();
let static_values: Vec<StaticValue> = dex
.static_values(cd.static_values_off)
.into_iter()
.map(|v| resolve_static_value(&v, dex))
.collect();
let anns = if cd.annotations_off != 0 {
annotations::read_class_annotations(raw, cd.annotations_off)
} else {
Vec::new()
};
let raw = annotations::nesting_from(&anns, &|t: u32| dex.type_name(t).to_string());
let nesting = ResolvedNesting {
enclosing_class: raw.enclosing_class.map(|t| dex.class_name(t)),
enclosing_method: raw.enclosing_method.map(|m| {
let mid = dex.method(m);
(
dex.class_name(mid.class_idx),
dex.string(mid.name_idx).to_string(),
)
}),
member_classes: raw
.member_classes
.into_iter()
.map(|t| dex.class_name(t))
.collect(),
};
PoolClass {
name,
access: cd.access_flags,
super_name,
interfaces,
source_file,
static_fields,
instance_fields,
direct_methods,
virtual_methods,
static_values,
nesting,
dex_idx,
}
}
pub fn desc_type(desc: &str) -> JavaType {
parse_field_descriptor(desc).unwrap_or(JavaType::Object("java/lang/Object".into()))
}
pub fn find_outer_name(pool: &DexPool, internal: &str) -> Option<String> {
if let Some(pc) = pool.get_if_materialized(internal) {
if let Some(enc) = &pc.nesting.enclosing_class {
return Some(enc.clone());
}
}
let mut rest = internal;
while let Some(d) = rest.rfind('$') {
let cand = &rest[..d];
if pool.has_name(cand) {
return Some(cand.to_string());
}
rest = cand;
}
None
}
fn clean_member_tail(rest: &str) -> bool {
if rest.starts_with('-') {
return false;
}
let tail = rest.rsplit('$').next().unwrap_or(rest);
if tail.is_empty() || tail.starts_with(|c: char| c.is_ascii_digit()) {
return false;
}
true
}
impl DexPool {
pub fn arm_retirement(&self) {
self.retire_armed
.store(true, std::sync::atomic::Ordering::Release);
let mut counts = self.retire_counts.lock().unwrap();
for c in counts.iter_mut() {
*c = 0;
}
for entry in self.classes.values() {
let di = match entry {
ClassEntry::Eager(pc) => pc.dex_idx,
ClassEntry::Lazy { at, .. } => at.0,
};
if let Some(c) = counts.get_mut(di) {
*c += 1;
}
}
}
pub fn report_class_done(&self, class_name: &str) -> Option<usize> {
if !self.retire_armed.load(std::sync::atomic::Ordering::Acquire) {
return None;
}
let entry = self.classes.get(class_name)?;
let di = match entry {
ClassEntry::Eager(pc) => pc.dex_idx,
ClassEntry::Lazy { at, .. } => at.0,
};
let mut counts = self.retire_counts.lock().unwrap();
match counts.get_mut(di) {
Some(c) => {
*c = c.saturating_sub(1);
if *c == 0 {
Some(di)
} else {
None
}
}
None => None,
}
}
pub fn release_images(&self, indexes: &[usize]) {
let mut counts = self.retire_counts.lock().unwrap();
for &i in indexes {
if let Some(dex) = self.dexes.get(i) {
dex.mark_released();
}
if let Some(c) = counts.get_mut(i) {
*c = u64::MAX; }
}
}
}
pub fn top_level_classes(pool: &DexPool) -> Vec<String> {
pool.class_names()
.filter(|name| match pool.outer_of(name) {
None => true,
Some(outer) => {
if pool.get(outer).is_none() {
return true;
}
let rest = name
.strip_prefix(outer)
.and_then(|t| t.strip_prefix('$'))
.unwrap_or("");
!clean_member_tail(rest)
}
})
.map(|n| n.to_string())
.collect()
}
pub fn case_rename_map(pool: &DexPool) -> std::collections::HashMap<String, String> {
use std::collections::HashMap;
let mut groups: HashMap<String, Vec<String>> = HashMap::new();
for t in top_level_classes(pool) {
groups.entry(t.to_lowercase()).or_default().push(t);
}
let folds: std::collections::HashSet<String> = groups.keys().cloned().collect();
let mut map = HashMap::new();
for (_, mut members) in groups {
members.sort();
for (i, m) in members.iter().enumerate() {
if i == 0 {
map.insert(m.clone(), m.clone());
continue;
}
let cut = m.rfind('/').map(|x| x + 1).unwrap_or(0);
let mut n = i + 1;
loop {
let cand = format!("{}{}_{}", &m[..cut], &m[cut..], n);
let fold = cand.to_lowercase();
if !folds.contains(&fold) {
map.insert(m.clone(), cand);
break;
}
n += 1;
}
}
}
map
}
pub fn install_case_renames(pool: &DexPool) {
jdc_core::rename::set_class_renames(case_rename_map(pool));
}
pub use jdc_core::rename::apply_class_rename;