use crate::hierarchy::ClassGraph;
use crate::{Engine, EngineError, Lineage, Result, names};
use asc_dex::annotations::AnnotationsDirectory;
use asc_dex::{Dex, encoded};
use asc_findrefs::{Defined, Query};
use regex::bytes::Regex;
use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicBool;
pub(crate) fn defined_methods(dex: &Dex<'_>, d: &Defined) -> Result<Vec<u32>> {
let mut out = Vec::new();
match d {
Defined::Flags(mask) => {
for def in dex.class_defs() {
let def = def?;
if def.class_data_off == 0 {
continue;
}
for m in dex.class_data(def.class_data_off)?.methods() {
if m.access_flags & mask == *mask {
out.push(m.method_idx);
}
}
}
}
Defined::Annotation(a) => {
let Some(want) = dex.find_type_idx(&asc_dex::mutf8::encode(a))? else { return Ok(out) };
for def in dex.class_defs() {
let def = def?;
if def.annotations_off == 0 {
continue;
}
let dir = AnnotationsDirectory::parse(dex.buf(), def.annotations_off as usize)?;
for (method_idx, set_off) in dir.method_annotations {
if annotation_set_has(dex, set_off, want)? {
out.push(method_idx);
}
}
}
}
Defined::Members { classes, name, proto } => {
let name = name.as_deref();
for class in classes {
let Some((_, def)) = dex.find_class_def(&asc_dex::mutf8::encode(class))? else { continue };
if def.class_data_off == 0 {
continue;
}
for m in dex.class_data(def.class_data_off)?.methods() {
let id = dex.method_id(m.method_idx)?;
if name.is_some_and(|n| dex.string_bytes(id.name_idx).map(|b| b != n.as_bytes()).unwrap_or(true)) {
continue;
}
if let Some(want) = proto
&& asc_smali::proto_descriptor(dex, id.proto_idx as u32)? != *want
{
continue;
}
out.push(m.method_idx);
}
}
}
Defined::Override(_) | Defined::In(_) => {
return Err(EngineError::Query("override: / in:{...} must be resolved before a search".into()));
}
}
out.sort_unstable();
out.dedup();
Ok(out)
}
pub(crate) struct DefinedFilter<'d, 'a> {
dex: &'d Dex<'a>,
test: Test,
defs: Option<HashMap<u32, u32>>,
last: Option<(u32, Vec<u32>)>,
}
enum Test {
Members { classes: HashSet<Vec<u8>>, name: Option<String>, proto: Option<String> },
Class(ClassTest),
Nothing,
}
#[derive(Clone, Copy)]
enum ClassTest {
Flags(u32),
Annotation(u32),
}
impl<'d, 'a> DefinedFilter<'d, 'a> {
pub(crate) fn new(dex: &'d Dex<'a>, d: &Defined) -> Result<Self> {
let test = match d {
Defined::Flags(mask) => Test::Class(ClassTest::Flags(*mask)),
Defined::Annotation(a) => match dex.find_type_idx(&asc_dex::mutf8::encode(a))? {
Some(t) => Test::Class(ClassTest::Annotation(t)),
None => Test::Nothing,
},
Defined::Members { classes, name, proto } => Test::Members {
classes: classes.iter().map(|c| asc_dex::mutf8::encode(c)).collect(),
name: name.clone(),
proto: proto.clone(),
},
Defined::Override(_) | Defined::In(_) => {
return Err(EngineError::Query("override: / in:{...} must be resolved before a search".into()));
}
};
Ok(DefinedFilter { dex, test, defs: None, last: None })
}
pub(crate) fn matches(&mut self, method_idx: u32) -> Result<bool> {
let id = self.dex.method_id(method_idx)?;
let class_test = match &self.test {
Test::Nothing => return Ok(false),
Test::Members { classes, name, proto } => {
return Ok(classes.contains(self.dex.type_bytes(u32::from(id.class_idx))?)
&& name.as_ref().is_none_or(|n| self.dex.string_bytes(id.name_idx).is_ok_and(|b| b == n.as_bytes()))
&& match proto {
Some(want) => asc_smali::proto_descriptor(self.dex, u32::from(id.proto_idx))? == *want,
None => true,
});
}
Test::Class(t) => *t,
};
let class = u32::from(id.class_idx);
if self.last.as_ref().is_none_or(|(c, _)| *c != class) {
let matching = self.class_matches(class, class_test)?;
self.last = Some((class, matching));
}
Ok(self.last.as_ref().is_some_and(|(_, ms)| ms.contains(&method_idx)))
}
fn class_matches(&mut self, type_idx: u32, test: ClassTest) -> Result<Vec<u32>> {
let dex = self.dex;
let defs = match &mut self.defs {
Some(defs) => defs,
None => {
let mut defs = HashMap::new();
for (i, def) in (0..).zip(dex.class_defs()) {
defs.entry(def?.class_idx).or_insert(i);
}
self.defs.insert(defs)
}
};
let Some(&i) = defs.get(&type_idx) else { return Ok(Vec::new()) };
let def = dex.class_def(i)?;
let mut out = Vec::new();
match test {
ClassTest::Flags(mask) => {
if def.class_data_off != 0 {
out.extend(
dex.class_data(def.class_data_off)?.methods().filter(|m| m.access_flags & mask == mask).map(|m| m.method_idx),
);
}
}
ClassTest::Annotation(want) => {
if def.annotations_off != 0 {
for (method_idx, set_off) in AnnotationsDirectory::parse(dex.buf(), def.annotations_off as usize)?.method_annotations {
if annotation_set_has(dex, set_off, want)? {
out.push(method_idx);
}
}
}
}
}
Ok(out)
}
}
fn annotation_set_has(dex: &Dex<'_>, off: u32, want: u32) -> Result<bool> {
if off == 0 {
return Ok(false);
}
for item_off in asc_dex::annotations::parse_annotation_set(dex.buf(), off as usize)? {
let (item, _) = encoded::parse_annotation_item(dex.buf(), item_off as usize)?;
if item.annotation.type_idx == want {
return Ok(true);
}
}
Ok(false)
}
impl Engine {
pub(crate) fn resolve_defined(&self, queries: &[Query], cancel: &AtomicBool) -> Result<Option<Vec<Query>>> {
if !queries.iter().any(has_predicate) {
return Ok(None);
}
let (views, mut errors) = self.load_views(None, cancel)?;
let graph = self.class_graph(&views, &mut errors, cancel)?;
let resolved = queries.iter().map(|q| rewrite(q, &graph)).collect::<Result<_>>()?;
Ok(Some(resolved))
}
}
fn has_predicate(q: &Query) -> bool {
match q {
Query::Defined(Defined::Override(_) | Defined::In(_)) => true,
Query::All { all, none } => all.iter().chain(none).any(has_predicate),
_ => false,
}
}
fn rewrite(q: &Query, graph: &ClassGraph) -> Result<Query> {
Ok(match q {
Query::Defined(Defined::Override(target)) => Query::Defined(resolve_override(target, graph)?),
Query::Defined(Defined::In(inner)) => Query::Defined(resolve_in(inner, graph)?),
Query::All { all, none } => Query::All {
all: all.iter().map(|p| rewrite(p, graph)).collect::<Result<_>>()?,
none: none.iter().map(|p| rewrite(p, graph)).collect::<Result<_>>()?,
},
q => q.clone(),
})
}
fn resolve_override(target: &asc_findrefs::MemberQuery, graph: &ClassGraph) -> Result<Defined> {
let class = target
.class
.as_ref()
.filter(|c| c.precise && !c.pattern.is_empty())
.ok_or_else(|| EngineError::Query("override: needs a concrete class, e.g. override:android.os.IInterface->asBinder".into()))?;
let name = target.name.clone().ok_or_else(|| EngineError::Query("override: needs a method name".into()))?;
let proto = target.signature.as_ref().and_then(descriptor_proto);
let mut classes = vec![class.pattern.clone()];
classes.extend(graph.reachable(&class.pattern, Lineage::Subclasses, MAX_CLASSES));
Ok(Defined::Members { classes, name: Some(name), proto })
}
pub(crate) fn descriptor_proto(sig: &asc_findrefs::MemberSignature) -> Option<String> {
use asc_findrefs::{MemberSignature, TypeMatch};
let MemberSignature::Method { params, ret: Some(ret) } = sig else { return None };
fn descriptor(t: &TypeMatch) -> Option<&str> {
match t {
TypeMatch::Descriptor(d) => Some(d.as_str()),
TypeMatch::Simple { .. } => None,
}
}
let mut out = String::from("(");
for p in params {
out.push_str(descriptor(p)?);
}
out.push(')');
out.push_str(descriptor(ret)?);
Some(out)
}
fn resolve_in(inner: &Query, graph: &ClassGraph) -> Result<Defined> {
let classes = match inner {
Query::Extends(c) | Query::Implements(c) => {
let mut set = vec![c.clone()];
set.extend(graph.reachable(c, Lineage::Subclasses, MAX_CLASSES));
set
}
Query::Type(pattern) => {
let re = Regex::new(pattern).map_err(|e| EngineError::Query(format!("in:{{type:...}}: bad regex: {e}")))?;
graph.defined_classes_matching(&re)
}
Query::Defined(Defined::Annotation(a)) => graph.classes_annotated(a),
_ => return Err(EngineError::Query("in:{...} takes a class condition: extends:, impl:, type: or annotation:".into())),
};
Ok(Defined::Members { classes, name: None, proto: None })
}
const MAX_CLASSES: usize = 100_000;
pub(crate) fn parse_flags(value: &str) -> Result<u32> {
use asc_dex::access;
let mut mask = 0;
for raw in value.split(',') {
let name = raw.trim();
mask |= match name.to_ascii_lowercase().as_str() {
"public" => access::PUBLIC,
"private" => access::PRIVATE,
"protected" => access::PROTECTED,
"static" => access::STATIC,
"final" => access::FINAL,
"synchronized" => access::SYNCHRONIZED,
"bridge" => access::BRIDGE,
"varargs" => access::VARARGS,
"native" => access::NATIVE,
"interface" => access::INTERFACE,
"abstract" => access::ABSTRACT,
"synthetic" => access::SYNTHETIC,
"annotation" => access::ANNOTATION,
"enum" => access::ENUM,
"constructor" => access::CONSTRUCTOR,
_ => {
return Err(EngineError::Query(format!(
"flag:{name:?}: use public, private, protected, static, final, synchronized, native, abstract, synthetic, constructor"
)));
}
};
}
if mask == 0 {
return Err(EngineError::Query("flag: needs at least one access flag".into()));
}
Ok(mask)
}
pub(crate) fn annotation_descriptor(value: &str) -> Result<String> {
names::format_class_name(value.trim())
}