1use crate::{Engine, EngineError, Result, load_entry, names};
4use asc_dex::container::logical_dex_iter;
5use asc_dex::{Dex, DexError, mutf8};
6use std::sync::Mutex;
7use std::sync::atomic::{AtomicUsize, Ordering};
8use std::time::Instant;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub struct ClassListing {
14 pub descriptor: String,
16 pub dex_name: String,
18}
19
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
22#[non_exhaustive]
23pub struct DexClasses {
24 pub dex_name: String,
26 pub descriptors: Vec<String>,
28 pub error: Option<DexError>,
30 pub bad_class_defs: usize,
32 pub first_bad_class_def: Option<(u32, DexError)>,
34}
35
36impl DexClasses {
37 pub fn check(&self) -> Result<()> {
44 if let Some(e) = &self.error {
45 return Err(EngineError::BadDex { dex: self.dex_name.clone(), reason: e.to_string() });
46 }
47 if let Some((i, e)) = &self.first_bad_class_def {
48 return Err(EngineError::BadDex { dex: self.dex_name.clone(), reason: format!("class_def {i}: {e}") });
49 }
50 Ok(())
51 }
52}
53
54#[derive(Debug)]
56#[non_exhaustive]
57pub struct EntryClasses {
58 pub entry_name: String,
60 pub result: Result<Vec<DexClasses>>,
62}
63
64enum Prefix {
66 Bytes(Vec<u8>),
68 Decoded(String),
71}
72
73impl Prefix {
74 fn new(prefix: &str) -> Prefix {
75 if prefix.is_ascii() { Prefix::Bytes(prefix.as_bytes().to_vec()) } else { Prefix::Decoded(prefix.to_string()) }
76 }
77
78 fn matches(&self, raw: &[u8]) -> Option<String> {
80 match self {
81 Prefix::Bytes(p) => raw.starts_with(p).then(|| mutf8::decode(raw)),
82 Prefix::Decoded(p) => Some(mutf8::decode(raw)).filter(|name| name.starts_with(p.as_str())),
83 }
84 }
85}
86
87pub fn dex_classes(dex_name: &str, buf: &[u8], prefix: Option<&str>) -> DexClasses {
91 dex_classes_with(dex_name, buf, prefix.map(Prefix::new).as_ref(), false)
92}
93
94fn dex_classes_with(dex_name: &str, buf: &[u8], prefix: Option<&Prefix>, strict: bool) -> DexClasses {
96 let mut out = DexClasses { dex_name: dex_name.to_string(), ..Default::default() };
97 let dex = match Dex::with_name(buf, dex_name) {
98 Ok(d) => d,
99 Err(e) => {
100 out.error = Some(e);
101 return out;
102 }
103 };
104 let defs = dex.header().class_defs;
107 if defs.size == 0 {
108 return out;
109 }
110 if defs.off as u64 + defs.size as u64 * asc_dex::ClassDef::SIZE as u64 > buf.len() as u64 {
111 out.error = Some(DexError::BadRange { what: "class_defs", offset: defs.off as usize });
112 return out;
113 }
114 for (i, def) in dex.class_defs().enumerate() {
115 let bytes = def.and_then(|d| dex.type_bytes(d.class_idx));
116 match bytes {
117 Ok(b) => match prefix {
118 None => out.descriptors.push(mutf8::decode(b)),
119 Some(p) => out.descriptors.extend(p.matches(b)),
120 },
121 Err(e) => {
122 out.bad_class_defs += 1;
123 if out.first_bad_class_def.is_none() {
124 out.first_bad_class_def = Some((i as u32, e));
125 }
126 if strict {
127 break;
128 }
129 }
130 }
131 }
132 out
133}
134
135impl Engine {
136 pub fn scan_classes(&self, prefix: Option<&str>, progress: impl Fn(usize, usize) + Sync) -> Result<Vec<EntryClasses>> {
149 self.scan(prefix, false, progress)
150 }
151
152 fn scan(&self, prefix: Option<&str>, strict: bool, progress: impl Fn(usize, usize) + Sync) -> Result<Vec<EntryClasses>> {
155 let t0 = Instant::now();
156 let apk = self.open()?;
157 let entries = apk.dex_entries_in_order()?;
158 let key = self.apk_key(&apk);
159 let total = entries.len();
160 let mut order: Vec<usize> = (0..total).collect();
161 order.sort_by_key(|&i| std::cmp::Reverse(entries[i].comp_size));
162 let prefix = prefix.map(Prefix::new);
163 let slots: Vec<Mutex<Option<EntryClasses>>> = (0..total).map(|_| Mutex::new(None)).collect();
164 let next = AtomicUsize::new(0);
165 let done = AtomicUsize::new(0);
166 let workers = self.threads().min(total).max(1);
167 std::thread::scope(|s| {
168 for _ in 0..workers {
169 let (apk, entries, order, slots, key, prefix) = (&apk, &entries, &order, &slots, key.as_ref(), prefix.as_ref());
170 let (next, done, progress) = (&next, &done, &progress);
171 s.spawn(move || {
172 while let Some(&i) = order.get(next.fetch_add(1, Ordering::Relaxed)) {
173 let entry = &entries[i];
174 let result = load_entry(apk, key, entry, self.cache.as_deref(), &self.stats, None).map(|(loaded, _)| {
175 logical_dex_iter(&entry.name, loaded.bytes())
176 .map(|(name, buf)| dex_classes_with(&name, &buf, prefix, strict))
177 .collect()
178 });
179 *slots[i].lock().expect("entry slot mutex poisoned") =
180 Some(EntryClasses { entry_name: entry.name.clone(), result });
181 progress(done.fetch_add(1, Ordering::Relaxed) + 1, total);
182 }
183 });
184 }
185 });
186 let out: Vec<EntryClasses> =
187 slots.into_iter().map(|s| s.into_inner().expect("entry slot mutex poisoned").expect("every entry is scanned")).collect();
188 if self.debug {
189 let mut count = 0;
190 for e in &out {
191 for d in e.result.iter().flatten() {
192 count += d.descriptors.len();
193 self.log(format!("[APK] '{}' class_count={}", d.dex_name, d.descriptors.len()));
194 }
195 }
196 self.log(format!("[APK] listclass total={} us count={count} workers={workers}", t0.elapsed().as_micros()));
197 }
198 Ok(out)
199 }
200
201 pub fn list_classes(&self, prefix: Option<&str>) -> Result<Vec<ClassListing>> {
211 let prefix = prefix.map(names::class_prefix).transpose()?;
212 let mut out = Vec::new();
213 for entry in self.scan(prefix.as_deref(), true, |_, _| {})? {
214 for dex in entry.result? {
215 dex.check()?;
216 let dex_name = dex.dex_name;
217 out.extend(dex.descriptors.into_iter().map(|descriptor| ClassListing { descriptor, dex_name: dex_name.clone() }));
218 }
219 }
220 Ok(out)
221 }
222}
223
224pub(crate) fn inner_class_named(dex: &[u8], outer: &str, name: &str) -> Option<String> {
229 let prefix = format!("{}$", outer.strip_suffix(';')?);
230 let dex = Dex::parse(dex).ok()?;
231 let mut wanted = names::denormalize_identifier(name);
232 wanted.push(name.to_string());
233 (0..dex.class_def_count()).find_map(|i| {
234 let def = dex.class_def(i).ok()?;
235 let descriptor = dex.type_descriptor(def.class_idx).ok()?;
236 if !descriptor.starts_with(&prefix) {
237 return None;
238 }
239 inner_class_annotation_name(&dex, def.annotations_off).filter(|n| wanted.contains(n)).map(|_| descriptor)
240 })
241}
242
243fn inner_class_annotation_name(dex: &Dex<'_>, annotations_off: u32) -> Option<String> {
246 use asc_dex::encoded::{EncodedValue, parse_annotation_item};
247 if annotations_off == 0 {
248 return None;
249 }
250 let buf = dex.buf();
251 let dir = asc_dex::annotations::AnnotationsDirectory::parse(buf, annotations_off as usize).ok()?;
252 if dir.class_annotations_off == 0 {
253 return None;
254 }
255 for item_off in asc_dex::annotations::parse_annotation_set(buf, dir.class_annotations_off as usize).ok()? {
256 let (item, _) = parse_annotation_item(buf, item_off as usize).ok()?;
257 if dex.type_bytes(item.annotation.type_idx).ok()? != b"Ldalvik/annotation/InnerClass;" {
258 continue;
259 }
260 return item.annotation.elements.iter().find_map(|(name_idx, value)| match value {
261 EncodedValue::String(s) if dex.string_bytes(*name_idx).ok()? == b"name" => dex.string(*s).ok(),
262 _ => None,
263 });
264 }
265 None
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn dex_classes_filters_by_raw_prefix_and_reports_bad_defs() {
274 let dex = asc_fixture::make_dex();
275 let all = dex_classes("classes.dex", &dex, None);
276 assert_eq!(all.descriptors, ["Lexample/Test;"]);
277 assert!(all.check().is_ok());
278 assert!(dex_classes("classes.dex", &dex, Some("Lexample/")).descriptors.len() == 1);
279 assert!(dex_classes("classes.dex", &dex, Some("Lother")).descriptors.is_empty());
280
281 let mut bad = dex.clone();
282 let off = u32::from_le_bytes(bad[0x64..0x68].try_into().unwrap()) as usize;
283 bad[off..off + 4].copy_from_slice(&u32::MAX.to_le_bytes());
284 let r = dex_classes("classes.dex", &bad, None);
285 assert!(r.descriptors.is_empty());
286 assert_eq!(r.bad_class_defs, 1);
287 let msg = r.check().unwrap_err().to_string();
288 assert!(msg.starts_with("classes.dex: class_def 0: type index"), "{msg}");
289
290 let mut huge = dex.clone();
291 huge[0x60..0x64].copy_from_slice(&u32::MAX.to_le_bytes());
292 let r = dex_classes("classes.dex", &huge, None);
293 assert!(matches!(r.error, Some(DexError::BadRange { what: "class_defs", .. })));
294
295 let mut none = dex.clone();
297 none[0x60..0x68].copy_from_slice(&[0, 0, 0, 0, 0x00, 0xff, 0xff, 0xff]);
298 let r = dex_classes("classes.dex", &none, None);
299 assert_eq!(r.error, None);
300 assert!(r.descriptors.is_empty() && r.check().is_ok());
301
302 let r = dex_classes("x.dex", b"not a dex", None);
303 assert!(r.error.is_some() && r.check().is_err());
304
305 let only = asc_fixture::make_class_only_dex(&["Lb/B;", "La/A;"], 0, false);
306 assert_eq!(
307 dex_classes("c.dex", &only, None),
308 DexClasses { dex_name: "c.dex".into(), descriptors: vec!["Lb/B;".into(), "La/A;".into()], ..Default::default() }
309 );
310 }
311
312 #[test]
313 fn strict_stops_at_the_first_bad_class_def() {
314 let only = asc_fixture::make_class_only_dex(&["La/A;", "Lb/B;", "Lc/C;"], 0, false);
315 let off = u32::from_le_bytes(only[0x64..0x68].try_into().unwrap()) as usize;
316 let mut bad = only.clone();
317 for i in 0..2 {
318 bad[off + i * 32..off + i * 32 + 4].copy_from_slice(&u32::MAX.to_le_bytes());
319 }
320 let lenient = dex_classes("c.dex", &bad, None);
321 assert_eq!((lenient.bad_class_defs, lenient.descriptors.as_slice()), (2, &["Lc/C;".to_string()][..]));
322 let strict = dex_classes_with("c.dex", &bad, None, true);
323 assert_eq!((strict.bad_class_defs, strict.descriptors.len()), (1, 0));
324 assert_eq!(strict.check().unwrap_err().to_string(), lenient.check().unwrap_err().to_string());
325 }
326
327 #[test]
328 fn non_ascii_prefix_matches_decoded_names() {
329 let name = "L\u{1f600}/A;";
330 let canonical = asc_fixture::make_class_only_dex(&[name], 0, false);
331 let mut four = asc_fixture::make_class_only_dex(&["Lxxxx/A;"], 0, false);
334 let at = four.windows(8).position(|w| w == b"Lxxxx/A;").unwrap();
335 four[at + 1..at + 5].copy_from_slice("\u{1f600}".as_bytes());
336 for data in [&canonical, &four] {
337 assert_eq!(dex_classes("c.dex", data, None).descriptors, [name]);
338 assert_eq!(dex_classes("c.dex", data, Some("L\u{1f600}")).descriptors, [name]);
339 assert_eq!(dex_classes("c.dex", data, Some("L\u{1f600}/A")).descriptors, [name]);
340 assert!(dex_classes("c.dex", data, Some("L\u{1f601}")).descriptors.is_empty());
341 }
342 }
343}