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)]
12pub struct ClassListing {
13 pub descriptor: String,
14 pub dex_name: String,
15}
16
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
19pub struct DexClasses {
20 pub dex_name: String,
21 pub descriptors: Vec<String>,
22 pub error: Option<DexError>,
24 pub bad_class_defs: usize,
26 pub first_bad_class_def: Option<(u32, DexError)>,
28}
29
30impl DexClasses {
31 pub fn check(&self) -> Result<()> {
33 if let Some(e) = &self.error {
34 return Err(EngineError::BadDex { dex: self.dex_name.clone(), reason: e.to_string() });
35 }
36 if let Some((i, e)) = &self.first_bad_class_def {
37 return Err(EngineError::BadDex { dex: self.dex_name.clone(), reason: format!("class_def {i}: {e}") });
38 }
39 Ok(())
40 }
41}
42
43#[derive(Debug)]
45pub struct EntryClasses {
46 pub entry_name: String,
47 pub result: Result<Vec<DexClasses>>,
48}
49
50enum Prefix {
52 Bytes(Vec<u8>),
54 Decoded(String),
57}
58
59impl Prefix {
60 fn new(prefix: &str) -> Prefix {
61 if prefix.is_ascii() { Prefix::Bytes(prefix.as_bytes().to_vec()) } else { Prefix::Decoded(prefix.to_string()) }
62 }
63
64 fn matches(&self, raw: &[u8]) -> Option<String> {
66 match self {
67 Prefix::Bytes(p) => raw.starts_with(p).then(|| mutf8::decode(raw)),
68 Prefix::Decoded(p) => Some(mutf8::decode(raw)).filter(|name| name.starts_with(p.as_str())),
69 }
70 }
71}
72
73pub fn dex_classes(dex_name: &str, buf: &[u8], prefix: Option<&str>) -> DexClasses {
77 dex_classes_with(dex_name, buf, prefix.map(Prefix::new).as_ref(), false)
78}
79
80fn dex_classes_with(dex_name: &str, buf: &[u8], prefix: Option<&Prefix>, strict: bool) -> DexClasses {
82 let mut out = DexClasses { dex_name: dex_name.to_string(), ..Default::default() };
83 let dex = match Dex::with_name(buf, dex_name) {
84 Ok(d) => d,
85 Err(e) => {
86 out.error = Some(e);
87 return out;
88 }
89 };
90 let defs = dex.header().class_defs;
93 if defs.size == 0 {
94 return out;
95 }
96 if defs.off as u64 + defs.size as u64 * asc_dex::ClassDef::SIZE as u64 > buf.len() as u64 {
97 out.error = Some(DexError::BadRange { what: "class_defs", offset: defs.off as usize });
98 return out;
99 }
100 for (i, def) in dex.class_defs().enumerate() {
101 let bytes = def.and_then(|d| dex.type_bytes(d.class_idx));
102 match bytes {
103 Ok(b) => match prefix {
104 None => out.descriptors.push(mutf8::decode(b)),
105 Some(p) => out.descriptors.extend(p.matches(b)),
106 },
107 Err(e) => {
108 out.bad_class_defs += 1;
109 if out.first_bad_class_def.is_none() {
110 out.first_bad_class_def = Some((i as u32, e));
111 }
112 if strict {
113 break;
114 }
115 }
116 }
117 }
118 out
119}
120
121impl Engine {
122 pub fn scan_classes(&self, prefix: Option<&str>, progress: impl Fn(usize, usize) + Sync) -> Result<Vec<EntryClasses>> {
131 self.scan(prefix, false, progress)
132 }
133
134 fn scan(&self, prefix: Option<&str>, strict: bool, progress: impl Fn(usize, usize) + Sync) -> Result<Vec<EntryClasses>> {
137 let t0 = Instant::now();
138 let apk = self.open()?;
139 let entries = apk.dex_entries_in_order()?;
140 let key = self.apk_key(&apk);
141 let total = entries.len();
142 let mut order: Vec<usize> = (0..total).collect();
143 order.sort_by_key(|&i| std::cmp::Reverse(entries[i].comp_size));
144 let prefix = prefix.map(Prefix::new);
145 let slots: Vec<Mutex<Option<EntryClasses>>> = (0..total).map(|_| Mutex::new(None)).collect();
146 let next = AtomicUsize::new(0);
147 let done = AtomicUsize::new(0);
148 let workers = self.threads().min(total).max(1);
149 std::thread::scope(|s| {
150 for _ in 0..workers {
151 let (apk, entries, order, slots, key, prefix) = (&apk, &entries, &order, &slots, key.as_ref(), prefix.as_ref());
152 let (next, done, progress) = (&next, &done, &progress);
153 s.spawn(move || {
154 while let Some(&i) = order.get(next.fetch_add(1, Ordering::Relaxed)) {
155 let entry = &entries[i];
156 let result = load_entry(apk, key, entry, self.cache.as_deref(), &self.stats, None).map(|(loaded, _)| {
157 logical_dex_iter(&entry.name, loaded.bytes())
158 .map(|(name, buf)| dex_classes_with(&name, &buf, prefix, strict))
159 .collect()
160 });
161 *slots[i].lock().unwrap() = Some(EntryClasses { entry_name: entry.name.clone(), result });
162 progress(done.fetch_add(1, Ordering::Relaxed) + 1, total);
163 }
164 });
165 }
166 });
167 let out: Vec<EntryClasses> = slots.into_iter().map(|s| s.into_inner().unwrap().expect("every entry is scanned")).collect();
168 if self.debug {
169 let mut count = 0;
170 for e in &out {
171 for d in e.result.iter().flatten() {
172 count += d.descriptors.len();
173 self.log(format!("[APK] '{}' class_count={}", d.dex_name, d.descriptors.len()));
174 }
175 }
176 self.log(format!("[APK] listclass total={} us count={count} workers={workers}", t0.elapsed().as_micros()));
177 }
178 Ok(out)
179 }
180
181 pub fn list_classes(&self, prefix: Option<&str>) -> Result<Vec<ClassListing>> {
185 let prefix = prefix.map(names::class_prefix).transpose()?;
186 let mut out = Vec::new();
187 for entry in self.scan(prefix.as_deref(), true, |_, _| {})? {
188 for dex in entry.result? {
189 dex.check()?;
190 let dex_name = dex.dex_name;
191 out.extend(dex.descriptors.into_iter().map(|descriptor| ClassListing { descriptor, dex_name: dex_name.clone() }));
192 }
193 }
194 Ok(out)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn dex_classes_filters_by_raw_prefix_and_reports_bad_defs() {
204 let dex = asc_fixture::make_dex();
205 let all = dex_classes("classes.dex", &dex, None);
206 assert_eq!(all.descriptors, ["Lexample/Test;"]);
207 assert!(all.check().is_ok());
208 assert!(dex_classes("classes.dex", &dex, Some("Lexample/")).descriptors.len() == 1);
209 assert!(dex_classes("classes.dex", &dex, Some("Lother")).descriptors.is_empty());
210
211 let mut bad = dex.clone();
212 let off = u32::from_le_bytes(bad[0x64..0x68].try_into().unwrap()) as usize;
213 bad[off..off + 4].copy_from_slice(&u32::MAX.to_le_bytes());
214 let r = dex_classes("classes.dex", &bad, None);
215 assert!(r.descriptors.is_empty());
216 assert_eq!(r.bad_class_defs, 1);
217 let msg = r.check().unwrap_err().to_string();
218 assert!(msg.starts_with("classes.dex: class_def 0: type index"), "{msg}");
219
220 let mut huge = dex.clone();
221 huge[0x60..0x64].copy_from_slice(&u32::MAX.to_le_bytes());
222 let r = dex_classes("classes.dex", &huge, None);
223 assert!(matches!(r.error, Some(DexError::BadRange { what: "class_defs", .. })));
224
225 let mut none = dex.clone();
227 none[0x60..0x68].copy_from_slice(&[0, 0, 0, 0, 0x00, 0xff, 0xff, 0xff]);
228 let r = dex_classes("classes.dex", &none, None);
229 assert_eq!(r.error, None);
230 assert!(r.descriptors.is_empty() && r.check().is_ok());
231
232 let r = dex_classes("x.dex", b"not a dex", None);
233 assert!(r.error.is_some() && r.check().is_err());
234
235 let only = asc_fixture::make_class_only_dex(&["Lb/B;", "La/A;"], 0, false);
236 assert_eq!(
237 dex_classes("c.dex", &only, None),
238 DexClasses { dex_name: "c.dex".into(), descriptors: vec!["Lb/B;".into(), "La/A;".into()], ..Default::default() }
239 );
240 }
241
242 #[test]
243 fn strict_stops_at_the_first_bad_class_def() {
244 let only = asc_fixture::make_class_only_dex(&["La/A;", "Lb/B;", "Lc/C;"], 0, false);
245 let off = u32::from_le_bytes(only[0x64..0x68].try_into().unwrap()) as usize;
246 let mut bad = only.clone();
247 for i in 0..2 {
248 bad[off + i * 32..off + i * 32 + 4].copy_from_slice(&u32::MAX.to_le_bytes());
249 }
250 let lenient = dex_classes("c.dex", &bad, None);
251 assert_eq!((lenient.bad_class_defs, lenient.descriptors.as_slice()), (2, &["Lc/C;".to_string()][..]));
252 let strict = dex_classes_with("c.dex", &bad, None, true);
253 assert_eq!((strict.bad_class_defs, strict.descriptors.len()), (1, 0));
254 assert_eq!(strict.check().unwrap_err().to_string(), lenient.check().unwrap_err().to_string());
255 }
256
257 #[test]
258 fn non_ascii_prefix_matches_decoded_names() {
259 let name = "L\u{1f600}/A;";
260 let canonical = asc_fixture::make_class_only_dex(&[name], 0, false);
261 let mut four = asc_fixture::make_class_only_dex(&["Lxxxx/A;"], 0, false);
264 let at = four.windows(8).position(|w| w == b"Lxxxx/A;").unwrap();
265 four[at + 1..at + 5].copy_from_slice("\u{1f600}".as_bytes());
266 for data in [&canonical, &four] {
267 assert_eq!(dex_classes("c.dex", data, None).descriptors, [name]);
268 assert_eq!(dex_classes("c.dex", data, Some("L\u{1f600}")).descriptors, [name]);
269 assert_eq!(dex_classes("c.dex", data, Some("L\u{1f600}/A")).descriptors, [name]);
270 assert!(dex_classes("c.dex", data, Some("L\u{1f601}")).descriptors.is_empty());
271 }
272 }
273}