use std::collections::BTreeMap;
use droidsaw_common::cross_layer_taint::{
JsBridgeKey, NativeModuleMethodName, NativeModuleName,
};
use droidsaw_dex::annotation::EncodedValue;
use droidsaw_dex::ids::{MethodIdx, TypeIdx};
use crate::context::CrossLayerContext;
const REACT_MODULE_ANNS: &[&str] = &[
"Lcom/facebook/react/module/annotations/ReactModule;",
"Lcom/facebook/react/bridge/ReactModule;",
];
const REACT_METHOD_ANN: &str = "Lcom/facebook/react/bridge/ReactMethod;";
const REACT_MODULE_NAME_ELEMENT: &str = "name";
pub struct BridgeResolver {
pub mappings: BTreeMap<JsBridgeKey, Vec<(usize, MethodIdx)>>,
pub by_method: BTreeMap<String, Vec<(usize, MethodIdx)>>,
}
impl BridgeResolver {
pub fn resolve(ctx: &CrossLayerContext) -> Self {
let mut mappings: BTreeMap<JsBridgeKey, Vec<(usize, MethodIdx)>> = BTreeMap::new();
let mut by_method: BTreeMap<String, Vec<(usize, MethodIdx)>> = BTreeMap::new();
let Some(apk) = ctx.apk.as_ref() else {
return Self { mappings, by_method };
};
debug_assert!(
ctx.dex.len() <= apk.dex.len(),
"internal: ctx.dex (len={}) must not exceed apk.dex (len={}) — \
CrossLayerContext invariant violation",
ctx.dex.len(),
apk.dex.len(),
);
for ((i, dex), apk_dex) in ctx.dex.iter().enumerate().zip(apk.dex.iter()) {
let data = &apk_dex.data;
let Ok(annotated) = dex.find_annotated_methods(data, REACT_METHOD_ANN) else {
continue;
};
let class_module_name = scan_react_module_annotations(dex);
for m_idx in annotated {
#[allow(
clippy::as_conversions,
reason = "PROOF: u32 → usize widening, lossless on 64-bit; .get() handles OOB."
)]
let Some(m_id_item) = dex.methods.get(m_idx.0 as usize) else {
continue;
};
let Ok(method_name) = dex.get_string(m_id_item.name_idx) else {
continue;
};
by_method
.entry(method_name.to_string())
.or_default()
.push((i, m_idx));
let Some(module) = class_module_name.get(&m_id_item.class_idx) else {
continue;
};
let Some(method) = NativeModuleMethodName::try_new(method_name.to_string()) else {
continue;
};
let key = JsBridgeKey::new(module.clone(), method);
mappings.entry(key).or_default().push((i, m_idx));
}
}
Self { mappings, by_method }
}
}
fn scan_react_module_annotations(
dex: &droidsaw_dex::DexFile,
) -> BTreeMap<TypeIdx, NativeModuleName> {
let mut out: BTreeMap<TypeIdx, NativeModuleName> = BTreeMap::new();
for class_def in &dex.class_defs {
if class_def.annotations_off == 0 {
continue;
}
let Some(dir) = dex.annotations.get(&class_def.annotations_off) else {
continue;
};
if dir.class_annotations_off == 0 {
continue;
}
let Some(set) = dex.annotation_sets.get(&dir.class_annotations_off) else {
continue;
};
for item_off in set {
let Some(item) = dex.annotation_items.get(item_off) else {
continue;
};
let Ok(desc) = dex.get_type_descriptor(item.annotation.type_idx) else {
continue;
};
if !REACT_MODULE_ANNS.iter().any(|d| *d == desc) {
continue;
}
for (k, v) in &item.annotation.elements {
let Ok(elem_name) = dex.get_string(*k) else {
continue;
};
if elem_name != REACT_MODULE_NAME_ELEMENT {
continue;
}
let EncodedValue::String(name_idx) = v else {
continue;
};
let Ok(name_str) = dex.get_string(*name_idx) else {
continue;
};
let Some(nm) = NativeModuleName::try_new(name_str.to_string()) else {
continue;
};
out.insert(class_def.class_idx, nm);
break;
}
}
}
out
}