use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use crate::{mapping::R8Header, ProguardMapping, ProguardRecord};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) struct ObfuscatedName<'s>(&'s str);
impl<'s> ObfuscatedName<'s> {
pub(crate) fn as_str(&self) -> &'s str {
self.0
}
}
impl std::ops::Deref for ObfuscatedName<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) struct OriginalName<'s>(&'s str);
impl<'s> OriginalName<'s> {
pub(crate) fn as_str(&self) -> &'s str {
self.0
}
}
impl std::ops::Deref for OriginalName<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ClassInfo<'s> {
pub(crate) source_file: Option<&'s str>,
pub(crate) is_synthesized: bool,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum MethodReceiver<'s> {
ThisClass(OriginalName<'s>),
OtherClass(OriginalName<'s>),
}
impl<'s> MethodReceiver<'s> {
pub(crate) fn name(&self) -> OriginalName<'s> {
match self {
Self::ThisClass(name) => *name,
Self::OtherClass(name) => *name,
}
}
}
impl PartialEq for MethodReceiver<'_> {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
impl Eq for MethodReceiver<'_> {}
impl std::hash::Hash for MethodReceiver<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) struct MethodKey<'s> {
pub(crate) receiver: MethodReceiver<'s>,
pub(crate) name: OriginalName<'s>,
pub(crate) arguments: &'s str,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct MethodInfo {
pub(crate) is_synthesized: bool,
pub(crate) is_outline: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RewriteAction<'s> {
RemoveInnerFrames(usize),
Unknown(&'s str),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RewriteCondition<'s> {
Throws(&'s str),
Unknown(&'s str),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct RewriteRule<'s> {
pub(crate) conditions: Vec<RewriteCondition<'s>>,
pub(crate) actions: Vec<RewriteAction<'s>>,
}
#[derive(Clone, Debug)]
pub(crate) struct Member<'s> {
pub(crate) method: MethodKey<'s>,
pub(crate) startline: Option<usize>,
pub(crate) endline: Option<usize>,
pub(crate) original_startline: Option<usize>,
pub(crate) original_endline: Option<usize>,
pub(crate) outline_callsite_positions: Option<HashMap<usize, usize>>,
pub(crate) rewrite_rules: Vec<RewriteRule<'s>>,
}
fn parse_rewrite_rule<'s>(conditions: &[&'s str], actions: &[&'s str]) -> Option<RewriteRule<'s>> {
if conditions.is_empty() || actions.is_empty() {
return None;
}
let mut parsed_conditions = Vec::with_capacity(conditions.len());
for condition in conditions {
let condition = condition.trim();
if condition.is_empty() {
return None;
}
if let Some(rest) = condition.strip_prefix("throws(") {
let descriptor = rest.strip_suffix(')')?;
if descriptor.is_empty() {
return None;
}
parsed_conditions.push(RewriteCondition::Throws(descriptor));
} else {
parsed_conditions.push(RewriteCondition::Unknown(condition));
}
}
let mut parsed_actions = Vec::with_capacity(actions.len());
for action in actions {
let action = action.trim();
if action.is_empty() {
return None;
}
if let Some(rest) = action.strip_prefix("removeInnerFrames(") {
let count_str = rest.strip_suffix(')')?;
let count = count_str.parse().ok()?;
parsed_actions.push(RewriteAction::RemoveInnerFrames(count));
} else {
parsed_actions.push(RewriteAction::Unknown(action));
}
}
Some(RewriteRule {
conditions: parsed_conditions,
actions: parsed_actions,
})
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Members<'s> {
pub(crate) all: Vec<Member<'s>>,
pub(crate) by_params: HashMap<&'s str, Vec<Member<'s>>>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ParsedProguardMapping<'s> {
pub(crate) class_names: HashMap<ObfuscatedName<'s>, OriginalName<'s>>,
pub(crate) class_infos: HashMap<OriginalName<'s>, ClassInfo<'s>>,
pub(crate) method_infos: HashMap<MethodKey<'s>, MethodInfo>,
pub(crate) members: HashMap<(ObfuscatedName<'s>, ObfuscatedName<'s>), Members<'s>>,
}
impl<'s> ParsedProguardMapping<'s> {
pub(crate) fn parse(mapping: ProguardMapping<'s>, initialize_param_mapping: bool) -> Self {
let mut slf = Self::default();
let mut current_class_name = None;
let mut current_class = ClassInfo::default();
let mut unique_methods: HashSet<(&str, &str, &str)> = HashSet::new();
let mut records = mapping.iter().filter_map(Result::ok).peekable();
while let Some(record) = records.next() {
match record {
ProguardRecord::Field { .. } => {}
ProguardRecord::Header { .. } => {}
ProguardRecord::R8Header(_) => {
}
ProguardRecord::Class {
original,
obfuscated,
} => {
if let Some((obfuscated, original)) = current_class_name {
slf.class_names.insert(obfuscated, original);
slf.class_infos.insert(original, current_class);
}
current_class_name = Some((ObfuscatedName(obfuscated), OriginalName(original)));
current_class = ClassInfo::default();
unique_methods.clear();
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
match r8_header {
R8Header::RewriteFrame { .. } => {}
R8Header::SourceFile { file_name } => {
current_class.source_file = Some(file_name)
}
R8Header::Synthesized => current_class.is_synthesized = true,
R8Header::Outline => {}
R8Header::OutlineCallsite { .. } => {}
R8Header::Other => {}
}
records.next();
}
}
ProguardRecord::Method {
original,
obfuscated,
original_class,
line_mapping,
arguments,
..
} => {
let current_line = if initialize_param_mapping {
line_mapping
} else {
None
};
let (mut startline, mut endline) = match line_mapping.as_ref() {
Some(lm) => (lm.startline, lm.endline),
None => (None, None),
};
let (mut original_startline, mut original_endline) = match line_mapping {
None => (None, None),
Some(lm) => match lm.original_startline {
Some(os) => (Some(os), lm.original_endline),
None => (startline, endline),
},
};
if let (Some(s), Some(e)) = (startline, endline) {
if s > e {
startline = Some(e);
endline = Some(s);
}
}
if let (Some(os), Some(oe)) = (original_startline, original_endline) {
if os > oe {
original_startline = Some(oe);
original_endline = Some(os);
}
}
let Some((current_class_obfuscated, current_class_original)) =
current_class_name
else {
return Self::default();
};
let members = slf
.members
.entry((current_class_obfuscated, ObfuscatedName(obfuscated)))
.or_default();
let mut rewrite_rules: Vec<RewriteRule<'s>> = Vec::new();
let method = MethodKey {
receiver: match original_class {
Some(original_class) => {
MethodReceiver::OtherClass(OriginalName(original_class))
}
None => MethodReceiver::ThisClass(current_class_original),
},
name: OriginalName(original),
arguments,
};
let method_info: &mut MethodInfo = slf.method_infos.entry(method).or_default();
let mut outline_callsite_positions: Option<HashMap<usize, usize>> = None;
while let Some(ProguardRecord::R8Header(r8_header)) = records.peek() {
match r8_header {
R8Header::Synthesized => method_info.is_synthesized = true,
R8Header::Outline => {
method_info.is_outline = true;
}
R8Header::RewriteFrame {
conditions,
actions,
} => {
if let Some(rule) = parse_rewrite_rule(conditions, actions) {
rewrite_rules.push(rule);
}
}
R8Header::OutlineCallsite {
positions,
outline: _,
} => {
let map: HashMap<usize, usize> = positions
.iter()
.filter_map(|(k, v)| k.parse::<usize>().ok().map(|kk| (kk, *v)))
.collect();
if !map.is_empty() {
outline_callsite_positions = Some(map);
}
}
R8Header::SourceFile { .. } | R8Header::Other => {}
}
records.next();
}
let member = Member {
method,
startline,
endline,
original_startline,
original_endline,
outline_callsite_positions,
rewrite_rules,
};
members.all.push(member.clone());
if !initialize_param_mapping {
continue;
}
if let Some(ProguardRecord::Method {
line_mapping: Some(next_line),
..
}) = records.peek()
{
if let Some(current_line_mapping) = current_line {
if (current_line_mapping.startline == next_line.startline)
&& (current_line_mapping.endline == next_line.endline)
{
continue;
}
}
}
let key = (obfuscated, arguments, original);
if unique_methods.insert(key) {
members
.by_params
.entry(arguments)
.or_insert_with(|| Vec::with_capacity(1))
.push(member.clone());
}
} }
}
if let Some((obfuscated, original)) = current_class_name {
slf.class_names.insert(obfuscated, original);
slf.class_infos.insert(original, current_class);
}
slf
}
}