1use crate::calls::CompiledCall;
8use crate::{ClassQuery, ClassRelation, Defined, FindError, MemberQuery, MemberSignature, Query, RefKind, Result, TypeMatch};
9use memchr::memmem::Finder;
10use regex::bytes::Regex;
11use std::collections::BTreeMap;
12
13pub(crate) enum CompiledClass {
15 Precise(Vec<u8>),
18 Fuzzy(Regex),
20}
21
22pub(crate) enum CompiledName {
24 Substring(String),
26 Regex(Regex),
28 Exact { name: String, bytes: Vec<u8> },
30}
31
32pub(crate) struct CompiledType {
34 bytes: Vec<u8>,
36 simple_dims: Option<usize>,
38}
39
40impl CompiledType {
41 pub(crate) fn new(t: &TypeMatch) -> CompiledType {
42 match t {
43 TypeMatch::Descriptor(d) => CompiledType { bytes: asc_dex::mutf8::encode(d), simple_dims: None },
44 TypeMatch::Simple { name, dims } => {
45 CompiledType { bytes: asc_dex::mutf8::encode(&format!("{name};")), simple_dims: Some(*dims) }
46 }
47 }
48 }
49
50 pub(crate) fn matches(&self, d: &[u8]) -> bool {
52 let Some(dims) = self.simple_dims else { return d == self.bytes };
53 let Some(class) = d.get(dims..).filter(|_| d[..dims].iter().all(|&b| b == b'[')) else { return false };
56 let Some(class) = class.strip_prefix(b"L") else { return false };
57 class.ends_with(&self.bytes) && (class.len() == self.bytes.len() || class[class.len() - self.bytes.len() - 1] == b'/')
58 }
59}
60
61pub(crate) enum CompiledSignature {
63 Method { params: Vec<CompiledType>, ret: Option<CompiledType> },
64 Field(CompiledType),
65}
66
67pub(crate) struct CompiledMember {
69 pub class: Option<CompiledClass>,
70 pub name: Option<CompiledName>,
71 pub signature: Option<CompiledSignature>,
72}
73
74pub(crate) enum Parts {
75 Pattern(Option<Regex>),
77 Literal(Option<Finder<'static>>),
79 Member(CompiledMember),
80 MemberSet(Vec<CompiledMember>),
82 Relation(ClassRelation, Vec<u8>),
84 Defined(Defined),
88 Compound(Compound),
89 Call(CompiledCall),
91 Virtual(Box<CompiledQuery>, BTreeMap<String, String>),
93}
94
95#[non_exhaustive]
97pub struct Compound {
98 pub all: Vec<CompiledQuery>,
100 pub none: Vec<CompiledQuery>,
102}
103
104pub struct CompiledQuery {
112 query: Query,
113 kind: RefKind,
114 parts: Parts,
115}
116
117impl CompiledQuery {
118 pub fn new(q: &Query) -> Result<CompiledQuery> {
128 let parts = match q {
129 Query::String(p) if is_ascii_word(p) => Parts::Literal(Some(Finder::new(&asc_dex::mutf8::encode(p)).into_owned())),
133 Query::String(p) | Query::Type(p) => Parts::Pattern(compile_opt(p)?),
134 Query::StringLiteral(p) => Parts::Literal((!p.is_empty()).then(|| Finder::new(&asc_dex::mutf8::encode(p)).into_owned())),
137 Query::Method(m) => Parts::Member(compile_member(m, RefKind::Method)?),
138 Query::MethodSet(set) => Parts::MemberSet(set.iter().map(|m| compile_member(m, RefKind::Method)).collect::<Result<_>>()?),
139 Query::Nested(_) => {
140 return Err(FindError::Invalid(
141 "method:{...} has to be resolved over every DEX first (by the engine), not compiled for one".into(),
142 ));
143 }
144 Query::Call { target, constraints } => Parts::Call(CompiledCall::new(target, constraints)?),
145 Query::Virtual { target, via, .. } => Parts::Virtual(Box::new(CompiledQuery::new(target)?), via.clone()),
146 Query::Defined(Defined::Override(_) | Defined::In(_)) => {
147 return Err(FindError::Invalid(
148 "override: / in:{...} need the class hierarchy (every DEX); the engine resolves them before a search".into(),
149 ));
150 }
151 Query::Defined(d) => Parts::Defined(d.clone()),
152 Query::Field(m) | Query::FieldAccess { field: m, .. } => Parts::Member(compile_member(m, RefKind::Field)?),
153 Query::Extends(d) => Parts::Relation(ClassRelation::Extends, asc_dex::mutf8::encode(d)),
154 Query::Implements(d) => Parts::Relation(ClassRelation::Implements, asc_dex::mutf8::encode(d)),
155 Query::All { all, none } => {
156 if all.is_empty() {
157 return Err(FindError::Invalid("a compound query needs at least one condition in `all`".into()));
158 }
159 let part = |q: &Query| match q {
160 Query::All { .. } => Err(FindError::Invalid("compound queries do not nest".into())),
161 Query::Extends(_) | Query::Implements(_) => {
162 Err(FindError::Invalid("extends / impl name classes, not code: they cannot be combined".into()))
163 }
164 q => CompiledQuery::new(q),
165 };
166 Parts::Compound(Compound {
167 all: all.iter().map(part).collect::<Result<_>>()?,
168 none: none.iter().map(part).collect::<Result<_>>()?,
169 })
170 }
171 };
172 Ok(CompiledQuery { query: q.clone(), kind: q.kind(), parts })
173 }
174
175 #[inline]
177 pub fn kind(&self) -> RefKind {
178 self.kind
179 }
180
181 #[inline]
183 pub fn query(&self) -> &Query {
184 &self.query
185 }
186
187 #[inline]
189 pub fn relation(&self) -> Option<ClassRelation> {
190 self.query.relation()
191 }
192
193 #[inline]
195 pub fn compound(&self) -> Option<&Compound> {
196 match &self.parts {
197 Parts::Compound(c) => Some(c),
198 _ => None,
199 }
200 }
201
202 #[inline]
204 pub fn defined(&self) -> Option<&Defined> {
205 match &self.parts {
206 Parts::Defined(d) => Some(d),
207 _ => None,
208 }
209 }
210
211 #[inline]
212 pub(crate) fn parts(&self) -> &Parts {
213 &self.parts
214 }
215}
216
217impl std::fmt::Debug for CompiledQuery {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 f.debug_struct("CompiledQuery").field("query", &self.query).finish_non_exhaustive()
220 }
221}
222
223fn is_ascii_word(pattern: &str) -> bool {
226 !pattern.is_empty() && pattern.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
227}
228
229fn compile_opt(pattern: &str) -> Result<Option<Regex>> {
230 if pattern.is_empty() { Ok(None) } else { Ok(Some(Regex::new(pattern)?)) }
231}
232
233fn compile_member(m: &MemberQuery, kind: RefKind) -> Result<CompiledMember> {
234 let class = match m.class.as_ref().filter(|c| !c.pattern.is_empty()) {
235 None => None,
236 Some(ClassQuery { pattern, precise: true }) => Some(CompiledClass::Precise(asc_dex::mutf8::encode(pattern))),
237 Some(ClassQuery { pattern, precise: false }) => Some(CompiledClass::Fuzzy(Regex::new(pattern)?)),
238 };
239 let name = match m.name.as_deref().filter(|n| !n.is_empty()) {
240 None => None,
241 Some(n) if m.exact_name => Some(CompiledName::Exact { name: n.to_string(), bytes: asc_dex::mutf8::encode(n) }),
242 Some(n) if matches!(class, Some(CompiledClass::Precise(_))) => Some(CompiledName::Substring(n.to_string())),
245 Some(n) => Some(CompiledName::Regex(Regex::new(n)?)),
246 };
247 let signature = match (&m.signature, kind) {
248 (None, _) => None,
249 (Some(MemberSignature::Method { params, ret }), RefKind::Method) => Some(CompiledSignature::Method {
250 params: params.iter().map(CompiledType::new).collect(),
251 ret: ret.as_ref().map(CompiledType::new),
252 }),
253 (Some(MemberSignature::Field(t)), RefKind::Field) => Some(CompiledSignature::Field(CompiledType::new(t))),
254 (Some(sig), _) => return Err(FindError::Invalid(format!("signature {sig} does not fit a {} query", kind.as_str()))),
255 };
256 Ok(CompiledMember { class, name, signature })
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 fn assert_send_sync<T: Send + Sync>() {}
264
265 #[test]
266 fn compiled_query_is_send_sync() {
267 assert_send_sync::<CompiledQuery>();
268 }
269
270 #[test]
271 fn compiles_only_what_is_used() {
272 let q = Query::Method(MemberQuery {
273 class: Some(ClassQuery { pattern: "Lexample/Test;".into(), precise: true }),
274 name: Some("(".into()), exact_name: false,
276 signature: None,
277 });
278 let c = CompiledQuery::new(&q).unwrap();
279 assert_eq!(c.kind(), RefKind::Method);
280 assert_eq!(c.query(), &q);
281 let Parts::Member(m) = c.parts() else { panic!() };
282 assert!(matches!(m.class, Some(CompiledClass::Precise(_))));
283 assert!(matches!(m.name, Some(CompiledName::Substring(_))));
284
285 for class in [None, Some(ClassQuery { pattern: "Lexample/Test;".into(), precise: true })] {
287 let q = Query::Field(MemberQuery { class, name: Some("a(".into()), exact_name: true, signature: None });
288 let c = CompiledQuery::new(&q).unwrap();
289 let Parts::Member(m) = c.parts() else { panic!() };
290 assert!(matches!(&m.name, Some(CompiledName::Exact { name, bytes }) if name == "a(" && bytes == b"a("));
291 }
292 let q = Query::Method(MemberQuery { class: None, name: Some("\u{1F600}".into()), exact_name: true, signature: None });
293 let c = CompiledQuery::new(&q).unwrap();
294 let Parts::Member(m) = c.parts() else { panic!() };
295 assert!(matches!(&m.name, Some(CompiledName::Exact { bytes, .. }) if bytes == &[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80]));
296
297 let q = Query::Field(MemberQuery { class: None, name: Some("(".into()), exact_name: false, signature: None });
298 assert!(CompiledQuery::new(&q).is_err());
299 let q = Query::Field(MemberQuery {
300 class: Some(ClassQuery { pattern: "(".into(), precise: false }),
301 name: None,
302 exact_name: false,
303 signature: None,
304 });
305 assert!(CompiledQuery::new(&q).is_err());
306 let q = Query::String("[".into());
307 assert!(CompiledQuery::new(&q).is_err());
308
309 let c = CompiledQuery::new(&Query::StringLiteral("[".into())).unwrap();
311 assert_eq!(c.kind(), RefKind::String);
312 assert!(matches!(c.parts(), Parts::Literal(Some(f)) if f.needle() == b"["));
313 let c = CompiledQuery::new(&Query::StringLiteral("\u{1F600}".into())).unwrap();
314 assert!(matches!(c.parts(), Parts::Literal(Some(f)) if f.needle() == [0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80]));
315 assert!(matches!(CompiledQuery::new(&Query::StringLiteral(String::new())).unwrap().parts(), Parts::Literal(None)));
316
317 let c = CompiledQuery::new(&Query::String("token".into())).unwrap();
318 assert!(matches!(c.parts(), Parts::Literal(Some(f)) if f.needle() == b"token"));
319 let c = CompiledQuery::new(&Query::String("t.k".into())).unwrap();
320 assert!(matches!(c.parts(), Parts::Pattern(Some(_))));
321
322 let c = CompiledQuery::new(&Query::Implements("Lexample/Iface;".into())).unwrap();
323 assert_eq!(c.relation(), Some(ClassRelation::Implements));
324 assert!(matches!(c.parts(), Parts::Relation(ClassRelation::Implements, d) if d == b"Lexample/Iface;"));
325 assert_eq!(CompiledQuery::new(&Query::String("x".into())).unwrap().relation(), None);
326
327 let c = CompiledQuery::new(&Query::Type(String::new())).unwrap();
328 assert!(matches!(c.parts(), Parts::Pattern(None)));
329 let q = Query::Method(MemberQuery {
330 class: Some(ClassQuery { pattern: String::new(), precise: false }),
331 name: None,
332 exact_name: false,
333 signature: None,
334 });
335 let c = CompiledQuery::new(&q).unwrap();
336 let Parts::Member(m) = c.parts() else { panic!() };
337 assert!(m.class.is_none() && m.name.is_none());
338 }
339}