1use crate::{
9 Engine, FindOptions, Job, RefBatch, RefContext, RefResult, RefSite, RefSummary, Result, Rows, SearchTarget, Shape, WriteJob,
10 attach_context, hit_matched, results_from_hits, rows_compiled, search_entry_with, skip, tiers,
11};
12use asc_apk::Entry;
13use asc_cache::Tier;
14use asc_findrefs::{CompiledQuery, Compound, Defined, FieldAccess, FindRefs, Query};
15use std::collections::BTreeMap;
16use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
17use std::sync::{Arc, mpsc};
18use std::time::Instant;
19
20pub(crate) fn compound_rows(fr: &FindRefs<'_, '_, '_>, c: &Compound, opts: FindOptions) -> Result<Vec<RefResult>> {
25 let dex = fr.dex();
26 let code_all: Vec<&CompiledQuery> = c.all.iter().filter(|q| q.defined().is_none()).collect();
29 let code_none: Vec<&CompiledQuery> = c.none.iter().filter(|q| q.defined().is_none()).collect();
30 let def_all: Vec<&Defined> = c.all.iter().filter_map(|q| q.defined()).collect();
31 let def_none: Vec<&Defined> = c.none.iter().filter_map(|q| q.defined()).collect();
32
33 let parts: Vec<&CompiledQuery> = code_all.iter().chain(&code_none).copied().collect();
34 let hits = fr.find_many(&parts)?;
35 let n = code_all.len();
36 type PartHits<'h> = Vec<(u32, u32, Option<&'h str>, Option<FieldAccess>)>;
38 let mut methods: BTreeMap<u32, Vec<PartHits<'_>>> = BTreeMap::new();
39 if n == 0 {
40 let seed = defined_method_set(dex, def_all[0])?;
42 for m in seed {
43 methods.insert(m, Vec::new());
44 }
45 } else {
46 for (p, part_hits) in hits[..n].iter().enumerate() {
47 for h in part_hits {
48 let Some(ms) = &h.methods else { continue };
49 for &m in ms.ids() {
50 let slot = if p == 0 { Some(methods.entry(m).or_insert_with(|| vec![Vec::new(); n])) } else { methods.get_mut(&m) };
52 if let Some(slot) = slot {
53 slot[p].push((h.offset, h.matched_idx, h.note.as_deref(), h.field_access));
54 }
55 }
56 }
57 }
58 methods.retain(|_, per_part| per_part.iter().all(|h| !h.is_empty()));
59 }
60 for d in def_all.iter().skip(usize::from(n == 0)) {
64 retain_defined(dex, &mut methods, d, true)?;
65 }
66 for h in hits[n..].iter().flatten() {
68 for &m in h.methods.iter().flat_map(|ms| ms.ids()) {
69 methods.remove(&m);
70 }
71 }
72 for d in &def_none {
73 retain_defined(dex, &mut methods, d, false)?;
74 }
75 let def_labels: Vec<String> = def_all.iter().map(|d| d.label()).collect();
76 let mut rows = Vec::with_capacity(methods.len());
77 for (m, per_part) in methods {
78 let mut matched = Vec::new();
79 let mut sites = Vec::new();
80 for (part, part_hits) in code_all.iter().zip(per_part) {
81 let mut idxs: Vec<(u32, Option<&str>, Option<FieldAccess>)> =
82 part_hits.iter().map(|&(_, i, note, access)| (i, note, access)).collect();
83 idxs.sort_unstable();
84 idxs.dedup();
85 let base = matched.len();
86 for &(i, note, access) in &idxs {
87 matched.push(hit_matched(dex, part.kind(), i, note, access)?);
88 }
89 for (offset, i, note, access) in part_hits {
90 sites.push(RefSite {
91 offset,
92 matched: base + idxs.binary_search(&(i, note, access)).unwrap_or(0),
93 field_access: access,
94 pc: None,
95 line: None,
96 });
97 }
98 }
99 for label in &def_labels {
100 matched.push(crate::Matched::Symbol(label.clone()));
101 }
102 sites.sort_by_key(|s| s.offset);
103 rows.push(RefResult::method_row(dex, m, matched, sites)?);
104 }
105 if opts.context != RefContext::None {
106 attach_context(fr, &mut rows, opts.context)?;
107 }
108 Ok(rows)
109}
110
111fn retain_defined<V>(dex: &asc_dex::Dex<'_>, methods: &mut BTreeMap<u32, V>, d: &Defined, keep: bool) -> Result<()> {
114 let mut filter = crate::defs::DefinedFilter::new(dex, d)?;
115 let mut out = Vec::new();
116 for &m in methods.keys() {
117 if filter.matches(m)? != keep {
118 out.push(m);
119 }
120 }
121 for m in out {
122 methods.remove(&m);
123 }
124 Ok(())
125}
126
127fn defined_method_set(dex: &asc_dex::Dex<'_>, d: &Defined) -> Result<std::collections::BTreeSet<u32>> {
129 Ok(crate::defs::defined_methods(dex, d)?.into_iter().collect())
130}
131
132pub(crate) fn rule_rows(fr: &FindRefs<'_, '_, '_>, rules: &[&CompiledQuery], opts: FindOptions) -> Result<Vec<Vec<RefResult>>> {
136 let plain: Vec<usize> = (0..rules.len())
137 .filter(|&i| rules[i].relation().is_none() && rules[i].compound().is_none() && rules[i].defined().is_none())
138 .collect();
139 let hits = fr.find_many(&plain.iter().map(|&i| rules[i]).collect::<Vec<_>>())?;
140 let mut out: Vec<Option<Vec<RefResult>>> = (0..rules.len()).map(|_| None).collect();
141 for (&i, hits) in plain.iter().zip(hits) {
142 let mut rows = results_from_hits(fr.dex(), rules[i].kind(), &hits, opts.aggregate)?;
143 if opts.context != RefContext::None {
144 attach_context(fr, &mut rows, opts.context)?;
145 }
146 out[i] = Some(rows);
147 }
148 for (i, slot) in out.iter_mut().enumerate() {
149 if slot.is_none() {
150 *slot = Some(rows_compiled(fr, rules[i], opts)?);
151 }
152 }
153 Ok(out.into_iter().map(Option::unwrap_or_default).collect())
154}
155
156pub fn compile_rules(rules: &[Query]) -> Result<Vec<CompiledQuery>> {
158 Ok(rules.iter().map(CompiledQuery::new).collect::<std::result::Result<_, _>>()?)
159}
160
161impl Engine {
162 pub fn find_refs_rules(
170 &self,
171 rules: &[Query],
172 opts: FindOptions,
173 cancel: &AtomicBool,
174 sink: &mut dyn FnMut(usize, RefBatch),
175 ) -> Result<RefSummary> {
176 let t0 = Instant::now();
177 for rule in rules {
178 crate::compose::check_patterns(rule)?;
179 }
180 if let Some(engine) = self.transient(rules.iter().any(crate::compose::needs_program))? {
181 return engine.find_refs_rules(rules, opts, cancel, sink);
182 }
183 let apk = self.open()?;
184 let mut entries = apk.dex_entries()?;
185 entries.reverse();
187 let key = self.apk_key(&apk);
188 let mut summary = RefSummary { entries: entries.len(), ..Default::default() };
189 if entries.is_empty() || rules.is_empty() {
190 return Ok(summary);
191 }
192 let disk = self.cache.as_ref().and_then(|c| c.disk.as_deref()).filter(|d| d.uses(Tier::Rendered));
193 let rendered = disk.zip(key.as_ref());
194 let mut missing = Vec::new();
195 for (i, rule) in rules.iter().enumerate() {
196 let replay = rendered
197 .and_then(|(d, k)| d.get_rendered_view(k, &tiers::findrefs_key(rule, opts, Shape::Results)))
198 .and_then(tiers::decode_batches);
199 match replay {
200 Some(recorded) => {
201 for batch in recorded.batches {
202 summary.total_results += batch.results.len();
203 sink(i, batch);
204 }
205 summary.notes.extend(recorded.notes);
206 }
207 None => missing.push(i),
208 }
209 }
210 if missing.is_empty() {
211 summary.notes = crate::compose::merge_notes(std::mem::take(&mut summary.notes));
212 summary.total_us = t0.elapsed().as_micros();
213 self.log(format!("[APK] for_each_findrefs_rules total={} us rules={} source=result", summary.total_us, rules.len()));
214 return Ok(summary);
215 }
216 let todo: Vec<Query> = missing.iter().map(|&i| rules[i].clone()).collect();
219 let resolved = self.resolve(&todo, cancel)?;
220 let rule_notes = resolved.notes;
221 summary.notes.extend(rule_notes.iter().flatten().cloned());
222 summary.notes = crate::compose::merge_notes(std::mem::take(&mut summary.notes));
223 let _query = self.prebuild.query();
224 let compiled = compile_rules(resolved.queries.as_deref().unwrap_or(&todo))?;
225 let mut recorders: Vec<_> = missing.iter().map(|_| rendered.map(|_| tiers::ResultRecorder::new(tiers::L3_MAX_RESULT))).collect();
226 let next = AtomicUsize::new(0);
227 let (tx, rx) = mpsc::channel::<Vec<RefBatch>>();
228 let workers = self.threads().min(entries.len()).max(1);
229 let searched: Vec<&CompiledQuery> = compiled.iter().collect();
230 let (cache, stats, searched) = (self.cache.as_deref(), &self.stats, &searched);
231 let run = |fr: &FindRefs<'_, '_, '_>| rule_rows(fr, searched, opts).map(|rows| rows.into_iter().map(Rows::Results).collect());
232 let probe = skip::Probe::new(searched.clone());
234 let probe = &probe;
235 std::thread::scope(|s| {
236 for _ in 0..workers {
237 let tx = tx.clone();
238 let (apk, entries, next, key, run) = (&apk, &entries, &next, key.as_ref(), &run);
239 s.spawn(move || {
240 loop {
241 if cancel.load(Ordering::Relaxed) {
242 break;
243 }
244 let i = next.fetch_add(1, Ordering::Relaxed);
245 let Some(entry) = entries.get(i) else { break };
246 let batches = search_entry_with(apk, key, entry, searched.len(), run, Some(probe), cancel, cache, stats);
247 if tx.send(batches).is_err() {
248 break;
249 }
250 }
251 });
252 }
253 drop(tx);
254 for batches in rx {
255 for (j, batch) in batches.into_iter().enumerate() {
256 summary.total_results += batch.results.len();
257 if let Some(r) = recorders[j].as_mut() {
258 r.record(&batch);
259 }
260 sink(missing[j], batch);
261 }
262 }
263 });
264 if let Some((_, k)) = rendered.filter(|_| !cancel.load(Ordering::Relaxed)) {
266 for ((&i, recorder), notes) in missing.iter().zip(recorders).zip(&rule_notes) {
267 if let Some(payload) = recorder.and_then(|r| r.finish(notes)) {
268 let query = tiers::findrefs_key(&rules[i], opts, Shape::Results);
269 let job = WriteJob { key: k.clone(), apk: Arc::from(apk.path()), kind: Job::Rendered { query, payload } };
270 self.cache.as_ref().expect("rendered implies a cache").put_async(job);
271 }
272 }
273 }
274 summary.total_us = t0.elapsed().as_micros();
275 self.log(format!(
276 "[APK] for_each_findrefs_rules total={} us count={} rules={} searched={} workers={workers}",
277 summary.total_us,
278 entries.len(),
279 rules.len(),
280 missing.len()
281 ));
282 Ok(summary)
283 }
284
285 pub fn search_target_entry_rules(
287 &self,
288 target: &SearchTarget,
289 entry: &Entry,
290 rules: &[&CompiledQuery],
291 aggregate: bool,
292 cancel: &AtomicBool,
293 ) -> Vec<RefBatch> {
294 let opts = FindOptions { aggregate, context: RefContext::None };
295 let run = |fr: &FindRefs<'_, '_, '_>| rule_rows(fr, rules, opts).map(|rows| rows.into_iter().map(Rows::Results).collect());
296 search_entry_with(&target.apk, target.key.as_ref(), entry, rules.len(), &run, None, cancel, self.cache.as_deref(), &self.stats)
297 }
298}