use std::sync::Arc;
use ddc_dex::annotations::{read_encoded_annotation, EncodedValue};
use ddc_dex::DexFile;
use jdc_core::FxHashMap as HashMap;
use jdc_core::FxHashSet as HashSet;
const NONE: u32 = u32::MAX;
pub fn local_ok(dexes: &[Arc<DexFile>], cands: &HashMap<String, String>) -> HashSet<String> {
if cands.is_empty() {
return HashSet::default();
}
let mut names: Vec<&String> = cands.keys().collect();
names.sort();
let slots: Vec<(&str, &str, String, String)> = names
.iter()
.map(|n| {
let root = cands.get(*n).map(|s| s.as_str()).unwrap_or(*n);
(
n.as_str(),
root,
format!("{root}$"),
format!("L{n};"),
)
})
.collect();
let desc_owned: Vec<(String, u32)> = slots
.iter()
.enumerate()
.flat_map(|(i, s)| {
let mut v = vec![(s.3.clone(), i as u32)];
let twin = format!("L{};", s.0.replace('$', "/"));
if twin != s.3 {
v.push((twin, i as u32));
}
v
})
.collect();
let desc_slot: HashMap<&str, u32> = desc_owned
.iter()
.map(|(d, i)| (d.as_str(), *i))
.collect();
let mut merged: Vec<bool> = vec![false; slots.len()];
let nthreads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.min(4)
.min(dexes.len().max(1));
let chunk = dexes.len().div_ceil(nthreads).max(1);
std::thread::scope(|scope| {
let handles: Vec<_> = dexes
.chunks(chunk)
.map(|group| {
let slots = &slots;
let desc_slot = &desc_slot;
scope.spawn(move || {
let mut dead: Vec<bool> = vec![false; slots.len()];
for dex in group {
let d = scan_image(dex, slots, desc_slot);
for (i, x) in d.into_iter().enumerate() {
dead[i] |= x;
}
if dead.iter().all(|&x| x) {
break; }
}
dead
})
})
.collect();
for h in handles {
if let Ok(dead) = h.join() {
for (i, d) in dead.into_iter().enumerate() {
merged[i] |= d;
}
} else {
for d in merged.iter_mut() {
*d = true;
}
}
}
});
slots
.iter()
.zip(merged.iter())
.filter(|(_, dead)| !*dead)
.map(|(s, _)| s.0.to_string())
.collect()
}
type SlotInfo<'a> = (&'a str, &'a str, String, String);
fn scan_image(
dex: &DexFile,
slots: &[SlotInfo],
desc_slot: &HashMap<&str, u32>,
) -> Vec<bool> {
let mut dead: Vec<bool> = vec![false; slots.len()];
let mut cand_type: Vec<u32> = vec![NONE; dex.type_count()];
for (idx, slot) in cand_type.iter_mut().enumerate() {
if let Some(&s) = desc_slot.get(dex.type_name(idx as u32)) {
*slot = s;
}
}
let mut field_a: Vec<u32> = vec![NONE; dex.field_count()];
let mut field_b: Vec<u32> = vec![NONE; dex.field_count()];
for idx in 0..dex.field_count() {
let f = dex.field(idx as u32);
field_a[idx] = cand_type.get(f.class_idx as usize).copied().unwrap_or(NONE);
field_b[idx] = cand_type.get(f.type_idx as usize).copied().unwrap_or(NONE);
}
let mut meth_a: Vec<u32> = vec![NONE; dex.method_count()];
let mut meth_extra: HashMap<u32, Vec<u32>> = HashMap::default();
for (idx, slot) in meth_a.iter_mut().enumerate() {
let m = dex.method(idx as u32);
let mut hits: Vec<u32> = Vec::new();
let push = |s: u32, hits: &mut Vec<u32>| {
if s != NONE && !hits.contains(&s) {
hits.push(s);
}
};
push(cand_type.get(m.class_idx as usize).copied().unwrap_or(NONE), &mut hits);
let proto = dex.proto(m.proto_idx);
push(
cand_type.get(proto.return_type_idx as usize).copied().unwrap_or(NONE),
&mut hits,
);
for &t in dex.proto_params(m.proto_idx) {
push(cand_type.get(t as usize).copied().unwrap_or(NONE), &mut hits);
}
if !hits.is_empty() {
*slot = hits[0];
if hits.len() > 1 {
meth_extra.insert(idx as u32, hits.split_off(1));
}
}
}
let proto_slots = |pidx: u32, out: &mut Vec<u32>| {
let proto = dex.proto(pidx);
let mut push = |s: u32| {
if s != NONE && !out.contains(&s) {
out.push(s);
}
};
push(cand_type.get(proto.return_type_idx as usize).copied().unwrap_or(NONE));
for &t in dex.proto_params(pidx) {
push(cand_type.get(t as usize).copied().unwrap_or(NONE));
}
};
let raw = dex.raw();
let record = |slot: u32, referrer: &str, dead: &mut Vec<bool>| {
if slot == NONE || dead[slot as usize] {
return;
}
let (_, root, prefix, _) = &slots[slot as usize];
if referrer != *root && !referrer.starts_with(prefix.as_str()) {
dead[slot as usize] = true;
}
};
let record_field = |fidx: u32, referrer: &str, dead: &mut Vec<bool>| {
if let Some(&a) = field_a.get(fidx as usize) {
record(a, referrer, dead);
}
if let Some(&b) = field_b.get(fidx as usize) {
record(b, referrer, dead);
}
};
let record_meth = |midx: u32, referrer: &str, dead: &mut Vec<bool>| {
if let Some(&a) = meth_a.get(midx as usize) {
record(a, referrer, dead);
}
if let Some(extra) = meth_extra.get(&midx) {
for &s in extra {
record(s, referrer, dead);
}
}
};
let record_ev = |ev: &EncodedValue, referrer: &str, dead: &mut Vec<bool>| {
let mut stack: Vec<EncodedValue> = vec![ev.clone()];
while let Some(v) = stack.pop() {
match v {
EncodedValue::Type(t) => {
let s = cand_type.get(t as usize).copied().unwrap_or(NONE);
record(s, referrer, dead);
}
EncodedValue::Field(f) | EncodedValue::Enum(f) => {
record_field(f, referrer, dead);
}
EncodedValue::Method(m) => record_meth(m, referrer, dead),
EncodedValue::MethodType(p) => {
let mut out = Vec::new();
proto_slots(p, &mut out);
for s in out {
record(s, referrer, dead);
}
}
EncodedValue::MethodHandle(h) => {
if let Some(mh) = dex.method_handle(h) {
if mh.is_field {
record_field(mh.target_id, referrer, dead);
} else {
record_meth(mh.target_id, referrer, dead);
}
}
}
EncodedValue::Array(items) => stack.extend(items),
EncodedValue::Annotation(a) => {
let s = cand_type.get(a.type_idx as usize).copied().unwrap_or(NONE);
record(s, referrer, dead);
stack.extend(a.elements.into_iter().map(|(_, v)| v));
}
_ => {}
}
}
};
let u4 = |off: usize| -> u32 {
raw.get(off..off + 4)
.map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.unwrap_or(0)
};
let walk_set = |set_off: u32, referrer: &str, dead: &mut Vec<bool>| {
let set = set_off as usize;
if set == 0 || set + 4 > raw.len() {
return;
}
let n = u4(set) as usize;
for i in 0..n {
let item = u4(set + 4 + 4 * i) as usize;
if item == 0 || item + 1 >= raw.len() {
continue;
}
if let Some((a, _)) = read_encoded_annotation(raw, item + 1) {
let s = cand_type.get(a.type_idx as usize).copied().unwrap_or(NONE);
record(s, referrer, dead);
for (_, v) in a.elements {
record_ev(&v, referrer, dead);
}
}
}
};
let walk_ann_dir = |dir_off: u32, referrer: &str, dead: &mut Vec<bool>| {
let off = dir_off as usize;
if off == 0 || off + 16 > raw.len() {
return;
}
walk_set(u4(off), referrer, dead);
let mut p = off + 16;
for k in 0..3 {
let n = u4(off + 4 + 4 * k) as usize;
for _ in 0..n {
if p + 8 > raw.len() {
return;
}
walk_set(u4(p + 4), referrer, dead);
p += 8;
}
}
};
for cd in &dex.class_defs {
if dead.iter().all(|&d| d) {
break; }
let referrer = dex.class_name(cd.class_idx);
let s = cand_type.get(cd.superclass_idx as usize).copied().unwrap_or(NONE);
record(s, &referrer, &mut dead);
for i in dex.interfaces_of(cd) {
let s = cand_type.get(i as usize).copied().unwrap_or(NONE);
record(s, &referrer, &mut dead);
}
let data = dex.class_data(cd);
for f in data.static_fields.iter().chain(data.instance_fields.iter()) {
record_field(f.field_idx, &referrer, &mut dead);
}
for m in data.direct_methods.iter().chain(data.virtual_methods.iter()) {
record_meth(m.method_idx, &referrer, &mut dead);
let Some(code) = (m.code_off != 0).then(|| dex.code_insns_bytes_at(m.code_off)).flatten() else {
continue;
};
ddc_dex::insn::scan_instructions(code, &mut |op, pc, bytes| {
let unit = |i: usize| -> u32 {
bytes
.get(2 * i..2 * i + 2)
.map(|b| u16::from_le_bytes([b[0], b[1]]) as u32)
.unwrap_or(NONE)
};
match op {
0x1c | 0x1f | 0x20 | 0x22..=0x25 | 0xff => {
let t = unit(pc + 1);
let s = cand_type.get(t as usize).copied().unwrap_or(NONE);
record(s, &referrer, &mut dead);
}
0x52..=0x6d => record_field(unit(pc + 1), &referrer, &mut dead),
0x6e..=0x72 | 0x74..=0x78 => record_meth(unit(pc + 1), &referrer, &mut dead),
0xfa | 0xfb => {
record_meth(unit(pc + 1), &referrer, &mut dead);
let mut out = Vec::new();
proto_slots(unit(pc + 2), &mut out);
for s in out {
record(s, &referrer, &mut dead);
}
}
0xfc => {
if let Some(cs) = dex.call_site(unit(pc + 1)) {
let mut out = Vec::new();
proto_slots(cs.proto_idx, &mut out);
for s in out {
record(s, &referrer, &mut dead);
}
for v in &cs.linker_args {
record_ev(v, &referrer, &mut dead);
}
}
}
0xfe => {
if let Some(mh) = dex.method_handle(unit(pc + 1)) {
if mh.is_field {
record_field(mh.target_id, &referrer, &mut dead);
} else {
record_meth(mh.target_id, &referrer, &mut dead);
}
}
}
_ => {}
}
});
}
walk_ann_dir(cd.annotations_off, &referrer, &mut dead);
if cd.static_values_off != 0 {
for v in dex.static_values(cd.static_values_off) {
record_ev(&v, &referrer, &mut dead);
}
}
}
dead
}
pub fn body_ref_segments(
dexes: &[Arc<DexFile>],
cand_pkgs: &HashSet<String>,
) -> HashMap<String, HashSet<String>> {
if cand_pkgs.is_empty() {
return HashMap::default();
}
let nthreads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.min(4)
.min(dexes.len().max(1));
let chunk = dexes.len().div_ceil(nthreads).max(1);
let merged: std::sync::Mutex<HashMap<String, HashSet<String>>> =
std::sync::Mutex::new(HashMap::default());
std::thread::scope(|scope| {
let handles: Vec<_> = dexes
.chunks(chunk)
.map(|group| {
let merged = &merged;
scope.spawn(move || {
for dex in group {
let part = scan_image_segments(dex, cand_pkgs);
let mut m = merged.lock().unwrap();
for (k, v) in part {
m.entry(k).or_default().extend(v);
}
}
})
})
.collect();
for h in handles {
let _ = h.join();
}
});
merged.into_inner().unwrap_or_default()
}
fn pkg_of(n: &str) -> &str {
match n.rsplit_once('/') {
Some((p, _)) => p,
None => "",
}
}
fn scan_image_segments(
dex: &DexFile,
cand_pkgs: &HashSet<String>,
) -> HashMap<String, HashSet<String>> {
let n = dex.type_count();
let mut tpkg: Vec<Box<str>> = Vec::with_capacity(n);
let mut tseg: Vec<Box<str>> = Vec::with_capacity(n);
for idx in 0..n {
let name = dex.type_name(idx as u32);
let mut b = name.strip_prefix('[').unwrap_or(name).to_string();
while b.starts_with('[') {
b.remove(0);
}
let inner = b
.strip_prefix('L')
.and_then(|s| s.strip_suffix(';'))
.unwrap_or("");
tpkg.push(
match inner.rfind('/') {
Some(j) => inner[..j].into(),
None => Box::from(""),
},
);
tseg.push(
match inner.find('/') {
Some(i) => inner[..i].into(),
None => Box::from(""),
},
);
}
let mut out: HashMap<String, HashSet<String>> = HashMap::default();
for cd in &dex.class_defs {
let referrer = dex.class_name(cd.class_idx);
let rp = pkg_of(&referrer);
if rp.is_empty() || !cand_pkgs.contains(rp) {
continue;
}
let mut segs: HashSet<String> = HashSet::default();
let note = |t: u32, segs: &mut HashSet<String>| {
let i = t as usize;
let Some(p) = tpkg.get(i) else { return };
if p.is_empty() || p.as_ref() == rp {
return;
}
if let Some(s) = tseg.get(i) {
if !s.is_empty() {
segs.insert(s.to_string());
}
}
};
note(cd.superclass_idx, &mut segs);
for i in dex.interfaces_of(cd) {
note(i, &mut segs);
}
let data = dex.class_data(cd);
for f in data.static_fields.iter().chain(data.instance_fields.iter()) {
let fr = dex.field(f.field_idx);
note(fr.class_idx, &mut segs);
note(fr.type_idx, &mut segs);
}
for m in data.direct_methods.iter().chain(data.virtual_methods.iter()) {
let mr = dex.method(m.method_idx);
note(mr.class_idx, &mut segs);
let proto = dex.proto(mr.proto_idx);
note(proto.return_type_idx, &mut segs);
for &t in dex.proto_params(mr.proto_idx) {
note(t, &mut segs);
}
let Some(code) = (m.code_off != 0)
.then(|| dex.code_insns_bytes_at(m.code_off))
.flatten()
else {
continue;
};
ddc_dex::insn::scan_instructions(code, &mut |op, pc, bytes| {
let unit = |i: usize| -> u32 {
bytes
.get(2 * i..2 * i + 2)
.map(|b| u16::from_le_bytes([b[0], b[1]]) as u32)
.unwrap_or(NONE)
};
match op {
0x1c | 0x1f | 0x20 | 0x22..=0x25 | 0xff => {
note(unit(pc + 1), &mut segs);
}
0x52..=0x6d => {
let f = dex.field(unit(pc + 1));
note(f.class_idx, &mut segs);
note(f.type_idx, &mut segs);
}
0x6e..=0x72 | 0x74..=0x78 | 0xfa | 0xfb => {
let mr = dex.method(unit(pc + 1));
note(mr.class_idx, &mut segs);
let proto = dex.proto(mr.proto_idx);
note(proto.return_type_idx, &mut segs);
for &t in dex.proto_params(mr.proto_idx) {
note(t, &mut segs);
}
}
_ => {}
}
});
}
if !segs.is_empty() {
out.entry(referrer_pkg_string(&referrer))
.or_default()
.extend(segs);
}
}
out
}
fn referrer_pkg_string(referrer: &str) -> String {
pkg_of(referrer).to_string()
}
pub type SegMaps = (
HashMap<String, HashSet<String>>,
HashMap<String, HashSet<String>>,
);
pub fn family_ref_segments(
dexes: &[Arc<DexFile>],
cand: &HashSet<String>,
) -> SegMaps {
if cand.is_empty() {
return (HashMap::default(), HashMap::default());
}
let nthreads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.min(4)
.min(dexes.len().max(1));
let chunk = dexes.len().div_ceil(nthreads).max(1);
let merged: std::sync::Mutex<SegMaps> =
std::sync::Mutex::new((HashMap::default(), HashMap::default()));
std::thread::scope(|scope| {
let handles: Vec<_> = dexes
.chunks(chunk)
.map(|group| {
let merged = &merged;
scope.spawn(move || {
let mut part: HashMap<String, HashSet<String>> = HashMap::default();
let mut part_nest: HashMap<String, HashSet<String>> =
HashMap::default();
for dex in group {
let (a, b) = scan_family_segments(dex, cand);
for (k, v) in a {
part.entry(k).or_default().extend(v);
}
for (k, v) in b {
part_nest.entry(k).or_default().extend(v);
}
}
let mut m = merged.lock().unwrap();
for (k, v) in part {
m.0.entry(k).or_default().extend(v);
}
for (k, v) in part_nest {
m.1.entry(k).or_default().extend(v);
}
})
})
.collect();
for h in handles {
let _ = h.join();
}
});
merged.into_inner().unwrap_or_default()
}
fn scan_family_segments(dex: &DexFile, cand: &HashSet<String>) -> SegMaps {
let n = dex.type_count();
let mut tseg: Vec<Box<str>> = Vec::with_capacity(n);
let mut tpkg_empty: Vec<bool> = Vec::with_capacity(n);
for idx in 0..n {
let name = dex.type_name(idx as u32);
let mut b = name.strip_prefix('[').unwrap_or(name).to_string();
while b.starts_with('[') {
b.remove(0);
}
let inner = b
.strip_prefix('L')
.and_then(|s| s.strip_suffix(';'))
.unwrap_or("");
tpkg_empty.push(!inner.contains('/'));
tseg.push(
match inner.find('/') {
Some(i) => inner[..i].into(),
None => Box::from(""),
},
);
}
let mut out: HashMap<String, HashSet<String>> = HashMap::default();
let mut out_nest: HashMap<String, HashSet<String>> = HashMap::default();
for cd in &dex.class_defs {
let referrer = dex.class_name(cd.class_idx);
if !cand.contains(&referrer) {
continue;
}
let rp = pkg_of(&referrer);
let rroot = match referrer.find('$') {
Some(i) => &referrer[..i],
None => referrer.as_str(),
};
let mut segs: HashSet<String> = HashSet::default();
let mut nest: HashSet<String> = HashSet::default();
let note = |t: u32, segs: &mut HashSet<String>, nest: &mut HashSet<String>| {
let i = t as usize;
if tpkg_empty.get(i).copied().unwrap_or(true) {
return;
}
let name = dex.type_name(t);
let inner = name
.trim_start_matches('[')
.strip_prefix('L')
.and_then(|s| s.strip_suffix(';'))
.unwrap_or("");
if let Some(d) = inner.find('$') {
let root = &inner[..d];
if root == rroot {
let rest = &inner[d + 1..];
let tail = match rest.find('$') {
Some(j) => &rest[..j],
None => rest,
};
if !tail.is_empty() {
nest.insert(tail.to_string());
}
return;
}
}
if let Some(j) = inner.rfind('/') {
if &inner[..j] == rp {
return;
}
}
if let Some(s) = tseg.get(i) {
if !s.is_empty() {
segs.insert(s.to_string());
}
}
};
note(cd.superclass_idx, &mut segs, &mut nest);
for i in dex.interfaces_of(cd) {
note(i, &mut segs, &mut nest);
}
let data = dex.class_data(cd);
for f in data.static_fields.iter().chain(data.instance_fields.iter()) {
let fr = dex.field(f.field_idx);
note(fr.class_idx, &mut segs, &mut nest);
note(fr.type_idx, &mut segs, &mut nest);
}
for m in data.direct_methods.iter().chain(data.virtual_methods.iter()) {
let mr = dex.method(m.method_idx);
note(mr.class_idx, &mut segs, &mut nest);
let proto = dex.proto(mr.proto_idx);
note(proto.return_type_idx, &mut segs, &mut nest);
for &t in dex.proto_params(mr.proto_idx) {
note(t, &mut segs, &mut nest);
}
let Some(code) = (m.code_off != 0)
.then(|| dex.code_insns_bytes_at(m.code_off))
.flatten()
else {
continue;
};
ddc_dex::insn::scan_instructions(code, &mut |op, pc, bytes| {
let unit = |i: usize| -> u32 {
bytes
.get(2 * i..2 * i + 2)
.map(|b| u16::from_le_bytes([b[0], b[1]]) as u32)
.unwrap_or(NONE)
};
match op {
0x1c | 0x1f | 0x20 | 0x22..=0x25 | 0xff => {
note(unit(pc + 1), &mut segs, &mut nest);
}
0x52..=0x6d => {
let f = dex.field(unit(pc + 1));
note(f.class_idx, &mut segs, &mut nest);
note(f.type_idx, &mut segs, &mut nest);
}
0x6e..=0x72 | 0x74..=0x78 | 0xfa | 0xfb => {
let mr = dex.method(unit(pc + 1));
note(mr.class_idx, &mut segs, &mut nest);
let proto = dex.proto(mr.proto_idx);
note(proto.return_type_idx, &mut segs, &mut nest);
for &t in dex.proto_params(mr.proto_idx) {
note(t, &mut segs, &mut nest);
}
}
_ => {}
}
});
}
if !segs.is_empty() {
out.entry(referrer.to_string()).or_default().extend(segs);
}
if !nest.is_empty() {
out_nest
.entry(rroot.to_string())
.or_default()
.extend(nest);
}
}
(out, out_nest)
}
#[derive(Default)]
pub struct AccessWidening {
pub classes: HashSet<String>,
pub methods: HashMap<String, HashSet<(String, String)>>,
pub fields: HashMap<String, HashSet<String>>,
pub unfinal_fields: HashSet<(String, String)>,
}
fn chain_prefixes(internal: &str, out: &mut HashSet<String>) {
let mut cur = internal;
loop {
out.insert(cur.to_string());
match cur.rfind('$') {
Some(i) if i > 0 => cur = &cur[..i],
_ => break,
}
}
}
pub fn access_widening_scan(dexes: &[Arc<DexFile>]) -> AccessWidening {
let nthreads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.min(4)
.min(dexes.len().max(1));
let chunk = dexes.len().div_ceil(nthreads).max(1);
let finals: HashSet<(&str, &str)> = {
let merged: std::sync::Mutex<HashSet<(&str, &str)>> =
std::sync::Mutex::new(HashSet::default());
std::thread::scope(|scope| {
let handles: Vec<_> = dexes
.chunks(chunk)
.map(|group| {
let merged = &merged;
scope.spawn(move || {
let mut part: HashSet<(&str, &str)> = HashSet::default();
for dex in group {
for cd in &dex.class_defs {
let data = dex.class_data(cd);
let any_final = data
.instance_fields
.iter()
.chain(data.static_fields.iter())
.any(|f| f.access_flags & crate::access::ACC_FINAL != 0);
if !any_final {
continue;
}
let owner = dex.type_name(cd.class_idx);
for f in
data.instance_fields.iter().chain(data.static_fields.iter())
{
if f.access_flags & crate::access::ACC_FINAL != 0 {
let fr = dex.field(f.field_idx);
part.insert((owner, dex.string(fr.name_idx)));
}
}
}
}
let mut m = merged.lock().unwrap();
m.extend(part);
})
})
.collect();
for h in handles {
let _ = h.join();
}
});
merged.into_inner().unwrap()
};
let merged: std::sync::Mutex<AccessWidening> =
std::sync::Mutex::new(AccessWidening::default());
let finals = &finals;
std::thread::scope(|scope| {
let handles: Vec<_> = dexes
.chunks(chunk)
.map(|group| {
let merged = &merged;
scope.spawn(move || {
let mut part = AccessWidening::default();
for dex in group {
scan_widening_image(dex, &mut part, finals);
}
let mut m = merged.lock().unwrap();
for c in part.classes {
m.classes.insert(c);
}
for (k, v) in part.methods {
m.methods.entry(k).or_default().extend(v);
}
for (k, v) in part.fields {
m.fields.entry(k).or_default().extend(v);
}
m.unfinal_fields.extend(part.unfinal_fields);
})
})
.collect();
for h in handles {
let _ = h.join();
}
});
merged.into_inner().unwrap_or_default()
}
fn scan_widening_image(
dex: &DexFile,
out: &mut AccessWidening,
finals: &HashSet<(&str, &str)>,
) {
let n = dex.type_count();
let mut tname: Vec<Box<str>> = Vec::with_capacity(n);
let mut tpkg: Vec<Box<str>> = Vec::with_capacity(n);
for idx in 0..n {
let name = dex.type_name(idx as u32);
let mut b = name.strip_prefix('[').unwrap_or(name).to_string();
while b.starts_with('[') {
b.remove(0);
}
let inner = b
.strip_prefix('L')
.and_then(|s| s.strip_suffix(';'))
.unwrap_or("");
let p = match inner.rfind('/') {
Some(j) => &inner[..j],
None => "",
};
tname.push(inner.into());
tpkg.push(p.into());
}
for cd in &dex.class_defs {
let referrer = dex.class_name(cd.class_idx);
let rp = pkg_of(&referrer);
if rp.is_empty() {
continue; }
let note_type = |t: u32, out: &mut AccessWidening| {
let i = t as usize;
let Some(nm) = tname.get(i) else { return };
if nm.is_empty() {
return;
}
let Some(p) = tpkg.get(i) else { return };
if p.is_empty() || p.as_ref() == rp {
return;
}
chain_prefixes(nm, &mut out.classes);
};
note_type(cd.superclass_idx, out);
for i in dex.interfaces_of(cd) {
note_type(i, out);
}
let data = dex.class_data(cd);
for f in data.static_fields.iter().chain(data.instance_fields.iter()) {
let fr = dex.field(f.field_idx);
note_type(fr.class_idx, out);
note_type(fr.type_idx, out);
}
for m in data.direct_methods.iter().chain(data.virtual_methods.iter()) {
let mr = dex.method(m.method_idx);
let mname = dex.string(mr.name_idx);
note_type(mr.class_idx, out);
let proto = dex.proto(mr.proto_idx);
note_type(proto.return_type_idx, out);
for &t in dex.proto_params(mr.proto_idx) {
note_type(t, out);
}
let Some(code) = (m.code_off != 0)
.then(|| dex.code_insns_bytes_at(m.code_off))
.flatten()
else {
continue;
};
let in_accessor = m.access_flags & crate::access::ACC_STATIC != 0
&& m.access_flags & crate::access::ACC_SYNTHETIC != 0;
let mut final_init_writes: HashMap<(String, String), u32> = HashMap::default();
ddc_dex::insn::scan_instructions(code, &mut |op, pc, bytes| {
let unit = |i: usize| -> u32 {
bytes
.get(2 * i..2 * i + 2)
.map(|b| u16::from_le_bytes([b[0], b[1]]) as u32)
.unwrap_or(NONE)
};
match op {
0x1c | 0x1f | 0x20 | 0x22..=0x25 | 0xff => {
note_type(unit(pc + 1), out);
}
0x52..=0x6d => {
let fr = dex.field(unit(pc + 1));
let owner = dex.class_name(fr.class_idx);
if matches!(op, 0x59..=0x5f | 0x67..=0x6d) {
let fname = dex.string(fr.name_idx);
if finals.contains(&(dex.type_name(fr.class_idx), fname)) {
let ctor = if op <= 0x5f { "<init>" } else { "<clinit>" };
if referrer == owner && mname == ctor {
*final_init_writes
.entry((owner.clone(), fname.to_string()))
.or_default() += 1;
} else {
out.unfinal_fields
.insert((owner.clone(), fname.to_string()));
}
}
}
let op_pkg = pkg_of(&owner);
let xpkg = !op_pkg.is_empty() && op_pkg != rp;
if xpkg {
note_type(fr.class_idx, out);
}
if xpkg || in_accessor {
out.fields
.entry(owner.clone())
.or_default()
.insert(dex.string(fr.name_idx).to_string());
}
note_type(fr.type_idx, out);
}
0x6e..=0x72 | 0x74..=0x78 | 0xfa | 0xfb => {
let mr = dex.method(unit(pc + 1));
let owner = dex.class_name(mr.class_idx);
let op_pkg = pkg_of(&owner);
let xpkg = !op_pkg.is_empty() && op_pkg != rp;
if xpkg {
note_type(mr.class_idx, out);
}
if xpkg || in_accessor {
out.methods
.entry(owner.clone())
.or_default()
.insert((
dex.string(mr.name_idx).to_string(),
dex.proto_desc(mr.proto_idx).to_string(),
));
}
let proto = dex.proto(mr.proto_idx);
note_type(proto.return_type_idx, out);
for &t in dex.proto_params(mr.proto_idx) {
note_type(t, out);
}
}
_ => {}
}
});
for (k, c) in final_init_writes {
if c >= 2 {
out.unfinal_fields.insert(k);
}
}
}
}
}