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
37#[non_exhaustive]
40pub struct MatchingMethods {
41 pub sets: Vec<BTreeSet<String>>,
43 pub notes: Vec<String>,
45}
46
47pub(crate) struct Resolved {
50 pub queries: Option<Vec<Query>>,
52 pub notes: Vec<Vec<String>>,
54}
55
56impl Resolved {
57 fn nothing(queries: usize) -> Resolved {
58 Resolved { queries: None, notes: vec![Vec::new(); queries] }
59 }
60}
61
62impl Engine {
63 pub fn matching_methods(&self, queries: &[Query], cancel: &AtomicBool) -> Result<MatchingMethods> {
73 if queries.iter().any(|q| q.relation().is_some()) {
74 return Err(EngineError::Query(
75 "extends: / impl: name classes, not code: a method condition needs string, literal, type, method or field conditions"
76 .into(),
77 ));
78 }
79 let mut sets = vec![BTreeSet::new(); queries.len()];
80 let mut errors = Vec::new();
81 let summary = self.find_refs_rules(queries, FindOptions::default(), cancel, &mut |i, batch| {
82 if let Some(e) = batch.error {
83 errors.push(format!("{}: {e}", batch.entry_name));
84 }
85 sets[i].extend(batch.results.into_iter().map(|r| r.signature));
86 })?;
87 if !errors.is_empty() {
89 return Err(EngineError::Query(format!("cannot answer a method condition: {}", errors.join("; "))));
90 }
91 Ok(MatchingMethods { sets, notes: summary.notes })
92 }
93
94 pub(crate) fn resolve(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Resolved> {
99 let defs = self.resolve_defined(queries, cancel)?;
100 let nested = self.resolve_nested(defs.as_deref().unwrap_or(queries), cancel)?;
101 let base = nested.queries.as_deref().or(defs.as_deref()).unwrap_or(queries);
102 let dispatched = self.resolve_dispatch(base, cancel)?;
103 let notes = nested.notes.into_iter().zip(dispatched.notes).map(|(a, b)| merge_notes(a.into_iter().chain(b).collect())).collect();
104 Ok(Resolved { queries: dispatched.queries.or(nested.queries).or(defs), notes })
105 }
106
107 pub(crate) fn transient(&self, needed: bool) -> Result<Option<Engine>> {
113 if !needed || self.cache.is_some() {
114 return Ok(None);
115 }
116 let dex_bytes: u64 = self.open()?.dex_entries()?.iter().map(|e| e.uncomp_size).sum();
118 let memory_bytes = usize::try_from(dex_bytes.saturating_mul(2)).unwrap_or(usize::MAX).max(64 << 20);
119 let mut engine = Engine::new(&self.apk_path).with_threads(self.threads).with_limits(self.limits).with_debug(self.debug);
120 engine.logger = self.logger.clone();
122 engine.stats = self.stats.clone();
123 Ok(Some(engine.with_cache(CacheConfig { memory_bytes, ..Default::default() })))
124 }
125
126 pub(crate) fn resolve_dispatch(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Resolved> {
129 let mut sets = Vec::new();
131 for (i, q) in queries.iter().enumerate() {
132 let mut own = Vec::new();
133 virtual_sets(q, &mut own);
134 sets.extend(own.into_iter().map(|(set, fan_out)| (i, set, fan_out)));
135 }
136 if sets.is_empty() {
137 return Ok(Resolved::nothing(queries.len()));
138 }
139 let graph = if sets.iter().flat_map(|(_, s, _)| s.iter()).any(|m| dispatch_target(m).is_some()) {
142 let (views, mut errors) = self.load_views(None, cancel)?;
143 let graph = self.class_graph(&views, &mut errors, cancel)?;
144 Some((views, graph))
145 } else {
146 None
147 };
148 let mut hierarchy = graph.as_ref().map(|(views, graph)| Hierarchy::new(graph, views)).transpose()?;
149 let mut notes = vec![Vec::new(); queries.len()];
150 let mut expanded = Vec::with_capacity(sets.len());
151 for (i, set, fan_out) in sets {
152 expanded.push(match hierarchy.as_mut() {
153 Some(h) => {
154 let e = h.dispatch_aliases(set, fan_out)?;
155 notes[i].extend(e.skipped.iter().map(|(alias, n)| skipped_note(alias, *n)));
156 (e.set, e.via)
157 }
158 None => (set.to_vec(), BTreeMap::new()),
159 });
160 }
161 let mut expanded = expanded.into_iter();
162 let queries = queries.iter().map(|q| with_aliases(q, &mut expanded)).collect();
163 Ok(Resolved { queries: Some(queries), notes: notes.into_iter().map(merge_notes).collect() })
164 }
165
166 pub(crate) fn resolve_nested(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Resolved> {
169 let mut inner = Vec::new();
170 let mut holds = Vec::with_capacity(queries.len());
171 for q in queries {
172 let before = inner.len();
173 nested_rules(q, &mut inner);
174 holds.push(inner.len() > before);
175 }
176 if inner.is_empty() {
177 return Ok(Resolved::nothing(queries.len()));
178 }
179 let inner: Vec<Query> = inner.into_iter().cloned().collect();
180 let matching = self.matching_methods(&inner, cancel)?;
181 let mut sets = matching.sets.into_iter();
182 let resolved = queries.iter().map(|q| substitute(q, &mut sets)).collect::<Result<_>>()?;
183 let notes = holds.into_iter().map(|h| if h { matching.notes.clone() } else { Vec::new() }).collect();
184 Ok(Resolved { queries: Some(resolved), notes })
185 }
186}
187
188fn skipped_note(alias: &str, defining: Defining) -> String {
190 let why = match defining {
191 Defining { count, complete: true } => format!("{count} implementers"),
192 Defining { count, complete: false } => format!("over {MAX_WALK} subclasses, {count}+ implementers"),
193 };
194 format!("dispatch not followed via {alias} ({why}): name it explicitly, or write method:*{{...}}, to include")
195}
196
197pub(crate) fn merge_notes(mut notes: Vec<String>) -> Vec<String> {
199 notes.sort();
200 notes.dedup();
201 notes
202}
203
204pub(crate) fn needs_program(q: &Query) -> bool {
207 match q {
208 Query::Nested(_) | Query::Virtual { .. } | Query::Defined(Defined::Override(_) | Defined::In(_)) => true,
209 Query::All { all, none } => all.iter().chain(none).any(needs_program),
210 Query::Call { target, .. } => needs_program(target),
211 Query::String(_)
212 | Query::StringLiteral(_)
213 | Query::Type(_)
214 | Query::Method(_)
215 | Query::MethodSet(_)
216 | Query::Defined(_)
217 | Query::Field(_)
218 | Query::FieldAccess { .. }
219 | Query::Extends(_)
220 | Query::Implements(_) => false,
221 }
222}
223
224pub(crate) fn check_patterns(q: &Query) -> Result<()> {
227 let mut inner = Vec::new();
228 nested_rules(q, &mut inner);
229 for rule in inner {
230 check_patterns(rule)?;
231 }
232 CompiledQuery::new(&substitute(q, &mut std::iter::repeat_with(BTreeSet::new))?)?;
233 Ok(())
234}
235
236fn nested_rules<'q>(q: &'q Query, out: &mut Vec<&'q Query>) {
239 match q {
240 Query::Nested(inner) => out.push(inner),
241 Query::All { all, none } => all.iter().chain(none).for_each(|p| nested_rules(p, out)),
242 Query::Call { target, .. } | Query::Virtual { target, .. } => nested_rules(target, out),
243 Query::String(_)
244 | Query::StringLiteral(_)
245 | Query::Type(_)
246 | Query::Method(_)
247 | Query::MethodSet(_)
248 | Query::Defined(_)
249 | Query::Field(_)
250 | Query::FieldAccess { .. }
251 | Query::Extends(_)
252 | Query::Implements(_) => {}
253 }
254}
255
256fn substitute(q: &Query, sets: &mut impl Iterator<Item = BTreeSet<String>>) -> Result<Query> {
258 Ok(match q {
259 Query::Nested(_) => {
260 let methods = sets.next().expect("one method set per nested rule");
261 Query::MethodSet(methods.iter().map(|m| names::exact_method(m)).collect::<Result<_>>()?)
262 }
263 Query::All { all, none } => Query::All {
264 all: all.iter().map(|p| substitute(p, sets)).collect::<Result<_>>()?,
265 none: none.iter().map(|p| substitute(p, sets)).collect::<Result<_>>()?,
266 },
267 Query::Call { target, constraints } => {
268 Query::Call { target: Box::new(substitute(target, sets)?), constraints: constraints.clone() }
269 }
270 Query::Virtual { target, via, fan_out } => {
271 Query::Virtual { target: Box::new(substitute(target, sets)?), via: via.clone(), fan_out: *fan_out }
272 }
273 Query::Defined(Defined::Override(_) | Defined::In(_)) => {
276 Query::Defined(Defined::Members { classes: Vec::new(), name: None, proto: None })
277 }
278 q => q.clone(),
279 })
280}
281
282fn virtual_sets<'q>(q: &'q Query, out: &mut Vec<(&'q [MemberQuery], Option<usize>)>) {
287 match q {
288 Query::Virtual { target, via, fan_out } if via.is_empty() => {
289 if let Query::MethodSet(set) = &**target {
290 out.push((set, *fan_out));
291 }
292 }
293 Query::All { all, none } => all.iter().chain(none).for_each(|p| virtual_sets(p, out)),
294 Query::Call { target, .. } => virtual_sets(target, out),
295 _ => {}
296 }
297}
298
299fn with_aliases(q: &Query, sets: &mut impl Iterator<Item = (Vec<MemberQuery>, BTreeMap<String, String>)>) -> Query {
301 match q {
302 Query::Virtual { target, via, fan_out } if via.is_empty() && matches!(**target, Query::MethodSet(_)) => {
303 let (set, via) = sets.next().expect("one expansion per dispatch set");
304 Query::Virtual { target: Box::new(Query::MethodSet(set)), via, fan_out: *fan_out }
305 }
306 Query::All { all, none } => Query::All {
307 all: all.iter().map(|p| with_aliases(p, sets)).collect(),
308 none: none.iter().map(|p| with_aliases(p, sets)).collect(),
309 },
310 Query::Call { target, constraints } => {
311 Query::Call { target: Box::new(with_aliases(target, sets)), constraints: constraints.clone() }
312 }
313 q => q.clone(),
314 }
315}
316
317fn dispatch_target(m: &MemberQuery) -> Option<(&str, &str, Option<String>)> {
321 let class = m.class.as_ref().filter(|c| c.precise)?;
322 let name = m.name.as_deref().filter(|n| m.exact_name && !n.starts_with('<'))?;
323 Some((&class.pattern, name, m.signature.as_ref().and_then(crate::defs::descriptor_proto)))
324}
325
326struct Expansion {
328 set: Vec<MemberQuery>,
330 via: BTreeMap<String, String>,
332 skipped: BTreeMap<String, Defining>,
335}
336
337struct Hierarchy<'g> {
341 graph: &'g ClassGraph,
342 dexes: Vec<Dex<'g>>,
344 declared: HashMap<String, (usize, Vec<u32>)>,
346 defining: HashMap<String, Defining>,
348}
349
350impl<'g> Hierarchy<'g> {
351 fn new(graph: &'g ClassGraph, views: &'g [DexView]) -> Result<Hierarchy<'g>> {
352 let dexes = views.iter().map(|v| Dex::with_name(&v.data, &v.name)).collect::<std::result::Result<_, _>>()?;
353 Ok(Hierarchy { graph, dexes, declared: HashMap::new(), defining: HashMap::new() })
354 }
355
356 fn defines(&mut self, class: &str, name: &str, proto: Option<&str>) -> Result<bool> {
359 if !self.declared.contains_key(class) {
360 let methods = match self.graph.definition(class) {
361 Some((view, def_idx)) => {
362 let dex = &self.dexes[view];
363 let def = dex.class_def(def_idx)?;
364 let ids = if def.class_data_off == 0 {
365 Vec::new()
366 } else {
367 let data = dex.class_data(def.class_data_off)?;
368 data.direct_methods.iter().chain(&data.virtual_methods).map(|m| m.method_idx).collect()
369 };
370 (view, ids)
371 }
372 None => (0, Vec::new()),
373 };
374 self.declared.insert(class.to_string(), methods);
375 }
376 let (view, ids) = &self.declared[class];
377 let Some(dex) = self.dexes.get(*view) else { return Ok(false) };
378 for &idx in ids {
379 let mid = dex.method_id(idx)?;
380 if dex.string_bytes(mid.name_idx)? == name.as_bytes()
381 && proto.is_none_or(|want| asc_smali::proto_descriptor(dex, u32::from(mid.proto_idx)).is_ok_and(|p| p == want))
382 {
383 return Ok(true);
384 }
385 }
386 Ok(false)
387 }
388
389 fn has_member(&mut self, class: &str, name: &str, proto: Option<&str>) -> Result<bool> {
393 let graph = self.graph;
394 for c in std::iter::once(class.to_string()).chain(graph.reachable(class, Lineage::Supers, MAX_SUPERS)) {
395 if c == "Ljava/lang/Object;" {
396 continue;
397 }
398 if graph.definition(&c).is_none() || self.defines(&c, name, proto)? {
399 return Ok(true);
400 }
401 }
402 Ok(false)
403 }
404
405 fn dispatch_aliases(&mut self, set: &[MemberQuery], fan_out: Option<usize>) -> Result<Expansion> {
418 let key = |class: &str, name: &str, proto: Option<&str>| format!("{class}->{name}{}", proto.unwrap_or(""));
419 let originals: BTreeSet<String> = set.iter().filter_map(dispatch_target).map(|(c, n, p)| key(c, n, p.as_deref())).collect();
420 let mut aliases: BTreeMap<String, (MemberQuery, BTreeSet<&str>)> = BTreeMap::new();
422 let mut skipped = BTreeMap::new();
423 let graph = self.graph;
424 for m in set {
425 let Some((class, name, proto)) = dispatch_target(m) else { continue };
426 let proto = proto.as_deref();
427 let inheritors = graph.inheritors(class, MAX_INHERITORS, |c| self.defines(c, name, proto))?;
428 let subclasses = inheritors.iter().skip(1).map(|c| (c.clone(), false));
429 let supers = inheritors.iter().flat_map(|c| graph.reachable(c, Lineage::Supers, MAX_SUPERS)).map(|s| (s, true));
430 for (on, is_super) in subclasses.chain(supers) {
431 let k = key(&on, name, proto);
432 if on == "Ljava/lang/Object;" || originals.contains(&k) || skipped.contains_key(&k) {
433 continue;
434 }
435 if is_super && !self.has_member(&on, name, proto)? {
437 continue;
438 }
439 if let (true, Some(limit)) = (is_super, fan_out) {
440 let n = match self.defining.get(&k) {
441 Some(&n) => n,
442 None => {
443 let n = graph.defining(&on, MAX_WALK, |c| self.defines(c, name, proto))?;
444 self.defining.insert(k.clone(), n);
445 n
446 }
447 };
448 if !n.complete || n.count > limit {
449 aliases.remove(&k);
450 skipped.insert(k, n);
451 continue;
452 }
453 }
454 let (_, stands_for) =
455 aliases.entry(k).or_insert_with(|| (m.clone().with_class(Some(ClassQuery::exact(on))), BTreeSet::new()));
456 stands_for.insert(class);
457 }
458 }
459 let mut out = set.to_vec();
460 let mut via = BTreeMap::new();
461 for (k, (q, stands_for)) in aliases {
462 out.push(q);
463 via.insert(k, label(&stands_for));
464 }
465 Ok(Expansion { set: out, via, skipped })
466 }
467}
468
469fn label(classes: &BTreeSet<&str>) -> String {
471 let mut out = classes.iter().take(MAX_LABEL_CLASSES).copied().collect::<Vec<_>>().join("|");
472 if classes.len() > MAX_LABEL_CLASSES {
473 out.push_str(&format!(" +{}", classes.len() - MAX_LABEL_CLASSES));
474 }
475 out
476}