1use crate::hierarchy::{ClassGraph, Defining};
10use crate::{CacheConfig, DexView, Engine, EngineError, FindOptions, Lineage, Result, names};
11use asc_dex::Dex;
12use asc_findrefs::{ClassQuery, CompiledQuery, Defined, MemberQuery, Query};
13use std::collections::{BTreeMap, BTreeSet, HashMap};
14use std::sync::atomic::AtomicBool;
15
16pub const DEFAULT_FAN_OUT: usize = 16;
24const MAX_INHERITORS: usize = 10_000;
26const MAX_SUPERS: usize = 1_000;
28const MAX_WALK: usize = 1_024;
34const MAX_LABEL_CLASSES: usize = 3;
36
37pub struct MatchingMethods {
40 pub sets: Vec<BTreeSet<String>>,
42 pub notes: Vec<String>,
44}
45
46pub(crate) struct Resolved {
49 pub queries: Option<Vec<Query>>,
51 pub notes: Vec<Vec<String>>,
53}
54
55impl Resolved {
56 fn nothing(queries: usize) -> Resolved {
57 Resolved { queries: None, notes: vec![Vec::new(); queries] }
58 }
59}
60
61impl Engine {
62 pub fn matching_methods(&self, queries: &[Query], cancel: &AtomicBool) -> Result<MatchingMethods> {
65 if queries.iter().any(|q| q.relation().is_some()) {
66 return Err(EngineError::Query(
67 "extends: / impl: name classes, not code: a method condition needs string, literal, type, method or field conditions"
68 .into(),
69 ));
70 }
71 let mut sets = vec![BTreeSet::new(); queries.len()];
72 let mut errors = Vec::new();
73 let summary = self.find_refs_rules(queries, FindOptions::default(), cancel, &mut |i, batch| {
74 if let Some(e) = batch.error {
75 errors.push(format!("{}: {e}", batch.entry_name));
76 }
77 sets[i].extend(batch.results.into_iter().map(|r| r.signature));
78 })?;
79 if !errors.is_empty() {
81 return Err(EngineError::Query(format!("cannot answer a method condition: {}", errors.join("; "))));
82 }
83 Ok(MatchingMethods { sets, notes: summary.notes })
84 }
85
86 pub(crate) fn resolve(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Resolved> {
91 let defs = self.resolve_defined(queries, cancel)?;
92 let nested = self.resolve_nested(defs.as_deref().unwrap_or(queries), cancel)?;
93 let base = nested.queries.as_deref().or(defs.as_deref()).unwrap_or(queries);
94 let dispatched = self.resolve_dispatch(base, cancel)?;
95 let notes = nested.notes.into_iter().zip(dispatched.notes).map(|(a, b)| merge_notes(a.into_iter().chain(b).collect())).collect();
96 Ok(Resolved { queries: dispatched.queries.or(nested.queries).or(defs), notes })
97 }
98
99 pub(crate) fn transient(&self, needed: bool) -> Result<Option<Engine>> {
105 if !needed || self.cache.is_some() {
106 return Ok(None);
107 }
108 let dex_bytes: u64 = self.open()?.dex_entries()?.iter().map(|e| e.uncomp_size).sum();
110 let memory_bytes = usize::try_from(dex_bytes.saturating_mul(2)).unwrap_or(usize::MAX).max(64 << 20);
111 let mut engine = Engine::new(&self.apk_path).with_threads(self.threads).with_limits(self.limits).with_debug(self.debug);
112 engine.logger = self.logger.clone();
114 engine.stats = self.stats.clone();
115 Ok(Some(engine.with_cache(CacheConfig { memory_bytes, ..Default::default() })))
116 }
117
118 pub(crate) fn resolve_dispatch(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Resolved> {
121 let mut sets = Vec::new();
123 for (i, q) in queries.iter().enumerate() {
124 let mut own = Vec::new();
125 virtual_sets(q, &mut own);
126 sets.extend(own.into_iter().map(|(set, fan_out)| (i, set, fan_out)));
127 }
128 if sets.is_empty() {
129 return Ok(Resolved::nothing(queries.len()));
130 }
131 let graph = if sets.iter().flat_map(|(_, s, _)| s.iter()).any(|m| dispatch_target(m).is_some()) {
134 let (views, mut errors) = self.load_views(None, cancel)?;
135 let graph = self.class_graph(&views, &mut errors, cancel)?;
136 Some((views, graph))
137 } else {
138 None
139 };
140 let mut hierarchy = graph.as_ref().map(|(views, graph)| Hierarchy::new(graph, views)).transpose()?;
141 let mut notes = vec![Vec::new(); queries.len()];
142 let mut expanded = Vec::with_capacity(sets.len());
143 for (i, set, fan_out) in sets {
144 expanded.push(match hierarchy.as_mut() {
145 Some(h) => {
146 let e = h.dispatch_aliases(set, fan_out)?;
147 notes[i].extend(e.skipped.iter().map(|(alias, n)| skipped_note(alias, *n)));
148 (e.set, e.via)
149 }
150 None => (set.to_vec(), BTreeMap::new()),
151 });
152 }
153 let mut expanded = expanded.into_iter();
154 let queries = queries.iter().map(|q| with_aliases(q, &mut expanded)).collect();
155 Ok(Resolved { queries: Some(queries), notes: notes.into_iter().map(merge_notes).collect() })
156 }
157
158 pub(crate) fn resolve_nested(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Resolved> {
161 let mut inner = Vec::new();
162 let mut holds = Vec::with_capacity(queries.len());
163 for q in queries {
164 let before = inner.len();
165 nested_rules(q, &mut inner);
166 holds.push(inner.len() > before);
167 }
168 if inner.is_empty() {
169 return Ok(Resolved::nothing(queries.len()));
170 }
171 let inner: Vec<Query> = inner.into_iter().cloned().collect();
172 let matching = self.matching_methods(&inner, cancel)?;
173 let mut sets = matching.sets.into_iter();
174 let resolved = queries.iter().map(|q| substitute(q, &mut sets)).collect::<Result<_>>()?;
175 let notes = holds.into_iter().map(|h| if h { matching.notes.clone() } else { Vec::new() }).collect();
176 Ok(Resolved { queries: Some(resolved), notes })
177 }
178}
179
180fn skipped_note(alias: &str, defining: Defining) -> String {
182 let why = match defining {
183 Defining { count, complete: true } => format!("{count} implementers"),
184 Defining { count, complete: false } => format!("over {MAX_WALK} subclasses, {count}+ implementers"),
185 };
186 format!("dispatch not followed via {alias} ({why}): name it explicitly, or write method:*{{...}}, to include")
187}
188
189pub(crate) fn merge_notes(mut notes: Vec<String>) -> Vec<String> {
191 notes.sort();
192 notes.dedup();
193 notes
194}
195
196pub(crate) fn needs_program(q: &Query) -> bool {
199 match q {
200 Query::Nested(_) | Query::Virtual { .. } | Query::Defined(Defined::Override(_) | Defined::In(_)) => true,
201 Query::All { all, none } => all.iter().chain(none).any(needs_program),
202 Query::Call { target, .. } => needs_program(target),
203 Query::String(_)
204 | Query::StringLiteral(_)
205 | Query::Type(_)
206 | Query::Method(_)
207 | Query::MethodSet(_)
208 | Query::Defined(_)
209 | Query::Field(_)
210 | Query::FieldAccess { .. }
211 | Query::Extends(_)
212 | Query::Implements(_) => false,
213 }
214}
215
216pub(crate) fn check_patterns(q: &Query) -> Result<()> {
219 let mut inner = Vec::new();
220 nested_rules(q, &mut inner);
221 for rule in inner {
222 check_patterns(rule)?;
223 }
224 CompiledQuery::new(&substitute(q, &mut std::iter::repeat_with(BTreeSet::new))?)?;
225 Ok(())
226}
227
228fn nested_rules<'q>(q: &'q Query, out: &mut Vec<&'q Query>) {
231 match q {
232 Query::Nested(inner) => out.push(inner),
233 Query::All { all, none } => all.iter().chain(none).for_each(|p| nested_rules(p, out)),
234 Query::Call { target, .. } | Query::Virtual { target, .. } => nested_rules(target, out),
235 Query::String(_)
236 | Query::StringLiteral(_)
237 | Query::Type(_)
238 | Query::Method(_)
239 | Query::MethodSet(_)
240 | Query::Defined(_)
241 | Query::Field(_)
242 | Query::FieldAccess { .. }
243 | Query::Extends(_)
244 | Query::Implements(_) => {}
245 }
246}
247
248fn substitute(q: &Query, sets: &mut impl Iterator<Item = BTreeSet<String>>) -> Result<Query> {
250 Ok(match q {
251 Query::Nested(_) => {
252 let methods = sets.next().expect("one method set per nested rule");
253 Query::MethodSet(methods.iter().map(|m| names::exact_method(m)).collect::<Result<_>>()?)
254 }
255 Query::All { all, none } => Query::All {
256 all: all.iter().map(|p| substitute(p, sets)).collect::<Result<_>>()?,
257 none: none.iter().map(|p| substitute(p, sets)).collect::<Result<_>>()?,
258 },
259 Query::Call { target, constraints } => {
260 Query::Call { target: Box::new(substitute(target, sets)?), constraints: constraints.clone() }
261 }
262 Query::Virtual { target, via, fan_out } => {
263 Query::Virtual { target: Box::new(substitute(target, sets)?), via: via.clone(), fan_out: *fan_out }
264 }
265 Query::Defined(Defined::Override(_) | Defined::In(_)) => {
268 Query::Defined(Defined::Members { classes: Vec::new(), name: None, proto: None })
269 }
270 q => q.clone(),
271 })
272}
273
274fn virtual_sets<'q>(q: &'q Query, out: &mut Vec<(&'q [MemberQuery], Option<usize>)>) {
279 match q {
280 Query::Virtual { target, via, fan_out } if via.is_empty() => {
281 if let Query::MethodSet(set) = &**target {
282 out.push((set, *fan_out));
283 }
284 }
285 Query::All { all, none } => all.iter().chain(none).for_each(|p| virtual_sets(p, out)),
286 Query::Call { target, .. } => virtual_sets(target, out),
287 _ => {}
288 }
289}
290
291fn with_aliases(q: &Query, sets: &mut impl Iterator<Item = (Vec<MemberQuery>, BTreeMap<String, String>)>) -> Query {
293 match q {
294 Query::Virtual { target, via, fan_out } if via.is_empty() && matches!(**target, Query::MethodSet(_)) => {
295 let (set, via) = sets.next().expect("one expansion per dispatch set");
296 Query::Virtual { target: Box::new(Query::MethodSet(set)), via, fan_out: *fan_out }
297 }
298 Query::All { all, none } => Query::All {
299 all: all.iter().map(|p| with_aliases(p, sets)).collect(),
300 none: none.iter().map(|p| with_aliases(p, sets)).collect(),
301 },
302 Query::Call { target, constraints } => {
303 Query::Call { target: Box::new(with_aliases(target, sets)), constraints: constraints.clone() }
304 }
305 q => q.clone(),
306 }
307}
308
309fn dispatch_target(m: &MemberQuery) -> Option<(&str, &str, Option<String>)> {
313 let class = m.class.as_ref().filter(|c| c.precise)?;
314 let name = m.name.as_deref().filter(|n| m.exact_name && !n.starts_with('<'))?;
315 Some((&class.pattern, name, m.signature.as_ref().and_then(crate::defs::descriptor_proto)))
316}
317
318struct Expansion {
320 set: Vec<MemberQuery>,
322 via: BTreeMap<String, String>,
324 skipped: BTreeMap<String, Defining>,
327}
328
329struct Hierarchy<'g> {
333 graph: &'g ClassGraph,
334 dexes: Vec<Dex<'g>>,
336 declared: HashMap<String, (usize, Vec<u32>)>,
338 defining: HashMap<String, Defining>,
340}
341
342impl<'g> Hierarchy<'g> {
343 fn new(graph: &'g ClassGraph, views: &'g [DexView]) -> Result<Hierarchy<'g>> {
344 let dexes = views.iter().map(|v| Dex::with_name(&v.data, &v.name)).collect::<std::result::Result<_, _>>()?;
345 Ok(Hierarchy { graph, dexes, declared: HashMap::new(), defining: HashMap::new() })
346 }
347
348 fn defines(&mut self, class: &str, name: &str, proto: Option<&str>) -> Result<bool> {
351 if !self.declared.contains_key(class) {
352 let methods = match self.graph.definition(class) {
353 Some((view, def_idx)) => {
354 let dex = &self.dexes[view];
355 let def = dex.class_def(def_idx)?;
356 let ids = if def.class_data_off == 0 {
357 Vec::new()
358 } else {
359 let data = dex.class_data(def.class_data_off)?;
360 data.direct_methods.iter().chain(&data.virtual_methods).map(|m| m.method_idx).collect()
361 };
362 (view, ids)
363 }
364 None => (0, Vec::new()),
365 };
366 self.declared.insert(class.to_string(), methods);
367 }
368 let (view, ids) = &self.declared[class];
369 let Some(dex) = self.dexes.get(*view) else { return Ok(false) };
370 for &idx in ids {
371 let mid = dex.method_id(idx)?;
372 if dex.string_bytes(mid.name_idx)? == name.as_bytes()
373 && proto.is_none_or(|want| asc_smali::proto_descriptor(dex, u32::from(mid.proto_idx)).is_ok_and(|p| p == want))
374 {
375 return Ok(true);
376 }
377 }
378 Ok(false)
379 }
380
381 fn has_member(&mut self, class: &str, name: &str, proto: Option<&str>) -> Result<bool> {
385 let graph = self.graph;
386 for c in std::iter::once(class.to_string()).chain(graph.reachable(class, Lineage::Supers, MAX_SUPERS)) {
387 if c == "Ljava/lang/Object;" {
388 continue;
389 }
390 if graph.definition(&c).is_none() || self.defines(&c, name, proto)? {
391 return Ok(true);
392 }
393 }
394 Ok(false)
395 }
396
397 fn dispatch_aliases(&mut self, set: &[MemberQuery], fan_out: Option<usize>) -> Result<Expansion> {
410 let key = |class: &str, name: &str, proto: Option<&str>| format!("{class}->{name}{}", proto.unwrap_or(""));
411 let originals: BTreeSet<String> = set.iter().filter_map(dispatch_target).map(|(c, n, p)| key(c, n, p.as_deref())).collect();
412 let mut aliases: BTreeMap<String, (MemberQuery, BTreeSet<&str>)> = BTreeMap::new();
414 let mut skipped = BTreeMap::new();
415 let graph = self.graph;
416 for m in set {
417 let Some((class, name, proto)) = dispatch_target(m) else { continue };
418 let proto = proto.as_deref();
419 let inheritors = graph.inheritors(class, MAX_INHERITORS, |c| self.defines(c, name, proto))?;
420 let subclasses = inheritors.iter().skip(1).map(|c| (c.clone(), false));
421 let supers = inheritors.iter().flat_map(|c| graph.reachable(c, Lineage::Supers, MAX_SUPERS)).map(|s| (s, true));
422 for (on, is_super) in subclasses.chain(supers) {
423 let k = key(&on, name, proto);
424 if on == "Ljava/lang/Object;" || originals.contains(&k) || skipped.contains_key(&k) {
425 continue;
426 }
427 if is_super && !self.has_member(&on, name, proto)? {
429 continue;
430 }
431 if let (true, Some(limit)) = (is_super, fan_out) {
432 let n = match self.defining.get(&k) {
433 Some(&n) => n,
434 None => {
435 let n = graph.defining(&on, MAX_WALK, |c| self.defines(c, name, proto))?;
436 self.defining.insert(k.clone(), n);
437 n
438 }
439 };
440 if !n.complete || n.count > limit {
441 aliases.remove(&k);
442 skipped.insert(k, n);
443 continue;
444 }
445 }
446 let (_, stands_for) = aliases.entry(k).or_insert_with(|| {
447 (MemberQuery { class: Some(ClassQuery { pattern: on, precise: true }), ..m.clone() }, BTreeSet::new())
448 });
449 stands_for.insert(class);
450 }
451 }
452 let mut out = set.to_vec();
453 let mut via = BTreeMap::new();
454 for (k, (q, stands_for)) in aliases {
455 out.push(q);
456 via.insert(k, label(&stands_for));
457 }
458 Ok(Expansion { set: out, via, skipped })
459 }
460}
461
462fn label(classes: &BTreeSet<&str>) -> String {
464 let mut out = classes.iter().take(MAX_LABEL_CLASSES).copied().collect::<Vec<_>>().join("|");
465 if classes.len() > MAX_LABEL_CLASSES {
466 out.push_str(&format!(" +{}", classes.len() - MAX_LABEL_CLASSES));
467 }
468 out
469}