#![allow(clippy::collapsible_match)]
use jdc_core::FxHashSet as HashSet;
use jdc_core::ir::build::has_side_effects;
use jdc_core::emit::is_java_keyword;
use jdc_core::ir::expr::{AssignOp, BinOp, ConcatPart, ConstVal, Expr, TypeRef, UnOp};
use jdc_core::ir::stmt::{CaseGroup, Catch, Stmt};
use jdc_core::types::{JavaType, MethodDescriptor};
use jdc_core::var::VarTable;
use crate::lift::MethodEnv;
use crate::{access, desc_type, DexPool};
use ddc_dex::insn::{Insn, InsnKind, InvokeKind};
use jdc_core::types::parse_method_descriptor;
pub fn rewrite_exprs<F: FnMut(&mut Expr)>(s: &mut Stmt, f: &mut F) {
walk_stmt_exprs(s, f);
}
pub(crate) fn walk_stmt_exprs<F: FnMut(&mut Expr)>(s: &mut Stmt, f: &mut F) {
match s {
Stmt::Block(v) => {
for x in v.iter_mut() {
walk_stmt_exprs(x, f);
}
}
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
f(e);
}
Stmt::Return(Some(e)) => f(e),
Stmt::LocalDef { init, .. } => {
if let Some(e) = init {
f(e);
}
}
Stmt::If {
cond,
then_stmt,
else_stmt,
} => {
f(cond);
walk_stmt_exprs(then_stmt, f);
if let Some(e) = else_stmt {
walk_stmt_exprs(e, f);
}
}
Stmt::While { cond, body } => {
f(cond);
walk_stmt_exprs(body, f);
}
Stmt::DoWhile { body, cond } => {
walk_stmt_exprs(body, f);
f(cond);
}
Stmt::For {
init,
cond,
update,
body,
} => {
for x in init.iter_mut() {
walk_stmt_exprs(x, f);
}
if let Some(c) = cond {
f(c);
}
for u in update.iter_mut() {
f(u);
}
walk_stmt_exprs(body, f);
}
Stmt::ForEach { iterable, body, .. } => {
f(iterable);
walk_stmt_exprs(body, f);
}
Stmt::Switch {
selector,
cases,
default,
..
} => {
f(selector);
for c in cases {
for x in c.body.iter_mut() {
walk_stmt_exprs(x, f);
}
if let Some(g) = &mut c.guard {
f(g);
}
}
if let Some(d) = default {
walk_stmt_exprs(d, f);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
walk_stmt_exprs(body, f);
for c in catches {
walk_stmt_exprs(&mut c.body, f);
}
if let Some(fl) = finally {
walk_stmt_exprs(fl, f);
}
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
} => {
for x in resources.iter_mut() {
walk_stmt_exprs(x, f);
}
walk_stmt_exprs(body, f);
for c in catches {
walk_stmt_exprs(&mut c.body, f);
}
if let Some(fl) = finally {
walk_stmt_exprs(fl, f);
}
}
Stmt::Assert { cond, msg } => {
f(cond);
if let Some(m) = msg {
f(m);
}
}
Stmt::Synchronized { lock, body } => {
f(lock);
walk_stmt_exprs(body, f);
}
Stmt::TernaryValue { e } => f(e),
Stmt::Labeled { body, .. } => walk_stmt_exprs(body, f),
Stmt::Goto(_) | Stmt::Label(_) | Stmt::Break(_) | Stmt::Continue(_) => {}
_ => {}
}
}
pub(crate) fn visit_stmt_exprs_ro<F: FnMut(&Expr)>(s: &Stmt, f: &mut F) {
match s {
Stmt::Block(v) => {
for x in v {
visit_stmt_exprs_ro(x, f);
}
}
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
f(e);
}
Stmt::Return(Some(e)) => f(e),
Stmt::LocalDef { init: Some(e), .. } => f(e),
Stmt::If {
cond,
then_stmt,
else_stmt,
} => {
f(cond);
visit_stmt_exprs_ro(then_stmt, f);
if let Some(e) = else_stmt {
visit_stmt_exprs_ro(e, f);
}
}
Stmt::While { cond, body } => {
f(cond);
visit_stmt_exprs_ro(body, f);
}
Stmt::DoWhile { body, cond } => {
visit_stmt_exprs_ro(body, f);
f(cond);
}
Stmt::For {
init,
cond,
update,
body,
} => {
for x in init {
visit_stmt_exprs_ro(x, f);
}
if let Some(c) = cond {
f(c);
}
for u in update {
f(u);
}
visit_stmt_exprs_ro(body, f);
}
Stmt::ForEach { iterable, body, .. } => {
f(iterable);
visit_stmt_exprs_ro(body, f);
}
Stmt::Switch {
selector,
cases,
default,
..
} => {
f(selector);
for c in cases {
for x in &c.body {
visit_stmt_exprs_ro(x, f);
}
if let Some(g) = &c.guard {
f(g);
}
}
if let Some(d) = default {
visit_stmt_exprs_ro(d, f);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
visit_stmt_exprs_ro(body, f);
for c in catches {
visit_stmt_exprs_ro(&c.body, f);
}
if let Some(fl) = finally {
visit_stmt_exprs_ro(fl, f);
}
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
..
} => {
for x in resources {
visit_stmt_exprs_ro(x, f);
}
visit_stmt_exprs_ro(body, f);
for c in catches {
visit_stmt_exprs_ro(&c.body, f);
}
if let Some(fl) = finally {
visit_stmt_exprs_ro(fl, f);
}
}
Stmt::Assert { cond, msg } => {
f(cond);
if let Some(m) = msg {
f(m);
}
}
Stmt::Synchronized { lock, body } => {
f(lock);
visit_stmt_exprs_ro(body, f);
}
Stmt::TernaryValue { e } => f(e),
Stmt::Labeled { body, .. } => visit_stmt_exprs_ro(body, f),
_ => {}
}
}
fn count_expr_ro(e: &Expr, m: &mut jdc_core::FxHashMap<u32, usize>) {
if let Expr::Local { var, .. } = e {
*m.entry(*var).or_insert(0) += 1;
return;
}
if let Expr::Method {
name,
is_special: true,
owner: Some(o),
args,
..
} = e
{
if &**name == "<init>" && !matches!(**o, Expr::This) {
for a in args {
count_expr_ro(a, m);
}
return;
}
}
for_each_child(e, &mut |c| count_expr_ro(c, m));
}
fn visit_exprs<F: FnMut(&Expr)>(e: &Expr, f: &mut F) {
f(e);
for_each_child(e, &mut |c| visit_exprs(c, f));
}
fn for_each_child<F: FnMut(&Expr)>(e: &Expr, f: &mut F) {
match e {
Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => f(e),
Expr::Bin { l, r, .. } => {
f(l);
f(r);
}
Expr::Cond { c, t, f: fe } => {
f(c);
f(t);
f(fe);
}
Expr::Assign { target, value, .. } => {
f(target);
f(value);
}
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => f(e),
Expr::Field { owner, .. } => {
if let Some(o) = owner {
f(o);
}
}
Expr::Method { owner, args, .. } => {
if let Some(o) = owner {
f(o);
}
for a in args {
f(a);
}
}
Expr::ArrayIndex { array, index } => {
f(array);
f(index);
}
Expr::New { args, .. } => {
for a in args {
f(a);
}
}
Expr::NewArray { dims, init, .. } => {
for d in dims {
f(d);
}
if let Some(v) = init {
for x in v {
f(x);
}
}
}
Expr::NewMultiArray { dims, .. } => {
for d in dims {
f(d);
}
}
Expr::StringConcat(parts) => {
for p in parts {
if let ConcatPart::Str(x) = p {
f(x);
}
}
}
Expr::Invokedynamic { args, .. } => {
for a in args {
f(a);
}
}
Expr::AnonNew { args, .. } => {
for a in args {
f(a);
}
}
_ => {}
}
}
fn for_each_child_mut<F: FnMut(&mut Expr)>(e: &mut Expr, f: &mut F) {
match e {
Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => f(e),
Expr::Bin { l, r, .. } => {
f(l);
f(r);
}
Expr::Cond { c, t, f: fe } => {
f(c);
f(t);
f(fe);
}
Expr::Assign { target, value, .. } => {
f(target);
f(value);
}
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => f(e),
Expr::Field { owner, .. } => {
if let Some(o) = owner {
f(o);
}
}
Expr::Method { owner, args, .. } => {
if let Some(o) = owner {
f(o);
}
for a in args.iter_mut() {
f(a);
}
}
Expr::ArrayIndex { array, index } => {
f(array);
f(index);
}
Expr::New { args, .. } => {
for a in args.iter_mut() {
f(a);
}
}
Expr::NewArray { dims, init, .. } => {
for d in dims.iter_mut() {
f(d);
}
if let Some(v) = init {
for x in v.iter_mut() {
f(x);
}
}
}
Expr::NewMultiArray { dims, .. } => {
for d in dims.iter_mut() {
f(d);
}
}
Expr::StringConcat(parts) => {
for p in parts {
if let ConcatPart::Str(x) = p {
f(x);
}
}
}
Expr::Invokedynamic { args, .. } => {
for a in args.iter_mut() {
f(a);
}
}
Expr::AnonNew { args, .. } => {
for a in args.iter_mut() {
f(a);
}
}
_ => {}
}
}
pub(crate) fn deep_rewrite<F: FnMut(&mut Expr)>(e: &mut Expr, f: &mut F) {
f(e);
for_each_child_mut(e, &mut |c| deep_rewrite(c, f));
}
fn visit_exprs_reads<F: FnMut(&Expr)>(e: &Expr, f: &mut F) {
f(e);
match e {
Expr::Assign { target, value, .. } => {
if !matches!(**target, Expr::Local { .. }) {
visit_exprs_reads(target, f);
}
visit_exprs_reads(value, f);
}
Expr::PreIncDec { e: inner, .. } | Expr::PostIncDec { e: inner, .. } => {
if !matches!(**inner, Expr::Local { .. }) {
visit_exprs_reads(inner, f);
}
}
_ => for_each_child(e, &mut |c| visit_exprs_reads(c, f)),
}
}
fn deep_rewrite_reads<F: FnMut(&mut Expr)>(e: &mut Expr, f: &mut F) {
f(e);
match e {
Expr::Assign { target, value, .. } => {
if !matches!(**target, Expr::Local { .. }) {
deep_rewrite_reads(target, f);
}
deep_rewrite_reads(value, f);
}
Expr::PreIncDec { e: inner, .. } | Expr::PostIncDec { e: inner, .. } => {
if !matches!(**inner, Expr::Local { .. }) {
deep_rewrite_reads(inner, f);
}
}
_ => for_each_child_mut(e, &mut |c| deep_rewrite_reads(c, f)),
}
}
fn collect_vars(e: &Expr, out: &mut HashSet<u32>) {
visit_exprs(e, &mut |x| {
if let Expr::Local { var, .. } = x {
out.insert(*var);
}
});
}
fn stmt_collect_vars(s: &Stmt, out: &mut HashSet<u32>, assignments: bool) {
match s {
Stmt::Block(v) => {
for x in v {
stmt_collect_vars(x, out, assignments);
}
}
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
if let Expr::Local { var, .. } = &**target {
if assignments {
out.insert(*var);
}
} else {
collect_vars(target, out);
}
collect_vars(value, out);
}
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
collect_vars(e, out)
}
Stmt::Return(Some(e)) => collect_vars(e, out),
Stmt::LocalDef { var, init, .. } => {
if assignments {
out.insert(*var);
}
if let Some(e) = init {
collect_vars(e, out);
}
}
Stmt::If {
cond,
then_stmt,
else_stmt,
} => {
collect_vars(cond, out);
stmt_collect_vars(then_stmt, out, assignments);
if let Some(e) = else_stmt {
stmt_collect_vars(e, out, assignments);
}
}
Stmt::While { cond, body } => {
collect_vars(cond, out);
stmt_collect_vars(body, out, assignments);
}
Stmt::DoWhile { body, cond } => {
stmt_collect_vars(body, out, assignments);
collect_vars(cond, out);
}
Stmt::For {
init,
cond,
update,
body,
} => {
for x in init {
stmt_collect_vars(x, out, assignments);
}
if let Some(c) = cond {
collect_vars(c, out);
}
for u in update {
collect_vars(u, out);
}
stmt_collect_vars(body, out, assignments);
}
Stmt::ForEach {
var,
iterable,
body,
..
} => {
if assignments {
out.insert(*var);
}
collect_vars(iterable, out);
stmt_collect_vars(body, out, assignments);
}
Stmt::Switch {
selector,
cases,
default,
..
} => {
collect_vars(selector, out);
for c in cases {
for x in &c.body {
stmt_collect_vars(x, out, assignments);
}
}
if let Some(d) = default {
stmt_collect_vars(d, out, assignments);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
stmt_collect_vars(body, out, assignments);
for c in catches {
stmt_collect_vars(&c.body, out, assignments);
}
if let Some(f) = finally {
stmt_collect_vars(f, out, assignments);
}
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
} => {
for x in resources {
stmt_collect_vars(x, out, assignments);
}
stmt_collect_vars(body, out, assignments);
for c in catches {
stmt_collect_vars(&c.body, out, assignments);
}
if let Some(f) = finally {
stmt_collect_vars(f, out, assignments);
}
}
Stmt::Synchronized { lock, body } => {
collect_vars(lock, out);
stmt_collect_vars(body, out, assignments);
}
Stmt::Assert { cond, msg } => {
collect_vars(cond, out);
if let Some(m) = msg {
collect_vars(m, out);
}
}
Stmt::Labeled { body, .. } => stmt_collect_vars(body, out, assignments),
_ => {}
}
}
pub(crate) fn walk_all<F: FnMut(&Stmt)>(s: &Stmt, f: &mut F) {
f(s);
match s {
Stmt::Block(v) => {
for x in v {
walk_all(x, f);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
walk_all(then_stmt, f);
if let Some(e) = else_stmt {
walk_all(e, f);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => walk_all(body, f),
Stmt::For { init, body, .. } => {
for x in init {
walk_all(x, f);
}
walk_all(body, f);
}
Stmt::ForEach { body, .. } => walk_all(body, f),
Stmt::Switch { cases, default, .. } => {
for c in cases {
for x in &c.body {
walk_all(x, f);
}
}
if let Some(d) = default {
walk_all(d, f);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
walk_all(body, f);
for c in catches {
walk_all(&c.body, f);
}
if let Some(fl) = finally {
walk_all(fl, f);
}
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
} => {
for x in resources {
walk_all(x, f);
}
walk_all(body, f);
for c in catches {
walk_all(&c.body, f);
}
if let Some(fl) = finally {
walk_all(fl, f);
}
}
Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => walk_all(body, f),
_ => {}
}
}
pub fn bind_catches(s: &mut Stmt, vt: &mut VarTable) {
let mut defined: jdc_core::FxHashSet<u32> = jdc_core::FxHashSet::default();
for v in &vt.vars {
if v.is_param {
defined.insert(v.id);
}
}
collect_defined_locals(s, &mut defined);
let reads_all = count_locals_stmts(std::slice::from_ref(s));
let mut assigned: jdc_core::FxHashSet<u32> = jdc_core::FxHashSet::default();
collect_assigned_locals(s, &mut assigned);
let mut fallback_bound: Vec<u32> = Vec::new();
bind_catches_walk(s, vt, &defined, &reads_all, &assigned, &mut fallback_bound);
if !fallback_bound.is_empty() {
remove_bare_decls(s, &fallback_bound);
}
}
fn collect_defined_locals(s: &Stmt, out: &mut jdc_core::FxHashSet<u32>) {
crate::passes::walk_all(s, &mut |st| {
match st {
Stmt::LocalDef { var, .. } => {
out.insert(*var);
}
Stmt::ExprStmt(Expr::Assign { target, .. }) => {
if let Expr::Local { var, .. } = &**target {
out.insert(*var);
}
}
_ => {}
}
});
}
fn collect_bare_decls(s: &Stmt, out: &mut Vec<u32>) {
walk_all(s, &mut |st| {
if let Stmt::LocalDef { var, init: None, .. } = st {
if !out.contains(var) {
out.push(*var);
}
}
});
}
fn collect_assigned_locals(s: &Stmt, out: &mut jdc_core::FxHashSet<u32>) {
crate::passes::walk_all(s, &mut |st| {
match st {
Stmt::LocalDef { var, init: Some(_), .. } => {
out.insert(*var);
}
Stmt::ExprStmt(Expr::Assign { target, .. }) => {
if let Expr::Local { var, .. } = &**target {
out.insert(*var);
}
}
_ => {}
}
});
}
fn first_thrown_unassigned_local(
body: &Stmt,
vt: &VarTable,
assigned: &jdc_core::FxHashSet<u32>,
) -> Option<u32> {
let mut found: Option<u32> = None;
let c = body.clone();
walk_all(&c, &mut |st| {
if let Stmt::Throw(th) = st {
if let Expr::Local { var, .. } = th {
let v = *var;
if !assigned.contains(&v) {
if let JavaType::Object(o) = vt.var(v).ty.erased() {
if o.as_ref() == "java/lang/Throwable" {
found = Some(v);
}
}
}
}
}
});
found
}
fn remove_first_leaf(s: &mut Stmt) {
if let Stmt::Block(v) = s {
if let Some(f) = v.first_mut() {
remove_first_leaf(f);
}
while matches!(v.first(), Some(Stmt::Block(b)) if b.is_empty()) {
v.remove(0);
}
} else {
*s = Stmt::Block(vec![]);
}
}
fn remove_bare_decls(s: &mut Stmt, vars: &[u32]) {
let vars: jdc_core::FxHashSet<u32> = vars.iter().copied().collect();
walk_mut_deep(s, &mut |st| {
if let Stmt::Block(v) = st {
v.retain(|x| {
!matches!(x, Stmt::LocalDef { var, init: None, .. } if vars.contains(var))
});
}
});
}
fn first_undefined_local_read(
body: &Stmt,
defined: &jdc_core::FxHashSet<u32>,
) -> Option<u32> {
let mut found: Option<u32> = None;
visit_stmt_exprs_ro(body, &mut |e| {
if found.is_none() {
visit_exprs(e, &mut |x| {
if found.is_none() {
if let Expr::Local { var, .. } = x {
if !defined.contains(var) {
found = Some(*var);
}
}
}
});
}
});
found
}
#[allow(clippy::too_many_arguments)]
fn bind_catches_walk(
s: &mut Stmt,
vt: &mut VarTable,
defined: &jdc_core::FxHashSet<u32>,
reads_all: &jdc_core::FxHashMap<u32, usize>,
assigned: &jdc_core::FxHashSet<u32>,
fallback_bound: &mut Vec<u32>,
) {
match s {
Stmt::Try {
body,
catches,
finally,
} => {
bind_catches_walk(body, vt, defined, reads_all, assigned, fallback_bound);
for c in catches.iter_mut() {
if c.var == u32::MAX {
let stored = match first_leaf_stmt(c.body.as_ref()) {
Stmt::LocalDef { var, init: None, .. } => Some(*var),
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
match (&**target, &**value) {
(Expr::Local { var, .. }, Expr::Local { var: src, .. })
if !defined.contains(src) =>
{
Some(*var)
}
_ => None,
}
}
_ => None,
};
if let Some(v) = stored {
let exc = c
.exc
.first()
.cloned()
.unwrap_or_else(|| "java/lang/Throwable".into());
let slot = vt.var(v).slot;
let name = "e".to_string();
let new_var =
vt.add_catch_var(slot, name, TypeRef::J(JavaType::Object(exc)));
remove_first_leaf(c.body.as_mut());
rewrite_local_refs(c.body.as_mut(), v, new_var);
c.var = new_var;
} else if let Some(v) = first_undefined_local_read(c.body.as_ref(), defined)
.filter(|v| {
let in_catch = count_locals_stmts(std::slice::from_ref(c.body.as_ref()));
reads_all.get(v).copied().unwrap_or(0)
== in_catch.get(v).copied().unwrap_or(0)
})
.filter(|v| {
let ty = vt.var(*v).ty.erased();
let exc = c
.exc
.first()
.cloned()
.unwrap_or_else(|| "java/lang/Throwable".into());
matches!(&ty, JavaType::Object(o)
if o.as_ref() == exc.as_ref()
|| o.as_ref() == "java/lang/Throwable")
})
{
c.var = v;
fallback_bound.push(v);
} else if let Some(v) =
first_thrown_unassigned_local(c.body.as_ref(), vt, assigned)
.filter(|v| {
let in_catch =
count_locals_stmts(std::slice::from_ref(c.body.as_ref()));
reads_all.get(v).copied().unwrap_or(0)
== in_catch.get(v).copied().unwrap_or(0)
})
.filter(|v| {
let ty = vt.var(*v).ty.erased();
let exc = c
.exc
.first()
.cloned()
.unwrap_or_else(|| "java/lang/Throwable".into());
matches!(&ty, JavaType::Object(o)
if o.as_ref() == exc.as_ref()
|| o.as_ref() == "java/lang/Throwable")
})
{
c.var = v;
fallback_bound.push(v);
}
}
if c.var != u32::MAX {
let in_catch =
count_locals_stmts(std::slice::from_ref(c.body.as_ref()));
let mut twins: Vec<u32> = Vec::new();
collect_bare_decls(c.body.as_ref(), &mut twins);
let mut swept: Vec<u32> = Vec::new();
for x in twins {
if x == c.var || assigned.contains(&x) {
continue;
}
if reads_all.get(&x).copied().unwrap_or(0)
!= in_catch.get(&x).copied().unwrap_or(0)
{
continue;
}
if !matches!(vt.var(x).ty.erased(),
JavaType::Object(o) if o.as_ref() == "java/lang/Throwable")
{
continue;
}
rewrite_local_refs(c.body.as_mut(), x, c.var);
swept.push(x);
}
if !swept.is_empty() {
remove_bare_decls(c.body.as_mut(), &swept);
}
}
bind_catches_walk(&mut c.body, vt, defined, reads_all, assigned, fallback_bound);
}
if let Some(f) = finally {
bind_catches_walk(f.as_mut(), vt, defined, reads_all, assigned, fallback_bound);
}
}
Stmt::Block(v) => {
for x in v.iter_mut() {
bind_catches_walk(x, vt, defined, reads_all, assigned, fallback_bound);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
bind_catches_walk(then_stmt, vt, defined, reads_all, assigned, fallback_bound);
if let Some(e) = else_stmt {
bind_catches_walk(e, vt, defined, reads_all, assigned, fallback_bound);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => bind_catches_walk(body, vt, defined, reads_all, assigned, fallback_bound),
Stmt::For { init, body, .. } => {
for x in init.iter_mut() {
bind_catches_walk(x, vt, defined, reads_all, assigned, fallback_bound);
}
bind_catches_walk(body, vt, defined, reads_all, assigned, fallback_bound);
}
Stmt::ForEach { body, .. }
| Stmt::Labeled { body, .. }
| Stmt::Synchronized { body, .. } => bind_catches_walk(body, vt, defined, reads_all, assigned, fallback_bound),
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
for x in c.body.iter_mut() {
bind_catches_walk(x, vt, defined, reads_all, assigned, fallback_bound);
}
}
if let Some(d) = default {
bind_catches_walk(d, vt, defined, reads_all, assigned, fallback_bound);
}
}
_ => {}
}
}
fn rewrite_local_refs(s: &mut Stmt, from: u32, to: u32) {
rewrite_exprs(s, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == from {
*var = to;
}
}
});
});
}
pub fn cleanup(s: &mut Stmt) {
jdc_core::ir::stmt::flatten(s);
prune_dead_tails(s);
strip_empty(s);
merge_singleton_stmts(s);
}
fn seq_terminates(s: &Stmt) -> bool {
match s {
Stmt::Return(_) | Stmt::Throw(_) | Stmt::Break(_) | Stmt::Continue(_) => {
true
}
Stmt::Block(v) => v.iter().any(seq_terminates),
Stmt::If {
then_stmt,
else_stmt,
..
} => {
seq_terminates(then_stmt)
&& else_stmt
.as_ref()
.map(|e| seq_terminates(e))
.unwrap_or(false)
}
Stmt::Try {
body,
catches,
finally,
}
| Stmt::TryWithResources {
body,
catches,
finally,
..
} => {
if let Some(f) = finally {
if seq_terminates(f) {
return true;
}
}
seq_terminates(body) && catches.iter().all(|c| seq_terminates(&c.body))
}
Stmt::Labeled { body, .. } | Stmt::Synchronized { body, .. } => {
seq_terminates(body)
}
Stmt::While { .. } | Stmt::DoWhile { .. } => {
jdc_core::analysis::dead_end_infinite_while(s)
}
_ => false,
}
}
fn prune_seq(v: &mut Vec<Stmt>) {
if v.iter()
.any(|x| matches!(x, Stmt::Label(_) | Stmt::Goto(_)))
{
return;
}
if let Some(i) = v.iter().position(seq_terminates) {
v.truncate(i + 1);
}
}
pub fn prune_dead_tails(s: &mut Stmt) {
match s {
Stmt::Block(v) => {
for x in v.iter_mut() {
prune_dead_tails(x);
}
prune_seq(v);
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
prune_dead_tails(then_stmt);
if let Some(e) = else_stmt {
prune_dead_tails(e);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Labeled { body, .. }
| Stmt::Synchronized { body, .. } => prune_dead_tails(body),
Stmt::For { init, body, .. } => {
for x in init.iter_mut() {
prune_dead_tails(x);
}
prune_seq(init);
prune_dead_tails(body);
}
Stmt::Switch {
cases, default, ..
} => {
for c in cases {
for x in c.body.iter_mut() {
prune_dead_tails(x);
}
prune_seq(&mut c.body);
}
if let Some(d) = default {
prune_dead_tails(d);
}
}
Stmt::Try {
body,
catches,
finally,
}
| Stmt::TryWithResources {
body,
catches,
finally,
..
} => {
prune_dead_tails(body);
for c in catches {
prune_dead_tails(&mut c.body);
}
if let Some(f) = finally {
prune_dead_tails(f);
}
}
_ => {}
}
}
pub fn strip_phantom_field_writes(body: &mut Stmt, pool: &DexPool) {
fn phantom(pool: &DexPool, cls: &str, name: &str) -> bool {
let Some(pc) = pool.get(cls) else {
return false;
};
fn declares(pc: &crate::PoolClass, cls: &str, name: &str) -> bool {
pc.static_fields
.iter()
.chain(pc.instance_fields.iter())
.any(|f| {
f.name.as_ref() == name
|| jdc_core::rename::field_display(cls, &f.name, &f.desc)
.is_some_and(|d| d == name)
})
}
if declares(pc, cls, name) {
return false;
}
let mut cur: Option<String> = pc.super_name.clone();
let mut hops = 0u32;
while let Some(s) = cur {
hops += 1;
if hops > 64 {
return false;
}
if let Some(sup) = pool.get(&s) {
if declares(sup, &s, name) {
return false;
}
cur = sup.super_name.clone();
} else {
if !crate::fwdb::exists(&s) {
return false;
}
let mut found = false;
crate::fwdb::for_each_field(&s, |f, _| {
if f == name {
found = true;
}
});
if found {
return false;
}
cur = crate::fwdb::super_of(&s).map(|x| x.to_string());
}
}
true
}
fn droppable_value(pool: &DexPool, e: &Expr) -> bool {
match e {
Expr::Const(_) | Expr::Local { .. } => true,
Expr::Field { cls, name, .. } => phantom(pool, cls, name),
Expr::Cast { e, .. } => droppable_value(pool, e),
_ => false,
}
}
fn is_phantom_write(pool: &DexPool, st: &Stmt) -> bool {
if let Stmt::ExprStmt(Expr::Assign { target, value, .. }) = st {
if let Expr::Field { cls, name, .. } = &**target {
return phantom(pool, cls, name) && droppable_value(pool, value);
}
}
false
}
walk_mut_deep(body, &mut |st| match st {
Stmt::Block(v) => v.retain(|x| !is_phantom_write(pool, x)),
Stmt::Switch { cases, .. } => {
for c in cases.iter_mut() {
c.body.retain(|x| !is_phantom_write(pool, x));
}
}
Stmt::For { init, .. } => init.retain(|x| !is_phantom_write(pool, x)),
Stmt::TryWithResources { resources, .. } => {
resources.retain(|x| !is_phantom_write(pool, x))
}
_ => {}
});
if is_phantom_write(pool, body) {
*body = Stmt::Block(vec![]);
}
}
pub fn init_bare_decls(body: &mut Stmt, vt: &VarTable) {
walk_mut_deep(body, &mut |st| {
if let Stmt::LocalDef { var, init, .. } = st {
if init.is_some() {
return;
}
let lit = match vt.var(*var).ty.erased() {
JavaType::Boolean => "false",
JavaType::Int
| JavaType::Byte
| JavaType::Short
| JavaType::Char => "0",
JavaType::Long => "0L",
JavaType::Float => "0.0f",
JavaType::Double => "0.0d",
JavaType::Void => return,
JavaType::Object(_) | JavaType::Array(_) => "null",
};
*init = Some(Expr::Raw(lit.to_string()));
}
});
}
fn strip_empty(s: &mut Stmt) {
match s {
Stmt::Block(v) => {
v.retain(|x| !x.is_empty_block());
for x in v.iter_mut() {
strip_empty(x);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
strip_empty(then_stmt);
if let Some(e) = else_stmt {
strip_empty(e);
if e.is_empty_block() {
*else_stmt = None;
}
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => strip_empty(body),
Stmt::For { init, body, .. } => {
init.retain(|x| !x.is_empty_block());
strip_empty(body);
}
Stmt::ForEach { body, .. } => strip_empty(body),
Stmt::Switch { cases, default, .. } => {
for c in cases {
c.body.retain(|x| !x.is_empty_block());
}
if let Some(d) = default {
strip_empty(d);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
strip_empty(body);
for c in catches {
strip_empty(&mut c.body);
}
if let Some(f) = finally {
strip_empty(f);
}
}
Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => strip_empty(body),
_ => {}
}
}
fn merge_singleton_stmts(s: &mut Stmt) {
if let Stmt::Block(v) = s {
for x in v.iter_mut() {
merge_singleton_stmts(x);
}
}
}
pub fn prune_unreachable(s: &mut Stmt) {
if let Stmt::Block(v) = s {
let mut cut: Option<usize> = None;
for (i, x) in v.iter().enumerate() {
if matches!(x, Stmt::Return(_) | Stmt::Throw(_)) {
cut = Some(i + 1);
break;
}
}
if let Some(c) = cut {
if c < v.len() {
v.truncate(c);
}
}
for x in v.iter_mut() {
prune_unreachable(x);
}
} else {
walk_mut(s, &mut prune_unreachable);
}
}
fn walk_mut<F: FnMut(&mut Stmt)>(s: &mut Stmt, f: &mut F) {
match s {
Stmt::Block(v) => {
for x in v.iter_mut() {
f(x);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
f(then_stmt);
if let Some(e) = else_stmt {
f(e);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => f(body),
Stmt::For { init, body, .. } => {
for x in init.iter_mut() {
f(x);
}
f(body);
}
Stmt::ForEach { body, .. } => f(body),
Stmt::Switch { cases, default, .. } => {
for c in cases {
for x in c.body.iter_mut() {
f(x);
}
}
if let Some(d) = default {
f(d);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
f(body);
for c in catches {
f(&mut c.body);
}
if let Some(fl) = finally {
f(fl);
}
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
} => {
for r in resources.iter_mut() {
f(r);
}
f(body);
for c in catches {
f(&mut c.body);
}
if let Some(fl) = finally {
f(fl);
}
}
Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => f(body),
_ => {}
}
}
pub fn count_localdefs(s: &Stmt, out: &mut usize) {
if matches!(s, Stmt::LocalDef { .. }) {
*out += 1;
}
walk_all(s, &mut |x| {
if matches!(x, Stmt::LocalDef { .. }) {
*out += 1;
}
});
}
pub fn strip_trailing_void_return(s: &mut Stmt) {
if let Stmt::Block(v) = s {
loop {
match v.last_mut() {
Some(Stmt::Return(None)) => {
v.pop();
}
Some(Stmt::Block(_)) => {
let last = v.last_mut().unwrap();
let before = match last {
Stmt::Block(b) => b.len(),
_ => 0,
};
strip_trailing_void_return(last);
let after = match last {
Stmt::Block(b) => b.len(),
_ => 0,
};
if after == before {
break; }
}
_ => break,
}
}
}
}
pub fn invert_empty_thens(s: &mut Stmt) {
walk_mut_deep(s, &mut |st| {
if let Stmt::If {
cond,
then_stmt,
else_stmt,
} = st
{
let then_empty = matches!(&**then_stmt, Stmt::Block(v) if v.is_empty());
if then_empty {
if let Some(els) = else_stmt.take() {
let c = std::mem::replace(cond, Expr::Const(ConstVal::Int(0)));
*cond = jdc_core::convert::negate(c);
*then_stmt = els;
}
}
}
});
}
pub fn fold_short_circuits(s: &mut Stmt) {
for _ in 0..8 {
let mut changed = false;
walk_mut_deep(s, &mut |st| {
if try_fold_diamond(st) {
changed = true;
}
});
if !changed {
break;
}
}
}
fn try_fold_diamond(st: &mut Stmt) -> bool {
enum Shape {
ThenAnd,
ThenAndNot,
ElseOr,
ElseOrNot,
BareAnd,
BareAndNot,
}
let shape = match &*st {
Stmt::If {
then_stmt,
else_stmt: None,
..
} => match &**then_stmt {
Stmt::If {
then_stmt: t2,
else_stmt: None,
..
} if !matches!(&**t2, Stmt::Block(v) if v.is_empty()) => Some(Shape::BareAnd),
Stmt::If {
then_stmt: t2,
else_stmt: Some(_),
..
} if matches!(&**t2, Stmt::Block(v) if v.is_empty()) => Some(Shape::BareAndNot),
_ => None,
},
Stmt::If {
then_stmt,
else_stmt: Some(els),
..
} => match (&**then_stmt, &**els) {
(Stmt::If { else_stmt: Some(f2), .. }, _) if f2.as_ref() == els.as_ref() => {
Some(Shape::ThenAnd)
}
(Stmt::If { then_stmt: t2, else_stmt: Some(_), .. }, _)
if t2.as_ref() == els.as_ref() =>
{
Some(Shape::ThenAndNot)
}
(_, Stmt::If { then_stmt: t2, else_stmt: Some(_), .. })
if t2.as_ref() == then_stmt.as_ref() =>
{
Some(Shape::ElseOr)
}
(_, Stmt::If { else_stmt: Some(f2), .. })
if f2.as_ref() == then_stmt.as_ref() =>
{
Some(Shape::ElseOrNot)
}
_ => None,
},
_ => None,
};
let Some(shape) = shape else {
return false;
};
let taken = std::mem::replace(st, Stmt::Block(vec![]));
if matches!(shape, Shape::BareAnd | Shape::BareAndNot) {
let Stmt::If {
cond: c1,
then_stmt,
else_stmt: None,
} = taken
else {
unreachable!()
};
let Stmt::If {
cond: c2,
then_stmt: t2,
else_stmt: f2,
} = *then_stmt
else {
unreachable!()
};
let sc = |op: BinOp, l: Expr, r: Expr| Expr::Bin {
op,
l: Box::new(l),
r: Box::new(r),
ty: None,
};
match shape {
Shape::BareAnd => {
*st = Stmt::If {
cond: sc(BinOp::LogAnd, c1, c2),
then_stmt: t2,
else_stmt: None,
};
}
_ => {
let body = f2.unwrap();
*st = Stmt::If {
cond: sc(
BinOp::LogAnd,
c1,
Expr::Un {
op: UnOp::Not,
e: Box::new(c2),
},
),
then_stmt: body,
else_stmt: None,
};
}
}
return true;
}
let Stmt::If {
cond: c1,
then_stmt,
else_stmt: Some(els),
} = taken
else {
unreachable!("shape probe guaranteed an If with else")
};
let sc = |op: BinOp, l: Expr, r: Expr| Expr::Bin {
op,
l: Box::new(l),
r: Box::new(r),
ty: None,
};
let not = |e: Expr| Expr::Un {
op: UnOp::Not,
e: Box::new(e),
};
match shape {
Shape::BareAnd | Shape::BareAndNot => unreachable!("handled above"),
Shape::ThenAnd | Shape::ThenAndNot => {
let Stmt::If {
cond: c2,
then_stmt: t2,
else_stmt: Some(f2),
} = *then_stmt
else {
unreachable!()
};
let (nc, body, alt) = match shape {
Shape::ThenAnd => (sc(BinOp::LogAnd, c1, c2), t2, f2),
_ => (sc(BinOp::LogAnd, c1, not(c2)), f2, t2),
};
*st = Stmt::If {
cond: nc,
then_stmt: body,
else_stmt: Some(alt),
};
}
Shape::ElseOr | Shape::ElseOrNot => {
let Stmt::If {
cond: c2,
then_stmt: t2,
else_stmt: Some(f2),
} = *els
else {
unreachable!()
};
let body = then_stmt;
let (nc, alt) = match shape {
Shape::ElseOr => (sc(BinOp::LogOr, c1, c2), f2),
_ => (sc(BinOp::LogOr, c1, not(c2)), t2),
};
*st = Stmt::If {
cond: nc,
then_stmt: body,
else_stmt: Some(alt),
};
}
}
true
}
pub fn strip_clinit_returns(s: &mut Stmt) {
walk_mut_deep(s, &mut |st| {
if let Stmt::Block(v) = st {
v.retain(|x| !matches!(x, Stmt::Return(None)));
}
});
}
pub fn prepend_comment(s: &mut Stmt, text: String) {
let stmt = Stmt::Comment(text);
match s {
Stmt::Block(v) => v.insert(0, stmt),
other => {
let inner = std::mem::replace(other, Stmt::Block(vec![]));
*other = Stmt::Block(vec![stmt, inner]);
}
}
}
fn is_obj_expr(e: &Expr, obj_var: &dyn Fn(u32) -> bool) -> bool {
match e {
Expr::Local { var, .. } => obj_var(*var),
Expr::Const(_) => false,
_ => e.type_ref().erased().is_reference(),
}
}
fn null_side_rewrite(x: &mut Expr, obj_var: &dyn Fn(u32) -> bool) {
if let Expr::Bin { op, l, r, .. } = x {
if !matches!(op, BinOp::Eq | BinOp::Ne) {
return;
}
let l_zero = matches!(&**l, Expr::Const(ConstVal::Int(0)));
let r_zero = matches!(&**r, Expr::Const(ConstVal::Int(0)));
if l_zero && !r_zero && is_obj_expr(r, obj_var) {
**l = Expr::Const(ConstVal::Null);
} else if r_zero && !l_zero && is_obj_expr(l, obj_var) {
**r = Expr::Const(ConstVal::Null);
}
}
}
pub fn fused_expr_rewrites(s: &mut Stmt, vt: &VarTable) {
let mut obj_vars: Vec<bool> = Vec::with_capacity(vt.vars.len());
for v in &vt.vars {
obj_vars.push(v.ty.erased().is_reference());
}
rewrite_exprs(s, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Invokedynamic { name, args, .. } = x {
if name.starts_with('\0') && args.len() == 2 {
let (cls, ty) = match name.as_str() {
"\0cmp-long" => ("java/lang/Long", JavaType::Long),
"\0cmpl-float" | "\0cmpg-float" => ("java/lang/Float", JavaType::Float),
_ => ("java/lang/Double", JavaType::Double),
};
let desc = MethodDescriptor {
args: vec![ty.clone(), ty],
ret: JavaType::Int,
};
*x = Expr::Method {
owner: None,
cls: cls.into(),
name: "compare".into(),
desc: std::sync::Arc::new(desc),
args: args.clone(),
is_static: true,
is_interface: false,
is_special: false,
is_super: false,
is_dynamic: false,
type_args: vec![],
};
return;
}
}
null_side_rewrite(x, &|v| obj_vars.get(v as usize).copied().unwrap_or(false));
if let Expr::Bin { op, l, r, .. } = x {
if matches!(
op,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
) {
let l_const = matches!(&**l, Expr::Const(_));
let r_var = matches!(&**r, Expr::Local { .. });
if l_const && r_var {
std::mem::swap(l, r);
}
}
}
});
});
walk_mut_deep(s, &mut |st| {
if let Stmt::ExprStmt(Expr::Assign { target, value, .. }) = st {
let tgt_is_obj = match &**target {
Expr::Local { var, .. } => {
obj_vars.get(*var as usize).copied().unwrap_or(false)
}
other => other.type_ref().erased().is_reference(),
};
if tgt_is_obj {
if let Expr::Const(ConstVal::Int(0)) = &**value {
**value = Expr::Const(ConstVal::Null);
}
}
} else if let Stmt::LocalDef { var, init, .. } = st {
if obj_vars.get(*var as usize).copied().unwrap_or(false) {
if let Some(iv) = init {
if let Expr::Const(ConstVal::Int(0)) = iv {
*iv = Expr::Const(ConstVal::Null);
}
}
}
}
});
}
#[allow(dead_code)]
pub fn desugar_cmp_residuals(s: &mut Stmt) {
rewrite_exprs(s, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Invokedynamic { name, args, .. } = x {
if name.starts_with('\0') && args.len() == 2 {
let (cls, ty) = match name.as_str() {
"\0cmp-long" => ("java/lang/Long", JavaType::Long),
"\0cmpl-float" | "\0cmpg-float" => ("java/lang/Float", JavaType::Float),
_ => ("java/lang/Double", JavaType::Double),
};
let desc = jdc_core::types::MethodDescriptor {
args: vec![ty.clone(), ty],
ret: JavaType::Int,
};
*x = Expr::Method {
owner: None,
cls: cls.into(),
name: "compare".into(),
desc: std::sync::Arc::new(desc),
args: args.clone(),
is_static: true,
is_interface: false,
is_special: false,
is_super: false,
is_dynamic: false,
type_args: vec![],
};
}
}
});
});
}
pub fn ternary_fold(s: &mut Stmt) {
let mut changed = true;
let mut guard = 0;
while changed && guard < 8 {
changed = false;
guard += 1;
ternary_fold_walk(s, &mut changed);
strip_empty(s);
}
}
fn ternary_fold_walk(s: &mut Stmt, changed: &mut bool) {
if let Stmt::Block(v) = s {
for x in v.iter_mut() {
ternary_fold_walk(x, changed);
}
return;
}
let Stmt::If {
cond,
then_stmt,
else_stmt,
} = s
else {
return;
};
ternary_fold_walk(then_stmt, changed);
if let Some(e) = else_stmt {
ternary_fold_walk(e, changed);
}
let Some(else_b) = else_stmt else { return };
let target_of = |st: &Stmt| -> Option<u32> {
let Stmt::ExprStmt(Expr::Assign { target, .. }) = st else {
return None;
};
if let Expr::Local { var, .. } = &**target {
Some(*var)
} else {
None
}
};
let (Stmt::Block(tv), Stmt::Block(ev)) = (then_stmt.as_mut(), else_b.as_mut()) else {
return;
};
if tv.len() != 1 || ev.len() != 1 {
return;
}
let (Some(vt_), Some(ve)) = (target_of(&tv[0]), target_of(&ev[0])) else {
return;
};
if vt_ != ve {
return;
}
let mut touched = HashSet::default();
let (Stmt::ExprStmt(a_then), Stmt::ExprStmt(a_else)) = (&tv[0], &ev[0]) else {
return;
};
if let Expr::Assign { value, .. } = a_then {
collect_vars(value, &mut touched);
}
if let Expr::Assign { value, .. } = a_else {
collect_vars(value, &mut touched);
}
if touched.contains(&vt_) {
return;
}
let value_then = match a_then {
Expr::Assign { value, .. } => value.clone(),
_ => return,
};
let value_else = match a_else {
Expr::Assign { value, .. } => value.clone(),
_ => return,
};
let cond_e = cond.clone();
let folded = Stmt::ExprStmt(Expr::Assign {
target: Box::new(Expr::Local {
var: vt_,
ty: vt_join_ty(&value_then, &value_else),
}),
op: AssignOp::Plain,
value: Box::new(Expr::Cond {
c: Box::new(cond_e),
t: value_then,
f: value_else,
}),
});
*s = Stmt::Block(vec![folded]);
*changed = true;
}
fn vt_join_ty(a: &Expr, b: &Expr) -> TypeRef {
let ta = a.type_ref();
let tb = b.type_ref();
if ta.erased() == tb.erased() {
return ta;
}
TypeRef::J(JavaType::Object("java/lang/Object".into()))
}
pub fn fold_string_builders(s: &mut Stmt, vt: &VarTable) {
let n_vars = vt.vars.len().max(1);
let mut counts = vec![0usize; n_vars];
count_assignments(s, &mut counts);
let mut values: Vec<Option<Expr>> = vec![None; n_vars];
record_assignments(s, &counts, &mut values);
let mut appended_stmts: HashSet<u32> = HashSet::default();
walk_all(s, &mut |st| {
if let Stmt::ExprStmt(Expr::Method {
cls,
name,
owner,
args,
..
}) = st
{
if name.as_ref() == "append" && is_string_builder(cls) && args.len() == 1 {
if let Some(Expr::Local { var, .. }) = owner.as_deref() {
appended_stmts.insert(*var);
}
}
}
});
rewrite_exprs(s, &mut |e| {
fold_concat_in_expr(e, &values, &appended_stmts);
});
for _ in 0..8 {
let mut reads: HashSet<u32> = HashSet::default();
stmt_collect_vars(s, &mut reads, false);
if drop_unused_assigns(s, &reads) == 0 {
break;
}
}
}
pub fn count_stmts_deep(s: &Stmt, out: &mut usize) {
*out += 1;
walk_all(s, &mut |_| {
*out += 1;
});
}
struct VarAnalysis {
assigns: Vec<usize>,
reads: Vec<usize>,
values: Vec<Option<Expr>>,
}
fn analyze_vars(s: &Stmt, a: &mut VarAnalysis) {
walk_all(s, &mut |st| {
let exprs: Vec<&Expr> = match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
if let Expr::Local { var, .. } = &**target {
grow_to(&mut a.assigns, *var);
let idx = *var as usize;
a.assigns[idx] += 1;
if a.assigns[idx] == 1 {
if idx >= a.values.len() {
a.values.resize(idx + 1, None);
}
a.values[idx] = Some((**value).clone());
} else if idx < a.values.len() {
a.values[idx] = None;
}
}
let mut v: Vec<&Expr> = vec![value];
if !matches!(&**target, Expr::Local { .. }) {
v.push(target);
}
v
}
Stmt::ExprStmt(e @ (Expr::PreIncDec { .. } | Expr::PostIncDec { .. })) => {
let inner = match e {
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => e,
_ => unreachable!(),
};
if let Expr::Local { var, .. } = &**inner {
grow_to(&mut a.assigns, *var);
let idx = *var as usize;
a.assigns[idx] += 1;
if idx < a.values.len() {
a.values[idx] = None;
}
return;
}
vec![e]
}
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
vec![e]
}
Stmt::Return(Some(e)) => vec![e],
Stmt::LocalDef {
var, init: Some(e), ..
} => {
grow_to(&mut a.assigns, *var);
let idx = *var as usize;
a.assigns[idx] += 1;
if a.assigns[idx] == 1 {
if idx >= a.values.len() {
a.values.resize(idx + 1, None);
}
a.values[idx] = Some(e.clone());
} else if idx < a.values.len() {
a.values[idx] = None;
}
vec![e]
}
Stmt::If { cond, .. } => vec![cond],
Stmt::While { cond, .. } => vec![cond],
Stmt::DoWhile { cond, .. } => vec![cond],
_ => Vec::new(),
};
for e in exprs {
visit_exprs(e, &mut |x| {
if let Expr::Local { var, .. } = x {
grow_to(&mut a.reads, *var);
a.reads[*var as usize] += 1;
}
});
}
});
let n = a.assigns.len().max(a.reads.len()).max(a.values.len());
a.assigns.resize(n, 0);
a.reads.resize(n, 0);
a.values.resize(n, None);
}
fn grow_to(v: &mut Vec<usize>, var: u32) {
if var as usize >= v.len() {
v.resize(var as usize + 1, 0);
}
}
fn count_assignments(s: &Stmt, counts: &mut Vec<usize>) {
walk_all(s, &mut |st| match st {
Stmt::ExprStmt(Expr::Assign { target, .. }) => {
if let Expr::Local { var, .. } = &**target {
grow_to(counts, *var);
counts[*var as usize] += 1;
}
}
Stmt::LocalDef {
var, init: Some(_), ..
} => {
grow_to(counts, *var);
counts[*var as usize] += 1;
}
_ => {}
});
}
fn record_assignments(s: &Stmt, counts: &[usize], out: &mut Vec<Option<Expr>>) {
walk_all(s, &mut |st| match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
if let Expr::Local { var, .. } = &**target {
if counts.get(*var as usize).copied().unwrap_or(0) == 1 {
if *var as usize >= out.len() {
out.resize(*var as usize + 1, None);
}
out[*var as usize] = Some((**value).clone());
}
}
}
Stmt::LocalDef {
var, init: Some(e), ..
} if counts.get(*var as usize).copied().unwrap_or(0) == 1 => {
if *var as usize >= out.len() {
out.resize(*var as usize + 1, None);
}
out[*var as usize] = Some(e.clone());
}
_ => {}
});
}
fn fold_concat_in_expr(e: &mut Expr, values: &[Option<Expr>], appended: &HashSet<u32>) {
deep_rewrite(e, &mut |x| {
if let Expr::Method {
cls,
name,
args,
owner,
..
} = x
{
if name.as_ref() == "toString" && args.is_empty() && is_string_builder(cls) {
if let Some(o) = owner {
let mut has_stmt_appends = false;
visit_exprs(o, &mut |y| {
if let Expr::Local { var, .. } = y {
if appended.contains(var) {
has_stmt_appends = true;
}
}
});
if has_stmt_appends {
return;
}
let resolved = resolve_local(o, values);
if let Some(parts) = collect_sb_parts(&resolved, values, 0) {
if !parts.is_empty() {
*x = Expr::StringConcat(parts);
}
}
}
}
}
});
}
fn is_string_builder(cls: &str) -> bool {
cls == "java/lang/StringBuilder" || cls == "java/lang/StringBuffer"
}
fn resolve_local(e: &Expr, values: &[Option<Expr>]) -> Expr {
match e {
Expr::Local { var, ty } => values
.get(*var as usize)
.and_then(|o| o.clone())
.unwrap_or_else(|| Expr::Local {
var: *var,
ty: ty.clone(),
}),
other => other.clone(),
}
}
fn collect_sb_parts(e: &Expr, values: &[Option<Expr>], depth: u32) -> Option<Vec<ConcatPart>> {
if depth > 16 {
return None;
}
match e {
Expr::Method {
cls,
name,
args,
owner,
..
} if name.as_ref() == "append" && is_string_builder(cls) && args.len() == 1 => {
let mut parts =
collect_sb_parts(&resolve_local(owner.as_deref()?, values), values, depth + 1)?;
parts.push(ConcatPart::Str(args[0].clone()));
Some(parts)
}
Expr::New {
cls,
args,
raw: false,
..
} if is_string_builder(cls) => {
let mut parts = Vec::new();
for a in args {
if let Expr::Const(ConstVal::Str(sv)) = a {
parts.push(ConcatPart::Const(sv.to_string()));
} else {
parts.push(ConcatPart::Str(a.clone()));
}
}
Some(parts)
}
_ => None,
}
}
fn drop_unused_assigns(s: &mut Stmt, reads: &HashSet<u32>) -> usize {
let mut dropped = 0usize;
walk_mut_deep(s, &mut |st| {
if let Stmt::Block(v) = st {
let before = v.len();
v.retain(|x| match x {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &**target {
Expr::Local { var, .. } => reads.contains(var) || !is_sbish(value),
_ => true,
},
Stmt::LocalDef { var, init, .. } => {
reads.contains(var) || !init.as_ref().map(is_sbish).unwrap_or(false)
}
_ => true,
});
dropped += before - v.len();
}
});
dropped
}
fn is_sbish(e: &Expr) -> bool {
match e {
Expr::New {
cls, raw: false, ..
} => is_string_builder(cls),
Expr::Method { name, cls, .. } => name.as_ref() == "append" && is_string_builder(cls),
_ => false,
}
}
fn walk_mut_deep<F: FnMut(&mut Stmt)>(s: &mut Stmt, f: &mut F) {
f(s);
walk_mut(s, &mut |x| walk_mut_deep(x, f));
}
pub fn drop_empty_finallies(body: &mut Stmt) {
fn is_empty_block(s: &Stmt) -> bool {
match s {
Stmt::Block(v) => v.iter().all(is_empty_block),
_ => false,
}
}
walk_mut_deep(body, &mut |st| {
let Stmt::Try {
body: b,
catches,
finally,
} = st
else {
return;
};
if !catches.is_empty() {
return;
}
match finally {
None => {}
Some(f) if is_empty_block(f) => {}
Some(_) => return,
}
let inner = std::mem::replace(&mut **b, Stmt::Block(vec![]));
*st = inner;
});
}
pub fn final_dead_assigns(body: &mut Stmt) {
let mut reads: Vec<usize> = Vec::new();
count_reads(body, &mut reads);
let is_dead = |st: &Stmt| -> bool {
let Stmt::ExprStmt(Expr::Assign {
target,
value,
op: jdc_core::ir::expr::AssignOp::Plain,
..
}) = st
else {
return false;
};
let Expr::Local { var, .. } = &**target else {
return false;
};
reads.get(*var as usize).copied().unwrap_or(0) == 0 && !has_side_effects(value)
};
walk_mut_deep(body, &mut |st| {
if is_dead(st) {
*st = Stmt::Block(vec![]);
}
});
walk_mut_deep(body, &mut |st| {
if let Stmt::Block(v) = st {
v.retain(|x| !is_dead(x));
}
});
}
pub fn drop_pure_value_stmts(body: &mut Stmt) {
fn is_pure(s: &Stmt) -> bool {
let Stmt::ExprStmt(e) = s else {
return false;
};
!has_side_effects(e)
}
walk_mut_deep(body, &mut |st| {
if is_pure(st) {
*st = Stmt::Block(vec![]);
return;
}
match st {
Stmt::Switch { cases, .. } => {
for c in cases.iter_mut() {
c.body.retain(|x| !is_pure(x));
}
}
Stmt::For { init, .. } => init.retain(|x| !is_pure(x)),
_ => {}
}
});
}
pub fn drop_dead_raw_news(body: &mut Stmt) {
fn is_orphan(s: &Stmt) -> bool {
matches!(s, Stmt::ExprStmt(Expr::New { raw: true, .. }))
}
walk_mut_deep(body, &mut |st| match st {
Stmt::Block(v) => v.retain(|x| !is_orphan(x)),
Stmt::Switch { cases, .. } => {
for c in cases.iter_mut() {
c.body.retain(|x| !is_orphan(x));
}
}
Stmt::For { init, .. } => init.retain(|x| !is_orphan(x)),
Stmt::TryWithResources { resources, .. } => resources.retain(|x| !is_orphan(x)),
_ => {}
});
}
pub fn fold_synchronized(s: &mut Stmt) {
fold_sync_walk(s);
}
fn fold_sync_walk(s: &mut Stmt) {
walk_mut_deep(s, &mut |st| {
let Stmt::Block(v) = st else { return };
let mut i = 0;
while i < v.len() {
#[allow(unused_assignments)]
let mut advance = true;
if i + 1 < v.len() {
if let Some((repl, consumed)) = try_sync_at(v, i) {
advance = !matches!(repl.first(), Some(Stmt::Synchronized { .. }));
let _ = v.drain(i..i + consumed);
let n = repl.len();
v.splice(i..i, repl);
if advance {
i += n;
}
continue;
}
}
if let Some((repl, consumed)) = try_sync_in_try(v, i) {
let _ = v.drain(i..i + consumed);
let n = repl.len();
v.splice(i..i, repl);
i += n;
continue;
}
if let Some((repl, consumed)) = try_sync_deep(v, i) {
let _ = v.drain(i..i + consumed);
let n = repl.len();
v.splice(i..i, repl);
i += n;
continue;
}
if let Some((repl, consumed)) = try_sync_absorb_after(v, i) {
let _ = v.drain(i..i + consumed);
let n = repl.len();
v.splice(i..i, repl);
i += n;
continue;
}
i += 1;
}
});
}
fn is_pure_read_expr(e: &Expr) -> bool {
let mut pure = true;
visit_exprs(e, &mut |x| match x {
Expr::Const(_)
| Expr::Local { .. }
| Expr::This
| Expr::Field { .. } => {}
_ => pure = false,
});
pure
}
fn resolve_lock(e: &Expr, defs: &jdc_core::FxHashMap<u32, Expr>, depth: u32) -> Expr {
if depth >= 4 {
return e.clone();
}
if let Expr::Local { var, .. } = e {
if let Some(d) = defs.get(var) {
return resolve_lock(d, defs, depth + 1);
}
}
e.clone()
}
fn lock_same(a: &Expr, b: &Expr) -> bool {
match (a, b) {
(Expr::This, Expr::This) => true,
(Expr::Local { var: x, .. }, Expr::Local { var: y, .. }) => x == y,
(
Expr::Field {
owner: oa,
cls: ca,
name: na,
..
},
Expr::Field {
owner: ob,
cls: cb,
name: nb,
..
},
) => {
ca == cb
&& na == nb
&& match (oa, ob) {
(None, None) => true,
(Some(ra), Some(rb)) => lock_same(ra, rb),
_ => false,
}
}
(Expr::Const(x), Expr::Const(y)) => x == y,
_ => false,
}
}
fn lock_equiv(a: &Expr, b: &Expr, defs: &jdc_core::FxHashMap<u32, Expr>) -> bool {
lock_same(&resolve_lock(a, defs, 0), &resolve_lock(b, defs, 0))
}
fn flatten_leaves<'a>(s: &'a Stmt, out: &mut Vec<&'a Stmt>) {
match s {
Stmt::Block(v) => {
for x in v {
flatten_leaves(x, out);
}
}
_ => out.push(s),
}
}
fn strip_lock_exits(s: &mut Stmt, lock: &Expr, defs: &jdc_core::FxHashMap<u32, Expr>) {
walk_mut_deep(s, &mut |st| {
let Stmt::Block(v) = st else { return };
v.retain(|x| !matches!(x, Stmt::MonitorExit(e) if lock_equiv(e, lock, defs)));
});
}
fn try_sync_at(v: &[Stmt], i: usize) -> Option<(Vec<Stmt>, usize)> {
let mut defs: jdc_core::FxHashMap<u32, Expr> = jdc_core::FxHashMap::default();
for s in &v[..i] {
if let Stmt::LocalDef { var, init: Some(e), .. } = s {
defs.insert(*var, e.clone());
}
}
let (pre, enter_e, mut j, mut absorbed): (Vec<Stmt>, &Expr, usize, Vec<Stmt>) = match &v[i] {
Stmt::MonitorEnter(e) => (Vec::new(), e, i + 1, Vec::new()),
Stmt::Block(b) => {
let split = b
.iter()
.position(|x| matches!(x, Stmt::MonitorEnter(_)))?;
let e = match &b[split] {
Stmt::MonitorEnter(e) => e,
_ => unreachable!(),
};
let mut absorbed: Vec<Stmt> = Vec::new();
let mut k = split + 1;
while let Some(Stmt::LocalDef { var, init: Some(ex), .. }) = b.get(k) {
if !is_pure_read_expr(ex) {
break;
}
defs.insert(*var, ex.clone());
absorbed.push(b[k].clone());
k += 1;
}
if k != b.len() {
return None;
}
let pre: Vec<Stmt> = b[..split].to_vec();
for s in &pre {
if let Stmt::LocalDef { var, init: Some(e2), .. } = s {
defs.insert(*var, e2.clone());
}
}
(pre, e, i + 1, absorbed)
}
_ => return None,
};
let mut absorbed_vars: Vec<u32> = Vec::new();
while let Some(Stmt::LocalDef { var, init: Some(e), .. }) = v.get(j) {
if !is_pure_read_expr(e) {
break;
}
defs.insert(*var, e.clone());
absorbed_vars.push(*var);
absorbed.push(v[j].clone());
j += 1;
}
let Stmt::Try {
body,
catches,
finally,
} = v.get(j)?
else {
return None;
};
if finally.is_some() || catches.len() != 1 {
return None;
}
let c = &catches[0];
if !c.exc.is_empty() {
return None;
}
let mut flat: Vec<&Stmt> = Vec::new();
flatten_leaves(c.body.as_ref(), &mut flat);
if flat.len() < 2 {
return None;
}
let Stmt::MonitorExit(exit_e) = flat[0] else {
return None;
};
if !lock_equiv(enter_e, exit_e, &defs) {
return None;
}
if !matches!(flat[flat.len() - 1], Stmt::Throw(_)) {
return None;
}
for mid in &flat[1..flat.len() - 1] {
if !matches!(mid, Stmt::MonitorEnter(_) | Stmt::MonitorExit(_)) {
return None;
}
}
if !absorbed_vars.is_empty() {
let outside = count_locals_stmts(&v[j + 1..]);
if absorbed_vars
.iter()
.any(|av| outside.get(av).copied().unwrap_or(0) > 0)
{
return None;
}
}
let mut body_stmt = (**body).clone();
strip_lock_exits(&mut body_stmt, enter_e, &defs);
absorbed.push(body_stmt);
let mut consumed = j - i + 1;
if let Some(Stmt::MonitorExit(e)) = v.get(j + 1) {
if lock_equiv(e, enter_e, &defs) {
consumed += 1;
}
}
let mut repl = pre;
repl.push(Stmt::Synchronized {
lock: enter_e.clone(),
body: Box::new(Stmt::Block(absorbed)),
});
Some((repl, consumed))
}
fn try_sync_in_try(v: &[Stmt], i: usize) -> Option<(Vec<Stmt>, usize)> {
let Stmt::Try {
body,
catches,
finally,
} = &v[i]
else {
return None;
};
if finally.is_some() || catches.len() != 1 {
return None;
}
let c = &catches[0];
if !c.exc.is_empty() {
return None;
}
let mut flat: Vec<&Stmt> = Vec::new();
flatten_leaves(c.body.as_ref(), &mut flat);
if flat.len() < 2 {
return None;
}
let Stmt::MonitorExit(_) = flat[0] else {
return None;
};
if !matches!(flat[flat.len() - 1], Stmt::Throw(_)) {
return None;
}
for mid in &flat[1..flat.len() - 1] {
if !matches!(mid, Stmt::MonitorEnter(_) | Stmt::MonitorExit(_)) {
return None;
}
}
let Stmt::Block(bv) = body.as_ref() else {
return None;
};
let mut defs: jdc_core::FxHashMap<u32, Expr> = jdc_core::FxHashMap::default();
for s in &v[..i] {
if let Stmt::LocalDef { var, init: Some(e), .. } = s {
defs.insert(*var, e.clone());
}
}
let mut pre: Vec<Stmt> = Vec::new();
let mut enter_e: Option<Expr> = None;
let mut absorbed: Vec<Stmt> = Vec::new();
let mut absorbed_vars: Vec<u32> = Vec::new();
let mut rest_start: Option<usize> = None;
for (p, el) in bv.iter().enumerate() {
match el {
Stmt::MonitorEnter(e) => {
enter_e = Some(e.clone());
rest_start = Some(p + 1);
break;
}
Stmt::Block(inner) => {
let split = inner
.iter()
.position(|x| matches!(x, Stmt::MonitorEnter(_)));
if let Some(sp) = split {
let mut k = sp + 1;
let mut ok = true;
let mut tail: Vec<Stmt> = Vec::new();
while let Some(Stmt::LocalDef { var: _, init: Some(ex), .. }) = inner.get(k) {
if !is_pure_read_expr(ex) {
ok = false;
break;
}
tail.push(inner[k].clone());
k += 1;
}
if ok && k == inner.len() {
for s in &inner[..sp] {
if let Stmt::LocalDef { var, init: Some(ex), .. } = s {
defs.insert(*var, ex.clone());
}
}
pre.extend(inner[..sp].iter().cloned());
enter_e = Some(match &inner[sp] {
Stmt::MonitorEnter(e) => e.clone(),
_ => unreachable!(),
});
absorbed = tail;
rest_start = Some(p + 1);
break;
}
}
for s in inner.iter() {
if let Stmt::LocalDef { var, init: Some(ex), .. } = s {
defs.insert(*var, ex.clone());
}
}
pre.push(el.clone());
}
other => {
if let Stmt::LocalDef { var, init: Some(ex), .. } = other {
defs.insert(*var, ex.clone());
}
pre.push(other.clone());
}
}
}
let enter_e = enter_e?;
let rs = rest_start?;
let mut q = rs;
while let Some(Stmt::LocalDef { var, init: Some(ex), .. }) = bv.get(q) {
if !is_pure_read_expr(ex) {
break;
}
defs.insert(*var, ex.clone());
absorbed_vars.push(*var);
absorbed.push(bv[q].clone());
q += 1;
}
let rest: Vec<Stmt> = bv[q..].to_vec();
if rest.is_empty() {
return None;
}
if pre
.iter()
.any(|s| !matches!(s, Stmt::LocalDef { init: Some(e), .. } if is_pure_read_expr(e)))
{
return None;
}
let Stmt::MonitorExit(exit_e) = flat[0] else {
return None;
};
if !lock_equiv(&enter_e, exit_e, &defs) {
return None;
}
let outside_vars: Vec<u32> = pre
.iter()
.chain(absorbed.iter())
.filter_map(|s| match s {
Stmt::LocalDef { var, .. } => Some(*var),
_ => None,
})
.collect();
if !outside_vars.is_empty() {
let outside = count_locals_stmts(&v[i + 1..]);
if outside_vars
.iter()
.any(|av| outside.get(av).copied().unwrap_or(0) > 0)
{
return None;
}
}
let mut body_stmts = pre;
body_stmts.extend(absorbed);
body_stmts.extend(rest);
let mut sync_body = Stmt::Block(body_stmts);
strip_lock_exits(&mut sync_body, &enter_e, &defs);
let mut consumed = 1;
if let Some(Stmt::MonitorExit(e)) = v.get(i + 1) {
if lock_equiv(e, &enter_e, &defs) {
consumed += 1;
}
}
Some((
vec![Stmt::Synchronized {
lock: enter_e,
body: Box::new(sync_body),
}],
consumed,
))
}
fn try_sync_absorb_after(v: &[Stmt], i: usize) -> Option<(Vec<Stmt>, usize)> {
let Stmt::Synchronized { lock, body } = &v[i] else {
return None;
};
let Stmt::Try {
body: tbody,
catches,
finally,
} = v.get(i + 1)?
else {
return None;
};
if finally.is_some() {
return None;
}
let defs = defs_probe(v, i, body);
let mut absorbed_mid: Vec<Stmt> = Vec::new();
let mut rest_after: Option<Stmt> = None;
let mut found = false;
let mut scan = i + 2;
'scan: while scan < v.len() && scan < i + 14 {
match &v[scan] {
Stmt::MonitorExit(e) => {
if !lock_equiv(e, lock, &defs) {
return None;
}
found = true;
scan += 1;
break;
}
Stmt::Block(bv) => {
for (k, s) in bv.iter().enumerate() {
match s {
Stmt::MonitorExit(e) => {
if !lock_equiv(e, lock, &defs) {
return None;
}
absorbed_mid.push(Stmt::Block(bv[..k].to_vec()));
let rest = &bv[k + 1..];
rest_after = (!rest.is_empty())
.then(|| Stmt::Block(rest.to_vec()));
found = true;
scan += 1;
break 'scan;
}
Stmt::ExprStmt(e) if mid_safe(e) || is_local_commit(e) => {}
_ => break 'scan,
}
}
absorbed_mid.push(v[scan].clone());
scan += 1;
}
Stmt::ExprStmt(e) if mid_safe(e) || is_local_commit(e) => {
absorbed_mid.push(v[scan].clone());
scan += 1;
}
_ => break,
}
}
if !found {
return None;
}
let consumed = scan - i;
let mut t = (**tbody).clone();
let defs = defs_probe(v, i, body);
strip_lock_exits(&mut t, lock, &defs);
let mut cs = Vec::with_capacity(catches.len());
for c in catches.iter() {
let mut cb = (*c.body).clone();
strip_lock_exits(&mut cb, lock, &defs);
let bare_rethrow = c.exc.is_empty() && {
let mut leaves: Vec<&Stmt> = Vec::new();
flatten_leaves(&cb, &mut leaves);
matches!(leaves.as_slice(), [Stmt::Throw(Expr::Local { var, .. })] if *var == c.var)
};
if bare_rethrow {
continue;
}
let mut c2 = c.clone();
*c2.body = cb;
cs.push(c2);
}
let mut new_body = (**body).clone();
let adopted = Stmt::Try {
body: Box::new(t),
catches: cs,
finally: None,
};
match &mut new_body {
Stmt::Block(bv) => bv.push(adopted),
other => {
let inner = std::mem::replace(other, Stmt::Block(vec![]));
*other = Stmt::Block(vec![inner, adopted]);
}
}
if let Stmt::Block(bv) = &mut new_body {
bv.extend(absorbed_mid);
}
let sync = Stmt::Synchronized {
lock: lock.clone(),
body: Box::new(new_body),
};
let mut repl = vec![sync];
if let Some(r) = rest_after {
repl.push(r);
}
Some((repl, consumed))
}
fn mid_safe(e: &Expr) -> bool {
!has_side_effects(e)
}
fn is_local_commit(e: &Expr) -> bool {
match e {
Expr::Assign { target, op: AssignOp::Plain, .. } => {
matches!(&**target, Expr::Local { .. })
}
_ => false,
}
}
fn defs_probe<'a>(
v: &'a [Stmt],
i: usize,
body: &'a Stmt,
) -> jdc_core::FxHashMap<u32, Expr> {
let mut defs: jdc_core::FxHashMap<u32, Expr> = jdc_core::FxHashMap::default();
for s in &v[..i] {
if let Stmt::LocalDef { var, init: Some(e), .. } = s {
defs.insert(*var, e.clone());
}
}
if let Stmt::Block(bv) = body {
for s in bv {
if let Stmt::LocalDef { var, init: Some(e), .. } = s {
defs.insert(*var, e.clone());
}
}
}
defs
}
fn try_sync_deep(v: &[Stmt], i: usize) -> Option<(Vec<Stmt>, usize)> {
let Stmt::Try {
body,
catches,
finally,
} = &v[i]
else {
return None;
};
if finally.is_some() || catches.len() != 1 {
return None;
}
let c = &catches[0];
if !c.exc.is_empty() {
return None;
}
let mut flat: Vec<&Stmt> = Vec::new();
flatten_leaves(c.body.as_ref(), &mut flat);
if flat.len() < 2 {
return None;
}
let catch_exit = match flat[0] {
Stmt::MonitorExit(e) => e.clone(),
_ => return None,
};
if !matches!(flat[flat.len() - 1], Stmt::Throw(_)) {
return None;
}
for mid in &flat[1..flat.len() - 1] {
if !matches!(mid, Stmt::MonitorEnter(_) | Stmt::MonitorExit(_)) {
return None;
}
}
let mut defs0: jdc_core::FxHashMap<u32, Expr> = jdc_core::FxHashMap::default();
for s in &v[..i] {
if let Stmt::LocalDef { var, init: Some(e), .. } = s {
defs0.insert(*var, e.clone());
}
}
let mut body_mut = (**body).clone();
let folded = match &mut body_mut {
Stmt::Block(b) => try_fold_spine(b, &catch_exit, &defs0),
Stmt::If {
then_stmt,
else_stmt,
..
} => {
let mut hit = false;
if let Stmt::Block(b) = &mut **then_stmt {
hit |= try_fold_spine(b, &catch_exit, &defs0);
}
if !hit {
if let Some(els) = else_stmt {
if let Stmt::Block(b) = &mut **els {
hit |= try_fold_spine(b, &catch_exit, &defs0);
}
}
}
hit
}
_ => false,
};
if !folded {
return None;
}
let throw = flat[flat.len() - 1].clone();
let mut catches_out = catches.clone();
*catches_out[0].body = Stmt::Block(vec![throw]);
let mut consumed = 1;
if let Some(Stmt::MonitorExit(e)) = v.get(i + 1) {
if lock_equiv(e, &catch_exit, &defs0) {
consumed += 1;
}
}
Some((
vec![Stmt::Try {
body: Box::new(body_mut),
catches: catches_out,
finally: None,
}],
consumed,
))
}
fn try_fold_spine(
seq: &mut Vec<Stmt>,
catch_exit: &Expr,
defs_in: &jdc_core::FxHashMap<u32, Expr>,
) -> bool {
for p in (0..seq.len()).rev() {
if let Stmt::MonitorEnter(e0) = &seq[p] {
let mut defs = defs_in.clone();
for s in &seq[..p] {
if let Stmt::LocalDef { var, init: Some(ex), .. } = s {
defs.insert(*var, ex.clone());
}
}
for s in &seq[p + 1..] {
match s {
Stmt::LocalDef { var, init: Some(ex), .. } if is_pure_read_expr(ex) => {
defs.insert(*var, ex.clone());
}
_ => break,
}
}
if !lock_equiv(e0, catch_exit, &defs) {
return false;
}
let enter_e = e0.clone();
let rest: Vec<Stmt> = seq.split_off(p + 1);
let mut body = Stmt::Block(rest);
strip_lock_exits(&mut body, &enter_e, &defs);
seq[p] = Stmt::Synchronized {
lock: enter_e,
body: Box::new(body),
};
return true;
}
let is_last = p + 1 == seq.len();
let mut trial = seq[p].clone();
let hit = match &mut trial {
Stmt::Block(b) => try_fold_spine(b, catch_exit, defs_in),
Stmt::If {
then_stmt,
else_stmt,
..
} => {
let mut hit = false;
if let Stmt::Block(b) = &mut **then_stmt {
hit |= try_fold_spine(b, catch_exit, defs_in);
}
if !hit {
if let Some(els) = else_stmt {
if let Stmt::Block(b) = &mut **els {
hit |= try_fold_spine(b, catch_exit, defs_in);
}
}
}
hit
}
_ => false,
};
if hit {
if is_last {
seq[p] = trial;
return true;
}
return false;
}
}
false
}
pub fn forward_single_use(s: &mut Stmt, _vt: &VarTable) {
let mut analysis = VarAnalysis {
assigns: Vec::new(),
reads: Vec::new(),
values: Vec::new(),
};
analyze_vars(s, &mut analysis);
let mut assigns = analysis.assigns;
let mut reads = analysis.reads;
let n_vars = assigns.len().max(_vt.vars.len()).max(1);
assigns.resize(n_vars, 0);
reads.resize(n_vars, 0);
let mut values = analysis.values;
values.resize(n_vars, None);
let mut cand = vec![false; n_vars];
let mut edges: Vec<Vec<u32>> = vec![Vec::new(); n_vars];
for (v, cand_v) in cand.iter_mut().enumerate() {
if assigns.get(v).copied().unwrap_or(0) != 1 || reads.get(v).copied().unwrap_or(0) != 1 {
continue;
}
if let Some(val) = values.get(v).and_then(|o| o.as_ref()) {
let mut refs = HashSet::default();
collect_vars(val, &mut refs);
if refs.iter().any(|r| assigns[*r as usize] > 1) {
continue;
}
edges[v] = refs.into_iter().collect();
}
*cand_v = true;
}
let mut color = vec![0u8; n_vars]; let mut bad = vec![false; n_vars];
for root in 0..n_vars {
if color[root] != 0 || edges[root].is_empty() {
continue;
}
color[root] = 1;
let mut stack: Vec<(usize, usize)> = vec![(root, 0)];
while let Some(top) = stack.last_mut() {
let v = top.0;
let i = top.1;
top.1 += 1;
if i < edges[v].len() {
let r = edges[v][i] as usize;
if color[r] == 1 {
for &(pn, _) in &stack {
bad[pn] = true;
}
} else if color[r] == 0 {
color[r] = 1;
stack.push((r, 0));
} else if bad[r] {
for &(pn, _) in &stack {
bad[pn] = true;
}
}
} else {
color[v] = 2;
stack.pop();
}
}
}
let single: Vec<bool> = (0..n_vars).map(|v| cand[v] && !bad[v]).collect();
if !single.iter().any(|&b| b) {
return;
}
let pure: Vec<bool> = (0..n_vars)
.map(|v| {
single[v]
&& values
.get(v)
.and_then(|o| o.as_ref())
.map(|e| !has_side_effects(e))
.unwrap_or(false)
})
.collect();
let impure: Vec<bool> = (0..n_vars).map(|v| single[v] && !pure[v]).collect();
if pure.iter().any(|&b| b) {
let vals: Vec<Option<Expr>> = (0..n_vars)
.map(|v| {
if pure[v] {
values.get(v).cloned().flatten()
} else {
None
}
})
.collect();
rewrite_exprs(s, &mut |e| {
deep_rewrite_reads(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if let Some(Some(v)) = vals.get(*var as usize) {
*x = v.clone();
}
}
});
});
drop_defs(s, &pure);
}
if impure.iter().any(|&b| b) {
forward_adjacent_impure(s, &values, &impure);
}
}
fn count_reads(s: &Stmt, out: &mut Vec<usize>) {
walk_all(s, &mut |st| {
let exprs: Vec<&Expr> = match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
let mut v: Vec<&Expr> = vec![value];
if !matches!(&**target, Expr::Local { .. }) {
v.push(target);
}
v
}
Stmt::ExprStmt(e @ (Expr::PreIncDec { .. } | Expr::PostIncDec { .. })) => {
let inner = match e {
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => e,
_ => unreachable!(),
};
if matches!(**inner, Expr::Local { .. }) {
Vec::new()
} else {
vec![e]
}
}
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
vec![e]
}
Stmt::Return(Some(e)) => vec![e],
Stmt::LocalDef { init: Some(e), .. } => vec![e],
Stmt::If { cond, .. } => vec![cond],
Stmt::While { cond, .. } => vec![cond],
Stmt::DoWhile { cond, .. } => vec![cond],
Stmt::Switch { selector, .. } => vec![selector],
Stmt::ForEach { iterable, .. } => vec![iterable],
Stmt::Synchronized { lock, .. } => vec![lock],
Stmt::For { cond, update, .. } => {
let mut v: Vec<&Expr> = Vec::with_capacity(update.len() + 1);
if let Some(c) = cond {
v.push(c);
}
v.extend(update.iter());
v
}
Stmt::Assert { cond, msg } => {
let mut v: Vec<&Expr> = vec![cond];
if let Some(m) = msg {
v.push(m);
}
v
}
_ => Vec::new(),
};
for e in exprs {
visit_exprs(e, &mut |x| {
if let Expr::Local { var, .. } = x {
grow_to(out, *var);
out[*var as usize] += 1;
}
});
}
});
}
fn drop_defs(s: &mut Stmt, vars: &[bool]) {
walk_mut_deep(s, &mut |st| {
if let Stmt::Block(v) = st {
v.retain(|x| match x {
Stmt::ExprStmt(Expr::Assign { target, .. }) => match &**target {
Expr::Local { var, .. } => !vars.get(*var as usize).copied().unwrap_or(false),
_ => true,
},
Stmt::LocalDef { var, .. } => !vars.get(*var as usize).copied().unwrap_or(false),
_ => true,
});
}
});
}
fn forward_adjacent_impure(s: &mut Stmt, values: &[Option<Expr>], impure: &[bool]) {
if let Stmt::Block(v) = s {
let mut i = 0;
while i + 1 < v.len() {
let def_var = match &v[i] {
Stmt::ExprStmt(Expr::Assign { target, .. }) => match &**target {
Expr::Local { var, .. } => Some(*var),
_ => None,
},
Stmt::LocalDef { var, .. } => Some(*var),
_ => None,
};
if let Some(var) = def_var {
if impure.get(var as usize).copied().unwrap_or(false) {
let mut reads_here = vec![0usize; values.len().max(1)];
count_reads(&v[i + 1], &mut reads_here);
if reads_here.get(var as usize).copied().unwrap_or(0) == 1 {
let val = match &v[i] {
Stmt::ExprStmt(Expr::Assign { value, .. }) => (**value).clone(),
Stmt::LocalDef { init: Some(e), .. } => (*e).clone(),
_ => {
i += 1;
continue;
}
};
rewrite_exprs(&mut v[i + 1], &mut |e| {
deep_rewrite_reads(e, &mut |x| {
if let Expr::Local { var: v2, .. } = x {
if *v2 == var {
*x = val.clone();
}
}
});
});
v.remove(i);
i = i.saturating_sub(1);
continue;
}
}
}
i += 1;
}
}
walk_mut(s, &mut |x| forward_adjacent_impure(x, values, impure));
}
pub fn infer_types(vt: &mut VarTable, body: &mut Stmt, ret: &JavaType, env: &MethodEnv) {
let n = vt.vars.len();
let mut evidence: Vec<Vec<(JavaType, bool)>> = vec![Vec::new(); n];
let ev = |evidence: &mut Vec<Vec<(JavaType, bool)>>, var: u32, t: JavaType, strong: bool| {
if (var as usize) < evidence.len() {
evidence[var as usize].push((t, strong));
}
};
walk_all(body, &mut |st| match st {
Stmt::LocalDef { var, init, .. } => {
if let Some(e) = init {
expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t, false));
let strong = match e {
Expr::Const(_) => false,
Expr::Local { var: src, .. } => {
let src_ty = vt.vars.get(*src as usize).map(|v| v.ty.erased());
let tgt_ty = vt.vars.get(*var as usize).map(|v| v.ty.erased());
match (src_ty, tgt_ty) {
(Some(s), Some(g)) => {
(s.is_numeric() && g.is_numeric())
|| (s.is_reference() && g.is_reference())
}
_ => false,
}
}
_ => true,
};
ev(&mut evidence, *var, e.type_ref().erased(), strong);
}
}
Stmt::ExprStmt(e) => {
if let Expr::Assign { target, value, .. } = e {
if let Expr::Field { owner: Some(o), cls, is_static: false, .. } = &**target {
if cls.is_empty() {
} else if let Expr::Local { var, .. } = &**o {
ev(
&mut evidence,
*var,
JavaType::Object(cls.clone()),
true,
);
}
}
if let Expr::Local { var, .. } = &**target {
let strong = match &**value {
Expr::Const(_) => false,
Expr::Local { var: src, .. } => {
let src_ty = vt.vars.get(*src as usize).map(|v| v.ty.erased());
let tgt_ty = vt.vars.get(*var as usize).map(|v| v.ty.erased());
match (src_ty, tgt_ty) {
(Some(s), Some(g)) => {
(s.is_numeric() && g.is_numeric())
|| (s.is_reference() && g.is_reference())
}
_ => false,
}
}
_ => true,
};
ev(&mut evidence, *var, value.type_ref().erased(), strong);
}
}
expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t, false));
}
Stmt::Throw(e) => {
expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t, false));
if let Expr::Local { var, .. } = e {
ev(
&mut evidence,
*var,
JavaType::Object("java/lang/Throwable".into()),
true,
);
}
}
Stmt::Return(Some(e)) => {
expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t, false));
if let Expr::Local { var, .. } = e {
ev(&mut evidence, *var, ret.clone(), true);
}
}
Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
if let Expr::Local { var, .. } = e {
ev(
&mut evidence,
*var,
JavaType::Object("java/lang/Object".into()),
true,
);
}
}
Stmt::ForEach {
var,
iterable,
is_array,
..
} => {
if let Expr::Local { var: v0, .. } = iterable {
if *is_array {
ev(&mut evidence, *v0, JavaType::Array(Box::new(JavaType::Int)), true);
} else {
ev(
&mut evidence,
*v0,
JavaType::Object("java/lang/Object".into()),
true,
);
}
}
let _ = var;
}
_ => {}
});
let _ = env;
let mut derefed: Vec<bool> = vec![false; vt.vars.len()];
mark_derefs(body, &mut derefed);
let mut arr_derefed: Vec<bool> = vec![false; vt.vars.len()];
visit_all_exprs(body, &mut |x| {
if let Expr::ArrayIndex { array, .. } = x {
if let Expr::Local { var, .. } = &**array {
if (*var as usize) < arr_derefed.len() {
arr_derefed[*var as usize] = true;
}
}
}
if let Expr::Bin { op, l, r, .. } = x {
if matches!(op, BinOp::Eq | BinOp::Ne)
&& (matches!(&**r, Expr::Const(ConstVal::Null))
|| matches!(&**l, Expr::Const(ConstVal::Null)))
{
for side in [l, r] {
if let Expr::Local { var, .. } = &**side {
ev(
&mut evidence,
*var,
JavaType::Object("java/lang/Object".into()),
true,
);
}
}
}
}
});
const FINAL_JDK: &[&str] = &[
"java/lang/Boolean",
"java/lang/String",
"java/lang/Integer",
"java/lang/Long",
"java/lang/Short",
"java/lang/Byte",
"java/lang/Character",
"java/lang/Float",
"java/lang/Double",
];
for (i, evs) in evidence.iter().enumerate() {
let info = &vt.vars[i];
if info.is_param {
continue;
}
let strong: Vec<JavaType> = evs
.iter()
.filter(|(_, s)| *s)
.map(|(t, _)| t.clone())
.collect();
let all: Vec<JavaType> = evs.iter().map(|(t, _)| t.clone()).collect();
if info.ty.erased().is_reference() {
if strong.is_empty() {
let refs: std::collections::HashSet<&str> = all
.iter()
.filter_map(|t| match t {
JavaType::Object(n) if n.as_ref() != "java/lang/Object" => {
Some(n.as_ref())
}
_ => None,
})
.collect();
let mixed = all.iter().any(|t| t.is_numeric());
if refs.len() >= 2 || (mixed && !refs.is_empty()) || (mixed && all.iter().any(|t| matches!(t, JavaType::Array(_)))) {
vt.vars[i].ty =
TypeRef::J(JavaType::Object("java/lang/Object".into()));
continue;
}
}
if arr_derefed[i] {
let arr = strong
.iter()
.chain(all.iter())
.find(|t| matches!(t, JavaType::Array(_)))
.cloned();
if let Some(t) = arr {
vt.vars[i].ty = TypeRef::J(t);
continue;
}
}
let deref_pick = || {
if !derefed[i] {
return None;
}
strong
.iter()
.find(|t| {
matches!(t, JavaType::Object(n)
if !n.is_empty()
&& n.as_ref() != "java/lang/Object"
&& !FINAL_JDK.contains(&n.as_ref()))
})
.cloned()
};
if let Some(t) = deref_pick()
.or_else(|| pick_object(&strong))
.or_else(|| pick_object(&all))
{
vt.vars[i].ty = TypeRef::J(t);
}
} else if let Some(t) = pick_object(&strong) {
vt.vars[i].ty = TypeRef::J(t);
} else if strong.contains(&JavaType::Boolean) {
vt.vars[i].ty = TypeRef::J(JavaType::Boolean);
} else if !all.is_empty() && all.iter().all(|t| t.is_numeric()) {
if let Some(t) = pick_numeric(&all) {
vt.vars[i].ty = TypeRef::J(t);
}
} else {
let refs = all.iter().filter(|t| t.is_reference()).count();
let nums = all.iter().filter(|t| t.is_numeric()).count();
if refs > 0 && nums > 0 {
vt.vars[i].ty =
TypeRef::J(JavaType::Object("java/lang/Object".into()));
} else if let Some(t) = pick_object(&strong).or_else(|| pick_object(&all)) {
vt.vars[i].ty = TypeRef::J(t);
}
}
}
let types: Vec<&TypeRef> = vt.vars.iter().map(|v| &v.ty).collect();
rewrite_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, ty } = x {
if let Some(want) = types.get(*var as usize) {
if ty != *want {
*ty = (*want).clone();
}
}
}
});
});
coerce_num_consts(body, &types);
}
fn coerce_num_consts(body: &mut Stmt, types: &[&TypeRef]) {
walk_mut_deep(body, &mut |st| {
let (var, val): (u32, &mut Expr) = match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &mut **target {
Expr::Local { var, .. } => (*var, value),
_ => return,
},
Stmt::LocalDef {
var, init: Some(e), ..
} => (*var, e),
_ => return,
};
let want = match types.get(var as usize) {
Some(TypeRef::J(t)) => t.clone(),
_ => return,
};
let rewritten = match &*val {
Expr::Const(ConstVal::Long(l)) => match &want {
JavaType::Double => Some(Expr::Const(ConstVal::Double(*l as f64))),
JavaType::Float => Some(Expr::Const(ConstVal::Float(*l as f32))),
_ => None,
},
Expr::Const(ConstVal::Int(i)) => match &want {
JavaType::Double => Some(Expr::Const(ConstVal::Double(*i as f64))),
JavaType::Float => Some(Expr::Const(ConstVal::Float(*i as f32))),
_ => None,
},
_ => None,
};
if let Some(e) = rewritten {
*val = e;
}
});
}
fn pick_object(evs: &[JavaType]) -> Option<JavaType> {
evs.iter()
.find(|t| {
t.is_reference()
&& !matches!(t, JavaType::Object(n) if n.is_empty() || n.as_ref() == "java/lang/Object")
})
.cloned()
}
fn pick_numeric(evs: &[JavaType]) -> Option<JavaType> {
if evs.is_empty() {
return None;
}
let mut t = evs[0].clone();
for x in &evs[1..] {
t = join_numeric(&t, x);
}
Some(t)
}
fn join_numeric(a: &JavaType, b: &JavaType) -> JavaType {
if a == b {
return a.clone();
}
match (a, b) {
(JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
(JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
(JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
(JavaType::Int, x) if x.is_integral() => JavaType::Int,
(x, JavaType::Int) if x.is_integral() => JavaType::Int,
(x, y) if x.is_integral() && y.is_integral() => JavaType::Int,
_ => JavaType::Int,
}
}
fn expr_evidence<F: FnMut(u32, JavaType)>(e: &Expr, f: &mut F) {
visit_exprs(e, &mut |x| match x {
Expr::Method {
owner,
args,
desc,
is_static,
cls,
..
} => {
if !*is_static {
if let Some(o) = owner {
if let Expr::Local { var, .. } = &**o {
f(*var, JavaType::Object(cls.clone()));
}
}
}
for (i, a) in args.iter().enumerate() {
if let Expr::Local { var, .. } = a {
if let Some(t) = desc.args.get(i) {
f(*var, t.clone());
}
}
}
}
Expr::Field {
owner,
cls,
ty,
is_static,
..
} => {
if !*is_static && !cls.is_empty() {
if let Some(o) = owner {
if let Expr::Local { var, .. } = &**o {
f(*var, JavaType::Object(cls.clone()));
}
}
}
let _ = ty;
}
Expr::Bin { op, l, r, .. }
if matches!(op, BinOp::Eq | BinOp::Ne)
&& (matches!(&**r, Expr::Const(ConstVal::Null))
|| matches!(&**l, Expr::Const(ConstVal::Null))) =>
{
for side in [l, r] {
if let Expr::Local { var, .. } = &**side {
f(*var, JavaType::Object("java/lang/Object".into()));
}
}
}
Expr::ArrayIndex { array, .. } => {
if let Expr::Local { var, ty, .. } = &**array {
let t = ty.erased();
if matches!(t, JavaType::Array(_)) {
f(*var, t);
} else {
f(*var, JavaType::Array(Box::new(JavaType::Int)));
}
}
}
Expr::Assign { .. } => {
}
Expr::Cast { e: inner, .. } => {
let _ = inner;
}
Expr::InstanceOf { e: inner, .. } => {
let _ = inner;
}
_ => {}
});
}
pub fn mark_field_owner_concrete(vt: &mut VarTable, body: &mut Stmt) {
let mut owners: HashSet<u32> = HashSet::default();
rewrite_exprs(body, &mut |e| {
visit_exprs(e, &mut |x| {
if let Expr::Field {
owner: Some(o),
is_static: false,
..
} = x
{
if let Expr::Local { var, .. } = &**o {
owners.insert(*var);
}
}
});
});
for var in owners {
let i = var as usize;
if i >= vt.vars.len() {
continue;
}
if let JavaType::Object(cls) = vt.vars[i].ty.erased() {
if let Some(last) = cls.rsplit('$').next() {
if !last.is_empty() && last.chars().all(|c| c.is_ascii_digit()) {
vt.force_concrete_vars.insert(var);
}
}
}
}
let concrete: Vec<u32> = vt
.vars
.iter()
.filter(|v| {
if let JavaType::Object(cls) = v.ty.erased() {
cls.rsplit('$')
.next()
.is_some_and(|l| !l.is_empty() && l.chars().all(|c| c.is_ascii_digit()))
} else {
false
}
})
.map(|v| v.id)
.collect();
for var in concrete {
vt.force_concrete_vars.insert(var);
}
}
pub fn insert_object_narrowing_casts(
vt: &VarTable,
body: &mut Stmt,
pool: &DexPool,
ret: &JavaType,
) {
let value_ty = |e: &Expr| -> JavaType {
match e {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
}
};
let is_top_object_ty = |v: &JavaType| -> bool {
matches!(v, JavaType::Object(c) if c.as_ref() == "java/lang/Object")
};
let specific_ref = |ty: &TypeRef| -> Option<TypeRef> {
match ty {
TypeRef::J(JavaType::Object(c)) if c.as_ref() != "java/lang/Object" => {
Some(ty.clone())
}
TypeRef::J(JavaType::Array(_)) => Some(ty.clone()),
_ => match ty.erased() {
JavaType::Object(c) if c.as_ref() != "java/lang/Object" => Some(ty.clone()),
JavaType::Array(_) => Some(ty.clone()),
_ => None,
},
}
};
let non_cast_shape = |e: &Expr| -> bool {
!matches!(e, Expr::Const(_) | Expr::Cast { .. } | Expr::InstanceOf { .. })
};
let needs_downcast = |vty: &JavaType, tt: &JavaType| -> bool {
let (JavaType::Object(vn), JavaType::Object(tn)) = (vty, tt) else {
return false;
};
if vn.as_ref() == "java/lang/Object" || tn.as_ref() == "java/lang/Object" {
return false;
}
vn != tn && !pool.is_subtype(vn, tn)
};
let wants_cast = |e: &Expr, vty: &JavaType, tt: &JavaType| -> bool {
non_cast_shape(e) && (is_top_object_ty(vty) || needs_downcast(vty, tt))
};
walk_mut_deep(body, &mut |st| match st {
Stmt::ExprStmt(Expr::Assign {
target,
value,
op: AssignOp::Plain,
..
}) => {
let tgt_erased: JavaType;
let tgt_ref: Option<&TypeRef>;
match &**target {
Expr::Local { var, .. } => {
let ty = &vt.var(*var).ty;
tgt_erased = ty.erased();
tgt_ref = Some(ty);
}
Expr::Field { ty, .. } => {
tgt_erased = ty.erased();
tgt_ref = Some(ty);
}
Expr::ArrayIndex { array, .. } => {
let aty = match &**array {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
};
match aty {
JavaType::Array(inner) => {
tgt_erased = *inner;
tgt_ref = None;
}
_ => return,
}
}
_ => return,
}
let specific = match tgt_ref {
Some(tr) => specific_ref(tr),
None => specific_ref(&TypeRef::J(tgt_erased.clone())),
};
let Some(t) = specific else {
return;
};
let vty = value_ty(value);
if wants_cast(value, &vty, &tgt_erased) {
let v = std::mem::replace(value, Box::new(Expr::This));
**value = Expr::Cast { ty: t, e: v };
}
}
Stmt::LocalDef { var, init: Some(value), .. } => {
if let Some(t) = specific_ref(&vt.var(*var).ty) {
let tt = vt.var(*var).ty.erased();
let vty = value_ty(value);
if wants_cast(value, &vty, &tt) {
let v = std::mem::replace(value, Expr::This);
*value = Expr::Cast {
ty: t,
e: Box::new(v),
};
}
}
}
Stmt::Return(Some(value)) => {
if let Some(t) = specific_ref(&TypeRef::J(ret.clone())) {
let vty = value_ty(value);
if wants_cast(value, &vty, ret) {
let v = std::mem::replace(value, Expr::This);
*value = Expr::Cast { ty: t, e: Box::new(v) };
}
}
}
_ => {}
});
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
let (args, arg_tys): (&mut Vec<Expr>, &[JavaType]) = match x {
Expr::Method { desc, args, is_dynamic: false, .. } => {
(args, desc.args.as_slice())
}
Expr::New { arg_tys, args, .. } => (args, arg_tys.as_slice()),
_ => return,
};
for (i, a) in args.iter_mut().enumerate() {
let Some(formal) = arg_tys.get(i) else {
continue;
};
let Some(t) = specific_ref(&TypeRef::J(formal.clone())) else {
continue;
};
let aty = value_ty(a);
if !wants_cast(a, &aty, formal) {
continue;
}
let v = std::mem::replace(a, Expr::Const(ConstVal::Null));
*a = Expr::Cast { ty: t, e: Box::new(v) };
}
});
});
}
pub fn fix_primitive_assign_casts(vt: &VarTable, body: &mut Stmt, ret_ty: &JavaType) {
fn numlike(t: &JavaType) -> bool {
matches!(
t,
JavaType::Byte
| JavaType::Short
| JavaType::Int
| JavaType::Char
| JavaType::Long
| JavaType::Float
| JavaType::Double
)
}
fn val_ty(e: &Expr, vt: &VarTable) -> JavaType {
match e {
Expr::Local { var, .. } if (*var as usize) < vt.vars.len() => {
vt.var(*var).ty.erased()
}
other => other.type_ref().erased(),
}
}
fn wrap(ty: &JavaType, value: &mut Expr) {
if matches!(value, Expr::Cast { .. }) {
return;
}
if val_ty_static(value) == *ty {
return;
}
let taken = std::mem::replace(value, Expr::Const(ConstVal::Null));
*value = Expr::Cast {
ty: TypeRef::J(ty.clone()),
e: Box::new(taken),
};
}
fn val_ty_static(e: &Expr) -> JavaType {
e.type_ref().erased()
}
fn coerce(tt: &JavaType, value: &mut Expr, vt: &VarTable) {
match (&*value, tt) {
(Expr::Const(ConstVal::Int(b)), JavaType::Float) => {
*value = Expr::Const(ConstVal::Float(f32::from_bits(*b as u32)));
return;
}
(Expr::Const(ConstVal::Long(b)), JavaType::Double) => {
*value = Expr::Const(ConstVal::Double(f64::from_bits(*b as u64)));
return;
}
(Expr::Const(ConstVal::Float(f)), JavaType::Int) => {
*value = Expr::Const(ConstVal::Int(f.to_bits() as i32));
return;
}
(Expr::Const(ConstVal::Double(d)), JavaType::Long) => {
*value = Expr::Const(ConstVal::Long(d.to_bits() as i64));
return;
}
_ => {}
}
let vt_val = val_ty(value, vt);
let tt_bool = matches!(tt, JavaType::Boolean);
let val_bool = matches!(vt_val, JavaType::Boolean);
if tt_bool && !val_bool && numlike(&vt_val) {
if matches!(
value,
Expr::Const(_)
| Expr::Bin { op: BinOp::Eq | BinOp::Ne, .. }
| Expr::Un { op: UnOp::Not, .. }
) {
return;
}
let taken = std::mem::replace(value, Expr::Const(ConstVal::Null));
*value = Expr::Bin {
op: BinOp::Ne,
l: Box::new(taken),
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
};
} else if !tt_bool && val_bool && numlike(tt) {
if matches!(value, Expr::Cast { .. }) {
return;
}
let taken = std::mem::replace(value, Expr::Const(ConstVal::Null));
*value = Expr::Cast {
ty: TypeRef::J(tt.clone()),
e: Box::new(Expr::Cond {
c: Box::new(taken),
t: Box::new(Expr::Const(ConstVal::Int(1))),
f: Box::new(Expr::Const(ConstVal::Int(0))),
}),
};
} else if numlike(tt) && numlike(&vt_val) {
wrap(tt, value);
}
}
walk_mut_deep(body, &mut |st| match st {
Stmt::ExprStmt(Expr::Assign {
target,
op: AssignOp::Plain,
value,
}) => {
let tt = match &**target {
Expr::Local { var, .. } if (*var as usize) < vt.vars.len() => {
vt.var(*var).ty.erased()
}
Expr::Field { ty, .. } => ty.erased(),
Expr::ArrayIndex { array, .. } => match array.type_ref().erased() {
JavaType::Array(inner) => *inner,
_ => return,
},
_ => return,
};
coerce(&tt, value, vt);
}
Stmt::LocalDef {
var,
init: Some(value),
..
} => {
if (*var as usize) >= vt.vars.len() {
return;
}
let tt = vt.var(*var).ty.erased();
coerce(&tt, value, vt);
}
Stmt::Return(Some(e)) => {
if !numlike(ret_ty) || !numlike(&val_ty(e, vt)) {
return;
}
wrap(ret_ty, e);
}
_ => {}
});
}
pub fn fix_shadowed_super_fields(
body: &mut Stmt,
vt: &VarTable,
pool: &DexPool,
class_name: &str,
) {
fn display(pool: &DexPool, cls: &str, name: &str) -> Option<String> {
let pc = pool.get(cls)?;
let f = pc
.static_fields
.iter()
.chain(pc.instance_fields.iter())
.find(|f| f.name.as_ref() == name)?;
Some(
jdc_core::rename::field_display(cls, name, &f.desc)
.map(|d| d.to_string())
.unwrap_or_else(|| name.to_string()),
)
}
fn owner_ct(
o: &Option<Box<Expr>>,
vt: &VarTable,
class_name: &str,
) -> Option<std::sync::Arc<str>> {
match o.as_deref() {
None | Some(Expr::This) => Some(std::sync::Arc::from(class_name)),
Some(Expr::Local { var, .. }) => match vt.var(*var).ty.erased() {
JavaType::Object(n) => Some(n),
_ => None,
},
Some(other) => match other.type_ref().erased() {
JavaType::Object(n) => Some(n),
_ => None,
},
}
}
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
let Expr::Field {
owner,
cls,
name,
is_static: false,
..
} = x
else {
return;
};
let Some(ot) = owner_ct(owner, vt, class_name) else {
return;
};
if cls.as_ref() == ot.as_ref() {
return;
}
let Some(ref_display) = display(pool, cls, name) else {
return;
};
let mut cur = ot.to_string();
let mut shadowed = false;
let mut found = false;
for _ in 0..64 {
if display(pool, &cur, name).as_deref() == Some(ref_display.as_str()) {
shadowed = true;
}
let Some(pc) = pool.get(&cur) else {
return;
};
let Some(sup) = pc.super_name.clone() else {
return;
};
if sup.as_str() == cls.as_ref() {
found = true;
break;
}
cur = sup;
}
if !found || !shadowed {
return;
}
let ty = TypeRef::J(JavaType::Object(cls.clone()));
match owner {
Some(o) => {
if let Expr::Cast { ty: ct, .. } = &mut **o {
*ct = ty;
} else {
let taken = std::mem::replace(&mut **o, Expr::This);
**o = Expr::Cast {
ty,
e: Box::new(taken),
};
}
}
None => {
*owner = Some(Box::new(Expr::Cast {
ty,
e: Box::new(Expr::This),
}))
}
}
});
});
}
pub fn fix_field_owner_downcasts(body: &mut Stmt, vt: &VarTable, pool: &DexPool) {
fn owner_internal(o: &Expr, vt: &VarTable) -> Option<std::sync::Arc<str>> {
let t = match o {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
};
match t {
JavaType::Object(n) => Some(n),
_ => None,
}
}
fn declares_up(pool: &DexPool, class: &str, name: &str) -> bool {
let mut cur = Some(class.to_string());
let mut hops = 0;
while let Some(c) = cur {
hops += 1;
if hops > 64 {
return false;
}
let Some(pc) = pool.get(&c) else { return false };
if pc
.instance_fields
.iter()
.chain(pc.static_fields.iter())
.any(|f| f.name.as_ref() == name)
{
return true;
}
cur = pc.super_name.clone();
}
false
}
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Field {
owner: Some(o),
cls,
name,
is_static: false,
..
} = x
{
let Some(ot) = owner_internal(o, vt) else {
return;
};
if ot.as_ref() == cls.as_ref() || !pool.is_subtype(cls, &ot) {
return;
}
if declares_up(pool, &ot, name) || !declares_up(pool, cls, name) {
return;
}
let ty = TypeRef::J(JavaType::Object(cls.clone()));
if let Expr::Cast { ty: ct, .. } = &mut **o {
*ct = ty;
} else {
let taken = std::mem::replace(&mut **o, Expr::This);
**o = Expr::Cast { ty, e: Box::new(taken) };
}
}
let (tgt_ty, value) = match x {
Expr::Assign { target, op: AssignOp::Plain, value } => {
let t = match &**target {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
Expr::Field { ty, .. } => ty.erased(),
_ => return,
};
(t, value)
}
_ => return,
};
let JavaType::Object(sn) = tgt_ty else { return };
if let Expr::Cast { ty, .. } = &mut **value {
if let TypeRef::J(JavaType::Object(tn)) = ty {
if tn.as_ref() != sn.as_ref() && pool.is_subtype(&sn, tn) {
*ty = TypeRef::J(JavaType::Object(sn.clone()));
}
}
}
});
});
walk_mut_deep(body, &mut |st| {
if let Stmt::LocalDef { var, init: Some(value), .. } = st {
let tt = if (*var as usize) < vt.vars.len() {
vt.vars[*var as usize].ty.erased()
} else {
return;
};
let JavaType::Object(sn) = tt else { return };
if let Expr::Cast { ty, .. } = value {
if let TypeRef::J(JavaType::Object(tn)) = ty {
if tn.as_ref() != sn.as_ref() && pool.is_subtype(&sn, tn) {
*ty = TypeRef::J(JavaType::Object(sn.clone()));
}
}
}
}
});
}
pub fn fix_incomparable_equality(body: &mut Stmt, vt: &VarTable, pool: &crate::DexPool) {
fn side_ty(e: &Expr, vt: &VarTable) -> JavaType {
match e {
Expr::Local { var, .. } if (*var as usize) < vt.vars.len() => {
vt.var(*var).ty.erased()
}
other => other.type_ref().erased(),
}
}
let incomparable = |a: &JavaType, b: &JavaType| -> bool {
use JavaType::{Array, Object};
match (a, b) {
(Object(x), Object(y)) => {
if x == y || x.as_ref() == "java/lang/Object" || y.as_ref() == "java/lang/Object"
{
return false;
}
let xi = pool.get(x).map(|c| c.is_interface()).unwrap_or(true);
let yi = pool.get(y).map(|c| c.is_interface()).unwrap_or(true);
if xi || yi {
return false;
}
!pool.is_subtype(x, y) && !pool.is_subtype(y, x)
}
(Array(_), Object(y)) | (Object(y), Array(_)) => {
y.as_ref() != "java/lang/Object"
&& y.as_ref() != "java/lang/Cloneable"
&& y.as_ref() != "java/io/Serializable"
&& !pool.get(y).map(|c| c.is_interface()).unwrap_or(false)
}
_ => false,
}
};
let is_ref = |t: &JavaType| matches!(t, JavaType::Object(_) | JavaType::Array(_));
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Bin {
op: BinOp::Eq | BinOp::Ne,
l,
r,
..
} = x
{
let lt = side_ty(l, vt);
let rt = side_ty(r, vt);
if is_ref(<) && matches!(&**r, Expr::Const(ConstVal::Int(0))) {
**r = Expr::Const(ConstVal::Null);
return;
}
if is_ref(&rt) && matches!(&**l, Expr::Const(ConstVal::Int(0))) {
**l = Expr::Const(ConstVal::Null);
return;
}
if incomparable(<, &rt) {
let taken = std::mem::replace(l, Box::new(Expr::Const(ConstVal::Null)));
**l = Expr::Cast {
ty: TypeRef::J(JavaType::Object("java/lang/Object".into())),
e: taken,
};
}
}
});
});
}
pub fn fix_primitive_arg_bridges(body: &mut Stmt, vt: &VarTable, pool: &crate::DexPool) {
fn val_ty(e: &Expr, vt: &VarTable) -> JavaType {
match e {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
}
}
#[allow(clippy::type_complexity)]
let is_bool = |t: &JavaType| matches!(t, JavaType::Boolean);
let is_num = |t: &JavaType| {
matches!(
t,
JavaType::Byte | JavaType::Short | JavaType::Int | JavaType::Char
| JavaType::Long | JavaType::Float | JavaType::Double
)
};
fn bridge(actual: &JavaType, formal: &JavaType, arg: &Expr) -> Option<Expr> {
match (arg, formal) {
(Expr::Const(ConstVal::Int(b)), JavaType::Float) => {
return Some(Expr::Const(ConstVal::Float(f32::from_bits(*b as u32))));
}
(Expr::Const(ConstVal::Long(b)), JavaType::Double) => {
return Some(Expr::Const(ConstVal::Double(f64::from_bits(*b as u64))));
}
(Expr::Const(ConstVal::Float(f)), JavaType::Int) => {
return Some(Expr::Const(ConstVal::Int(f.to_bits() as i32)));
}
(Expr::Const(ConstVal::Double(d)), JavaType::Long) => {
return Some(Expr::Const(ConstVal::Long(d.to_bits() as i64)));
}
_ => {}
}
if matches!(actual, JavaType::Boolean)
&& matches!(
formal,
JavaType::Byte | JavaType::Short | JavaType::Int | JavaType::Char
| JavaType::Long | JavaType::Float | JavaType::Double
)
{
Some(Expr::Cast {
ty: TypeRef::J(formal.clone()),
e: Box::new(Expr::Cond {
c: Box::new(arg.clone()),
t: Box::new(Expr::Const(ConstVal::Int(1))),
f: Box::new(Expr::Const(ConstVal::Int(0))),
}),
})
} else if matches!(formal, JavaType::Boolean)
&& matches!(
actual,
JavaType::Byte | JavaType::Short | JavaType::Int | JavaType::Char
| JavaType::Long | JavaType::Float | JavaType::Double
)
{
Some(Expr::Bin {
op: BinOp::Ne,
l: Box::new(arg.clone()),
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
})
} else if matches!(
actual,
JavaType::Byte | JavaType::Short | JavaType::Int | JavaType::Char
| JavaType::Long | JavaType::Float | JavaType::Double
) && matches!(
formal,
JavaType::Byte | JavaType::Short | JavaType::Int | JavaType::Char
| JavaType::Long | JavaType::Float | JavaType::Double
) && !implicit_widening(actual, formal)
{
Some(Expr::Cast {
ty: TypeRef::J(formal.clone()),
e: Box::new(arg.clone()),
})
} else {
None
}
}
fn fw_resolvable(n: &str) -> bool {
crate::fwdb::exists(n)
}
fn null_arg_cast(a: &mut Expr, f: &JavaType, pool: &DexPool) -> bool {
if !matches!(&*a, Expr::Const(ConstVal::Null)) {
return false;
}
if let JavaType::Object(n) = f {
let resolvable = pool.get(n).is_some() || fw_resolvable(n);
if n.as_ref() != "java/lang/Object" && resolvable {
*a = Expr::Cast {
ty: TypeRef::J(f.clone()),
e: Box::new(Expr::Const(ConstVal::Null)),
};
return true;
}
} else if let JavaType::Array(_) = f {
let mut e: &JavaType = f;
while let JavaType::Array(inner) = e {
e = &**inner;
}
let resolvable = match e {
JavaType::Object(n) => pool.get(n).is_some() || fw_resolvable(n),
_ => true, };
if resolvable {
*a = Expr::Cast {
ty: TypeRef::J(f.clone()),
e: Box::new(Expr::Const(ConstVal::Null)),
};
return true;
}
}
false
}
fn implicit_widening(a: &JavaType, f: &JavaType) -> bool {
use JavaType::*;
match (a, f) {
(Byte, Short) | (Byte, Int) | (Byte, Long) | (Byte, Float) | (Byte, Double) => true,
(Short, Int) | (Short, Long) | (Short, Float) | (Short, Double) => true,
(Char, Int) | (Char, Long) | (Char, Float) | (Char, Double) => true,
(Int, Long) | (Int, Float) | (Int, Double) => true,
(Long, Float) | (Long, Double) => true,
(Float, Double) => true,
(x, y) => x == y,
}
}
let overload_competes = |cls: &str, name: &str, args: &[JavaType]| -> bool {
if let Some(c) = pool.get(cls) {
return c.all_methods().any(|m| {
&*m.name == name
&& m.access & (crate::access::ACC_BRIDGE | crate::access::ACC_SYNTHETIC) == 0
&& m.parsed_desc()
.is_some_and(|d| d.args.len() == args.len() && d.args != args)
});
}
let _ = args;
false
};
let ctor_formals = |cls: &str, n: usize| -> Option<Vec<JavaType>> {
let c = pool.get(cls)?;
let mut out: Option<Vec<JavaType>> = None;
for m in c.all_methods() {
if &*m.name != "<init>" {
continue;
}
let Some(d) = m.parsed_desc() else { continue };
if d.args.len() != n {
continue;
}
match &out {
None => out = Some(d.args.clone()),
Some(prev) if *prev != d.args => return None,
Some(_) => {}
}
}
out
};
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| match x {
Expr::Method {
cls,
name,
desc,
args,
is_static,
..
} => {
if args.len() != desc.args.len() {
return;
}
let _ = is_static;
let mut competes: Option<bool> = None;
for (a, f) in args.iter_mut().zip(desc.args.iter()) {
if null_arg_cast(a, f, pool) {
continue;
}
if matches!(a, Expr::Cast { .. }) {
continue;
}
let subtype_arg = match (&val_ty(a, vt), f) {
(JavaType::Object(an), JavaType::Object(fn_)) => {
(an != fn_ && pool.is_subtype(an, fn_))
|| (fn_.as_ref() == "java/lang/Object"
&& an.as_ref() != "java/lang/Object")
}
(JavaType::Array(_), JavaType::Object(fn_))
if fn_.as_ref() == "java/lang/Object" =>
{
true
}
_ => false,
};
let null_obj =
matches!(a, Expr::Const(ConstVal::Null)) && matches!(f, JavaType::Object(_));
let framework_owner = pool.get(cls.as_ref()).is_none();
let fw_null_ambiguous = framework_owner
&& matches!(
(cls.as_ref(), name.as_ref()),
("java/lang/StringBuilder", "append")
| ("java/lang/StringBuffer", "append")
| ("java/io/PrintStream", "println")
| ("java/io/PrintStream", "print")
| ("java/io/PrintWriter", "println")
| ("java/io/PrintWriter", "print")
| ("java/lang/String", "valueOf")
);
let fw_subtype_pinnable = framework_owner
&& subtype_arg
&& !matches!(f, JavaType::Object(fn_) if fn_.as_ref() == "java/lang/Object")
&& matches!(
(cls.as_ref(), name.as_ref()),
("android/content/Intent", "putExtra")
| ("java/util/concurrent/ScheduledExecutorService", "schedule")
| ("java/util/concurrent/ExecutorService", "submit")
| ("java/util/concurrent/ExecutorService", "invokeAll")
);
if fw_subtype_pinnable
|| (subtype_arg
&& *competes.get_or_insert_with(|| {
overload_competes(cls, name, &desc.args)
}))
|| (null_obj
&& (fw_null_ambiguous
|| *competes.get_or_insert_with(|| {
overload_competes(cls, name, &desc.args)
})))
{
let old =
std::mem::replace(a, Expr::Const(ConstVal::Null));
*a = Expr::Cast {
ty: TypeRef::J(f.clone()),
e: Box::new(old),
};
continue;
}
if !is_bool(&val_ty(a, vt)) && !is_num(&val_ty(a, vt)) {
continue;
}
if !is_bool(f) && !is_num(f) {
continue;
}
if let Some(b) = bridge(&val_ty(a, vt), f, a) {
*a = b;
}
}
}
Expr::New { cls, args, arg_tys, .. } => {
let formals_owned = if arg_tys.len() == args.len() && !arg_tys.is_empty() {
Some(arg_tys.clone())
} else {
ctor_formals(cls, args.len())
};
if let Some(formals) = formals_owned {
for (a, f) in args.iter_mut().zip(formals.iter()) {
if null_arg_cast(a, f, pool) {
continue;
}
if matches!(a, Expr::Cast { .. }) {
continue;
}
if !is_bool(f) && !is_num(f) {
continue;
}
if let Some(b) = bridge(&val_ty(a, vt), f, a) {
*a = b;
}
}
}
}
_ => {}
});
});
}
pub fn deshadow_locals(vt: &mut VarTable, pool: &crate::DexPool, class_name: &str, body: &Stmt) {
let segs = pool.root_pkg_segs();
let obscured = crate::classdec::obscured_simples_snapshot();
let self_pkg = class_name.rfind('/').map(|i| &class_name[..i]).unwrap_or("");
let mut same_pkg_simples: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
visit_all_exprs(body, &mut |x| {
let cls = match x {
Expr::Field { cls, is_static: true, .. } => Some(cls.as_ref()),
Expr::Method { cls, is_static: true, .. } => Some(cls.as_ref()),
_ => None,
};
if let Some(c) = cls {
let pkg = c.rfind('/').map(|i| &c[..i]).unwrap_or("");
if pkg == self_pkg {
let simple = &c[c.rfind('/').map(|i| i + 1).unwrap_or(0)..];
let head = simple.split('$').next().unwrap_or(simple);
if !head.is_empty() {
same_pkg_simples.insert(head.to_string());
}
}
}
});
let hit = |name: &str| {
segs.contains(name) || obscured.contains(name) || same_pkg_simples.contains(name)
};
if (segs.is_empty() && obscured.is_empty() && same_pkg_simples.is_empty())
|| !vt.vars.iter().any(|v| hit(&v.name))
{
return;
}
let mut taken: jdc_core::FxHashSet<String> =
vt.vars.iter().map(|v| v.name.clone()).collect();
for v in vt.vars.iter_mut() {
if !hit(&v.name) {
continue;
}
let mut k = 0u32;
let cand = loop {
k += 1;
let c = if k == 1 {
format!("{}x", v.name)
} else {
format!("{}x{}", v.name, k)
};
if !taken.contains(&c) {
break c;
}
};
taken.insert(cand.clone());
v.name = cand;
}
}
pub fn fix_ctor_this_aliases(body: &mut Stmt, vt: &VarTable) {
let Some(this_var) = vt
.vars
.iter()
.find(|v| v.is_param && v.name == "this")
.map(|v| v.id)
else {
return;
};
let mut analysis = VarAnalysis {
assigns: Vec::new(),
reads: Vec::new(),
values: Vec::new(),
};
analyze_vars(body, &mut analysis);
enum Src {
This,
Loc(u32),
Other,
}
let mut sources: jdc_core::FxHashMap<u32, Vec<Src>> = jdc_core::FxHashMap::default();
walk_all(body, &mut |st| {
let (tgt, val): (u32, &Expr) = match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &**target {
Expr::Local { var, .. } => (*var, &**value),
_ => return,
},
Stmt::LocalDef { var, init: Some(value), .. } => (*var, value),
_ => return,
};
let s = match val {
Expr::This => Src::This,
Expr::Local { var: src, .. } if *src == this_var => Src::This,
Expr::Local { var: src, .. } => Src::Loc(*src),
_ => Src::Other,
};
sources.entry(tgt).or_default().push(s);
});
let n = vt
.vars
.len()
.max(analysis.assigns.len())
.max(sources.keys().map(|k| *k as usize + 1).max().unwrap_or(0));
let mut alias = vec![false; n];
loop {
let mut changed = false;
for (v, srcs) in &sources {
let vi = *v as usize;
if vi >= n || alias[vi] || srcs.is_empty() {
continue;
}
let ok = srcs.iter().all(|s| match s {
Src::This => true,
Src::Loc(w) => (*w as usize) < n && alias[*w as usize],
Src::Other => false,
});
if ok {
alias[vi] = true;
changed = true;
}
}
if !changed {
break;
}
}
if !alias.iter().any(|&b| b) {
return;
}
walk_stmt_exprs(body, &mut |e| {
deep_rewrite_reads(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if (*var as usize) < n && alias[*var as usize] {
*x = Expr::This;
}
}
});
});
}
pub fn fix_this0_owners(body: &mut Stmt, class_name: &str) {
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Field { owner: Some(_), cls, name, .. } = x {
if name.as_ref() == "this$0" && cls.as_ref() == class_name {
if let Expr::Field { owner, .. } = x {
*owner = None;
}
}
}
});
});
}
pub fn rescue_primitive_receivers(body: &mut Stmt, vt: &VarTable, pool: &crate::DexPool) {
fn prim(t: &JavaType) -> bool {
match t {
JavaType::Int
| JavaType::Long
| JavaType::Short
| JavaType::Byte
| JavaType::Char
| JavaType::Float
| JavaType::Double
| JavaType::Boolean => true,
JavaType::Object(n) => matches!(
n.as_ref(),
"java/lang/Boolean"
| "java/lang/String"
| "java/lang/Integer"
| "java/lang/Long"
| "java/lang/Short"
| "java/lang/Byte"
| "java/lang/Character"
| "java/lang/Float"
| "java/lang/Double"
),
_ => false,
}
}
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
let (recv_var, recv_ty) = {
let o: &Expr = match &*x {
Expr::Method { owner: Some(o), .. } => o,
Expr::Field { owner: Some(o), is_static: false, .. } => o,
_ => return,
};
let Expr::Local { var, ty } = o else {
return;
};
let vt_ty = if (*var as usize) < vt.vars.len() {
vt.var(*var).ty.erased()
} else {
ty.erased()
};
if !prim(&vt_ty) {
return;
}
(*var, vt_ty)
};
let cls: std::sync::Arc<str> = match &*x {
Expr::Method { cls, .. } | Expr::Field { cls, .. } => cls.clone(),
_ => return,
};
if let JavaType::Object(n) = &recv_ty {
if n.as_ref() == cls.as_ref() || pool.is_subtype(n.as_ref(), cls.as_ref()) {
return;
}
}
let mut cands: Vec<u32> = Vec::new();
for v in &vt.vars {
if let JavaType::Object(n) = v.ty.erased() {
if n.as_ref() == cls.as_ref()
|| pool.is_subtype(n.as_ref(), cls.as_ref())
{
cands.push(v.id);
}
}
}
let id = if cands.len() == 1 {
cands[0]
} else if cands.len() > 1 {
let slot = if (recv_var as usize) < vt.vars.len() {
Some(vt.var(recv_var).slot)
} else {
None
};
let mut hits: Vec<u32> = match slot {
Some(sl) => cands
.iter()
.copied()
.filter(|&c| {
c != recv_var
&& (c as usize) < vt.vars.len()
&& vt.var(c).slot == sl
})
.collect(),
None => Vec::new(),
};
if hits.is_empty() {
hits = cands
.iter()
.copied()
.filter(|&c| c != recv_var)
.collect();
}
hits.sort_unstable();
hits.dedup();
if hits.is_empty() {
return;
}
*hits.last().unwrap()
} else {
if cands.is_empty() {
return;
}
let slot = if (recv_var as usize) < vt.vars.len() {
vt.var(recv_var).slot
} else {
return;
};
let mut hits: Vec<u32> = cands
.into_iter()
.filter(|&c| {
c != recv_var
&& (c as usize) < vt.vars.len()
&& vt.var(c).slot == slot
})
.collect();
hits.sort_unstable();
hits.dedup();
if hits.len() != 1 {
return;
}
hits[0]
};
let o: &mut Box<Expr> = match x {
Expr::Method { owner: Some(o), .. } => o,
Expr::Field { owner: Some(o), is_static: false, .. } => o,
_ => return,
};
if let Expr::Local { var, ty } = &mut **o {
if *var == recv_var {
*var = id;
*ty = vt.vars[id as usize].ty.clone();
}
}
});
});
}
pub fn rescue_arg_return_swaps(
body: &mut Stmt,
vt: &VarTable,
pool: &crate::DexPool,
ret_ty: &JavaType,
) {
let mut top_ids: jdc_core::FxHashSet<u32> = jdc_core::FxHashSet::default();
for v in &vt.vars {
if v.is_param {
top_ids.insert(v.id);
}
}
if let Stmt::Block(stmts) = body {
for st in stmts.iter() {
if let Stmt::LocalDef { var, .. } = st {
top_ids.insert(*var);
}
}
}
let ret_wants_ref = match ret_ty {
JavaType::Object(n) => n.as_ref() != "java/lang/Object",
JavaType::Array(_) => true,
_ => false,
};
let try_swap = |a: &mut Expr, want: &JavaType| {
let Expr::Local { var, ty } = &mut *a else {
return;
};
if (*var as usize) >= vt.vars.len() {
return;
}
let avt = vt.var(*var).ty.erased();
let avt_ref = matches!(avt, JavaType::Object(_) | JavaType::Array(_));
let want_arr = matches!(want, JavaType::Array(_));
if avt_ref && !(avt == JavaType::Object("java/lang/Object".into())) {
return;
}
if avt_ref && !matches!(want, JavaType::Object(_)) {
return;
}
let slot = vt.var(*var).slot;
let mut hits: Vec<u32> = Vec::new();
for v in &vt.vars {
if v.slot != slot || !top_ids.contains(&v.id) || v.id == *var {
continue;
}
let t = v.ty.erased();
let ok = match (&t, want) {
(JavaType::Object(n), JavaType::Object(w)) => {
n.as_ref() == w.as_ref()
|| pool.is_subtype(n.as_ref(), w.as_ref())
}
(JavaType::Array(_), JavaType::Array(_)) => want_arr,
_ => false,
};
if ok {
hits.push(v.id);
}
}
hits.sort_unstable();
hits.dedup();
if hits.is_empty() {
return;
}
let id = *hits.last().unwrap();
*var = id;
*ty = vt.vars[id as usize].ty.clone();
};
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
match x {
Expr::Method { desc, args, is_dynamic: false, .. } => {
for (i, a) in args.iter_mut().enumerate() {
if let Some(f) = desc.args.get(i) {
if matches!(f, JavaType::Object(n) if n.as_ref() != "java/lang/Object")
|| matches!(f, JavaType::Array(_))
{
try_swap(a, f);
}
}
}
}
Expr::New { arg_tys, args, .. } => {
for (i, a) in args.iter_mut().enumerate() {
if let Some(f) = arg_tys.get(i) {
if matches!(f, JavaType::Object(n) if n.as_ref() != "java/lang/Object")
|| matches!(f, JavaType::Array(_))
{
try_swap(a, f);
}
}
}
}
_ => {}
}
});
});
if ret_wants_ref {
walk_mut_deep(body, &mut |st| {
if let Stmt::Return(Some(e)) = st {
try_swap(e, ret_ty);
}
});
}
}
pub fn fix_ref_null_assigns(vt: &VarTable, body: &mut Stmt) {
let is_ref_var = |var: u32| -> bool {
(var as usize) < vt.vars.len()
&& matches!(
vt.vars[var as usize].ty.erased(),
JavaType::Object(_) | JavaType::Array(_)
)
};
walk_mut_deep(body, &mut |st| match st {
Stmt::ExprStmt(Expr::Assign {
target,
value,
op: AssignOp::Plain,
..
}) => {
if let Expr::Local { var, .. } = &**target {
if is_ref_var(*var) && matches!(&**value, Expr::Const(ConstVal::Int(0))) {
**value = Expr::Const(ConstVal::Null);
}
}
}
Stmt::LocalDef {
var,
init: Some(e),
..
} => {
if is_ref_var(*var) && matches!(e, Expr::Const(ConstVal::Int(0))) {
*e = Expr::Const(ConstVal::Null);
}
}
_ => {}
});
}
pub fn fix_ref_array_null_consts(body: &mut Stmt) {
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::NewArray { elem, init: Some(list), .. } = x {
if matches!(elem.erased(), JavaType::Object(_) | JavaType::Array(_)) {
for slot in list.iter_mut() {
if matches!(&*slot, Expr::Const(ConstVal::Int(0))) {
*slot = Expr::Const(ConstVal::Null);
}
}
}
}
});
});
}
pub fn fix_null_sentinels(body: &mut Stmt, vt: &VarTable, ret: &JavaType) {
#[derive(Clone, Copy, PartialEq)]
enum WK {
Zero,
Copy(u32),
Other,
}
fn is_ref_jt(t: &JavaType) -> bool {
matches!(t, JavaType::Object(_) | JavaType::Array(_))
}
fn cls_write(e: &Expr) -> WK {
match e {
Expr::Const(ConstVal::Int(0)) => WK::Zero,
Expr::Local { var, .. } => WK::Copy(*var),
_ => WK::Other,
}
}
fn expr_writes(e: &Expr, writes: &mut jdc_core::FxHashMap<u32, Vec<WK>>) {
visit_exprs(e, &mut |x| match x {
Expr::Assign { target, op, value } => {
if let Expr::Local { var, .. } = &**target {
let k = if matches!(op, AssignOp::Plain) {
cls_write(value)
} else {
WK::Other
};
writes.entry(*var).or_default().push(k);
}
}
Expr::PreIncDec { e: inner, .. } | Expr::PostIncDec { e: inner, .. } => {
if let Expr::Local { var, .. } = &**inner {
writes.entry(*var).or_default().push(WK::Other);
}
}
_ => {}
});
}
fn collect_writes(
s: &Stmt,
writes: &mut jdc_core::FxHashMap<u32, Vec<WK>>,
has_zero: &mut bool,
) {
fn note(e: &Expr, has_zero: &mut bool, writes: &mut jdc_core::FxHashMap<u32, Vec<WK>>) {
visit_exprs(e, &mut |x| {
if let Expr::Const(ConstVal::Int(0)) = x {
*has_zero = true;
}
});
expr_writes(e, writes);
}
match s {
Stmt::Block(v) => {
for x in v {
collect_writes(x, writes, has_zero);
}
}
Stmt::LocalDef { var, init, .. } => {
if let Some(e) = init {
if matches!(e, Expr::Const(ConstVal::Int(0))) {
*has_zero = true;
}
writes.entry(*var).or_default().push(cls_write(e));
}
}
Stmt::ExprStmt(e) => note(e, has_zero, writes),
Stmt::Return(e) => {
if let Some(x) = e {
note(x, has_zero, writes);
}
}
Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
note(e, has_zero, writes);
}
Stmt::TernaryValue { e } => note(e, has_zero, writes),
Stmt::If { cond, then_stmt, else_stmt } => {
note(cond, has_zero, writes);
collect_writes(then_stmt, writes, has_zero);
if let Some(b) = else_stmt {
collect_writes(b, writes, has_zero);
}
}
Stmt::While { cond, body } | Stmt::DoWhile { body, cond } => {
note(cond, has_zero, writes);
collect_writes(body, writes, has_zero);
}
Stmt::For { init, cond, update, body } => {
for x in init {
collect_writes(x, writes, has_zero);
}
if let Some(c) = cond {
note(c, has_zero, writes);
}
for u in update {
note(u, has_zero, writes);
}
collect_writes(body, writes, has_zero);
}
Stmt::ForEach { var, iterable, body, .. } => {
writes.entry(*var).or_default().push(WK::Other);
note(iterable, has_zero, writes);
collect_writes(body, writes, has_zero);
}
Stmt::Switch { selector, cases, default, .. } => {
note(selector, has_zero, writes);
for c in cases {
if let Some(g) = &c.guard {
note(g, has_zero, writes);
}
for x in &c.body {
collect_writes(x, writes, has_zero);
}
}
if let Some(d) = default {
collect_writes(d, writes, has_zero);
}
}
Stmt::Try { body, catches, finally } => {
collect_writes(body, writes, has_zero);
for c in catches {
writes.entry(c.var).or_default().push(WK::Other);
collect_writes(&c.body, writes, has_zero);
}
if let Some(f) = finally {
collect_writes(f, writes, has_zero);
}
}
Stmt::TryWithResources { resources, body, catches, finally, .. } => {
for r in resources {
collect_writes(r, writes, has_zero);
}
collect_writes(body, writes, has_zero);
for c in catches {
writes.entry(c.var).or_default().push(WK::Other);
collect_writes(&c.body, writes, has_zero);
}
if let Some(f) = finally {
collect_writes(f, writes, has_zero);
}
}
Stmt::Assert { cond, msg } => {
note(cond, has_zero, writes);
if let Some(m) = msg {
note(m, has_zero, writes);
}
}
Stmt::Synchronized { lock, body } => {
note(lock, has_zero, writes);
collect_writes(body, writes, has_zero);
}
Stmt::Labeled { body, .. } => collect_writes(body, writes, has_zero),
_ => {}
}
}
let mut writes: jdc_core::FxHashMap<u32, Vec<WK>> =
jdc_core::FxHashMap::default();
let mut has_zero = false;
collect_writes(body, &mut writes, &mut has_zero);
let mut eligible: HashSet<u32> = HashSet::default();
let int_typed = |v: u32| {
(v as usize) < vt.vars.len() && vt.vars[v as usize].ty.erased() == JavaType::Int
};
loop {
let mut changed = false;
for (&v, ws) in writes.iter() {
if eligible.contains(&v) || ws.is_empty() || !int_typed(v) {
continue;
}
if ws.iter().all(|k| match k {
WK::Zero => true,
WK::Copy(u) => *u != v && eligible.contains(u),
WK::Other => false,
}) {
eligible.insert(v);
changed = true;
}
}
if !changed {
break;
}
}
if eligible.is_empty() && !has_zero {
return; }
#[derive(Clone, Copy, PartialEq)]
enum Mode {
Scan,
Rewrite,
}
struct St<'a> {
mode: Mode,
vt: &'a VarTable,
eligible: &'a HashSet<u32>,
ref_read: HashSet<u32>,
dead: HashSet<u32>,
sentinels: HashSet<u32>,
n_lit: usize,
n_var: usize,
}
fn arg_ctx(t: &JavaType) -> Option<JavaType> {
match t {
JavaType::Object(n) if n.as_ref() == "java/lang/Object" => None,
t if is_ref_jt(t) => Some(t.clone()),
_ => None,
}
}
fn walk_expr(e: &mut Expr, ctx: Option<JavaType>, st: &mut St) {
walk_expr_c(e, ctx, false, st);
}
fn walk_expr_c(e: &mut Expr, ctx: Option<JavaType>, cast_arg: bool, st: &mut St) {
let slot_ref = ctx.as_ref().is_some_and(is_ref_jt);
fn mk_null(ctx: &Option<JavaType>, cast_arg: bool) -> Expr {
match (cast_arg, ctx) {
(true, Some(t)) => Expr::Cast {
ty: TypeRef::J(t.clone()),
e: Box::new(Expr::Const(ConstVal::Null)),
},
_ => Expr::Const(ConstVal::Null),
}
}
match e {
Expr::Const(ConstVal::Int(0)) if slot_ref => {
if st.mode == Mode::Rewrite {
*e = mk_null(&ctx, cast_arg);
st.n_lit += 1;
}
return;
}
Expr::Local { var, .. } => {
if st.eligible.contains(var) {
if slot_ref {
st.ref_read.insert(*var);
} else {
st.dead.insert(*var);
}
}
if st.mode == Mode::Rewrite && slot_ref && st.sentinels.contains(var) {
*e = mk_null(&ctx, cast_arg);
st.n_var += 1;
}
return;
}
_ => {}
}
match e {
Expr::Method { owner, desc, args, .. } => {
if let Some(o) = owner {
walk_expr(o, None, st);
}
for (i, a) in args.iter_mut().enumerate() {
let c = desc.args.get(i).and_then(arg_ctx);
walk_expr_c(a, c, true, st);
}
}
Expr::Invokedynamic { desc, args, .. } => {
for (i, a) in args.iter_mut().enumerate() {
let c = desc.args.get(i).and_then(arg_ctx);
walk_expr_c(a, c, true, st);
}
}
Expr::New { args, arg_tys, .. } => {
for (i, a) in args.iter_mut().enumerate() {
let c = arg_tys.get(i).and_then(arg_ctx);
walk_expr_c(a, c, true, st);
}
}
Expr::AnonNew { args, .. } => {
for a in args.iter_mut() {
walk_expr(a, None, st);
}
}
Expr::Assign { target, value, .. } => {
if let Expr::Local { var: u, .. } = &**target {
if st.eligible.contains(u) {
return;
}
}
let vctx = match &**target {
Expr::Field { ty, .. } => {
let t = ty.erased();
is_ref_jt(&t).then_some(t)
}
Expr::ArrayIndex { array, .. } => match array.type_ref().erased() {
JavaType::Array(inner) if is_ref_jt(&inner) => Some(*inner),
_ => None,
},
_ => None,
};
walk_expr(target, None, st);
walk_expr(value, vctx, st);
}
Expr::Cast { ty, e: inner } => {
let t = ty.erased();
let c = is_ref_jt(&t).then_some(t);
walk_expr(inner, c, st);
}
Expr::InstanceOf { e: inner, .. } => {
walk_expr(inner, Some(JavaType::Object("java/lang/Object".into())), st);
}
Expr::Cond { c, t, f } => {
walk_expr(c, None, st);
let tc = ctx.clone();
walk_expr(t, tc, st);
walk_expr(f, ctx, st);
}
Expr::NewArray { elem, dims, init, .. } => {
for d in dims.iter_mut() {
walk_expr(d, None, st);
}
if let Some(list) = init {
let et = elem.erased();
let c = is_ref_jt(&et).then_some(et);
for slot in list.iter_mut() {
walk_expr(slot, c.clone(), st);
}
}
}
Expr::NewMultiArray { dims, .. } => {
for d in dims.iter_mut() {
walk_expr(d, None, st);
}
}
Expr::ArrayIndex { array, index } => {
walk_expr(array, None, st);
walk_expr(index, None, st);
}
Expr::Field { owner, .. } => {
if let Some(o) = owner {
walk_expr(o, None, st);
}
}
Expr::Bin { l, r, .. } => {
walk_expr(l, None, st);
walk_expr(r, None, st);
}
Expr::Un { e: inner, .. } => walk_expr(inner, None, st),
Expr::PreIncDec { e: inner, .. } | Expr::PostIncDec { e: inner, .. } => {
walk_expr(inner, None, st);
}
Expr::StringConcat(parts) => {
for p in parts.iter_mut() {
if let ConcatPart::Str(x) = p {
walk_expr(x, None, st);
}
}
}
Expr::Lambda(l) => {
for c in l.captures.iter_mut() {
walk_expr(c, None, st);
}
}
_ => {}
}
}
fn walk_stmt(s: &mut Stmt, st: &mut St, ret: &JavaType) {
match s {
Stmt::Block(v) => {
for x in v.iter_mut() {
walk_stmt(x, st, ret);
}
}
Stmt::ExprStmt(e) => walk_expr(e, None, st),
Stmt::LocalDef { var, init, .. } => {
if let Some(e) = init {
let ctx = if (*var as usize) < st.vt.vars.len() {
let t = st.vt.vars[*var as usize].ty.erased();
is_ref_jt(&t).then_some(t)
} else {
None
};
walk_expr(e, ctx, st);
}
}
Stmt::Return(e) => {
if let Some(x) = e {
let c = is_ref_jt(ret).then(|| ret.clone());
walk_expr(x, c, st);
}
}
Stmt::Throw(e) => {
walk_expr(e, Some(JavaType::Object("java/lang/Throwable".into())), st);
}
Stmt::If { cond, then_stmt, else_stmt } => {
walk_expr(cond, None, st);
walk_stmt(then_stmt, st, ret);
if let Some(b) = else_stmt {
walk_stmt(b, st, ret);
}
}
Stmt::While { cond, body } => {
walk_expr(cond, None, st);
walk_stmt(body, st, ret);
}
Stmt::DoWhile { body, cond } => {
walk_stmt(body, st, ret);
walk_expr(cond, None, st);
}
Stmt::For { init, cond, update, body } => {
for x in init.iter_mut() {
walk_stmt(x, st, ret);
}
if let Some(c) = cond {
walk_expr(c, None, st);
}
for u in update.iter_mut() {
walk_expr(u, None, st);
}
walk_stmt(body, st, ret);
}
Stmt::ForEach { iterable, body, .. } => {
walk_expr(iterable, None, st);
walk_stmt(body, st, ret);
}
Stmt::Switch { selector, cases, default, .. } => {
walk_expr(selector, None, st);
for c in cases.iter_mut() {
if let Some(g) = &mut c.guard {
walk_expr(g, None, st);
}
for x in c.body.iter_mut() {
walk_stmt(x, st, ret);
}
}
if let Some(d) = default {
walk_stmt(d, st, ret);
}
}
Stmt::Try { body, catches, finally } => {
walk_stmt(body, st, ret);
for c in catches.iter_mut() {
walk_stmt(&mut c.body, st, ret);
}
if let Some(f) = finally {
walk_stmt(f, st, ret);
}
}
Stmt::TryWithResources { resources, body, catches, finally, .. } => {
for r in resources.iter_mut() {
walk_stmt(r, st, ret);
}
walk_stmt(body, st, ret);
for c in catches.iter_mut() {
walk_stmt(&mut c.body, st, ret);
}
if let Some(f) = finally {
walk_stmt(f, st, ret);
}
}
Stmt::Assert { cond, msg } => {
walk_expr(cond, None, st);
if let Some(m) = msg {
walk_expr(m, None, st);
}
}
Stmt::Synchronized { lock, body } => {
walk_expr(lock, Some(JavaType::Object("java/lang/Object".into())), st);
walk_stmt(body, st, ret);
}
Stmt::TernaryValue { e } => walk_expr(e, None, st),
Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
walk_expr(e, Some(JavaType::Object("java/lang/Object".into())), st);
}
Stmt::Labeled { body, .. } => walk_stmt(body, st, ret),
_ => {}
}
}
let mut st = St {
mode: Mode::Scan,
vt,
eligible: &eligible,
ref_read: HashSet::default(),
dead: HashSet::default(),
sentinels: HashSet::default(),
n_lit: 0,
n_var: 0,
};
if !eligible.is_empty() {
walk_stmt(body, &mut st, ret);
st.sentinels = eligible
.iter()
.copied()
.filter(|v| st.ref_read.contains(v) && !st.dead.contains(v))
.collect();
}
if st.sentinels.is_empty() && !has_zero {
return;
}
st.mode = Mode::Rewrite;
walk_stmt(body, &mut st, ret);
if std::env::var_os("DDC_SENT").is_some() {
eprintln!(
"[sent] elig={} confirmed={} lit={} var={}",
eligible.len(),
st.sentinels.len(),
st.n_lit,
st.n_var
);
}
}
pub fn fix_int_operand_bridges(body: &mut Stmt, vt: &VarTable) {
fn side_bool(e: &Expr, vt: &VarTable) -> bool {
match e {
Expr::Local { var, .. } => {
matches!(vt.var(*var).ty.erased(), JavaType::Boolean)
}
Expr::Bin {
op: BinOp::And | BinOp::Or | BinOp::Xor,
l,
r,
..
} => side_bool(l, vt) && side_bool(r, vt),
other => matches!(other.type_ref().erased(), JavaType::Boolean),
}
}
fn side_int(e: &Expr, vt: &VarTable) -> bool {
if side_bool(e, vt) {
return false;
}
matches!(
match e {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
},
JavaType::Int | JavaType::Short | JavaType::Byte | JavaType::Char | JavaType::Long
)
}
fn wrap(e: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Cond {
c: e,
t: Box::new(Expr::Const(ConstVal::Int(1))),
f: Box::new(Expr::Const(ConstVal::Int(0))),
})
}
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Assign { target, value, op: AssignOp::Plain } = x {
let tgt_ty: Option<JavaType> = match &**target {
Expr::Field { ty, .. } => Some(ty.erased()),
Expr::ArrayIndex { array, .. } => {
match array.type_ref().erased() {
JavaType::Array(el) => Some(el.as_ref().clone()),
_ => None,
}
}
Expr::Local { var, .. } => {
if (*var as usize) < vt.vars.len() {
Some(vt.var(*var).ty.erased())
} else {
None
}
}
_ => None,
};
if let Some(t) = tgt_ty {
let tgt_int = matches!(
t,
JavaType::Int
| JavaType::Short
| JavaType::Byte
| JavaType::Char
| JavaType::Long
);
if tgt_int && side_bool(value, vt) && !side_int(value, vt) {
let taken = std::mem::replace(
value,
Box::new(Expr::Const(ConstVal::Null)),
);
let mut wrapped = wrap(taken);
if !matches!(t, JavaType::Int | JavaType::Long) {
wrapped = Box::new(Expr::Cast {
ty: TypeRef::J(t.clone()),
e: wrapped,
});
}
*value = wrapped;
} else if matches!(t, JavaType::Boolean)
&& side_int(value, vt)
&& !side_bool(value, vt)
{
let taken = std::mem::replace(
value,
Box::new(Expr::Const(ConstVal::Null)),
);
**value = Expr::Bin {
op: BinOp::Ne,
l: taken,
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
};
}
}
}
if let Expr::Cast { ty, e } = x {
let t = ty.erased();
let t_num = matches!(
t,
JavaType::Int
| JavaType::Short
| JavaType::Byte
| JavaType::Char
| JavaType::Long
| JavaType::Float
| JavaType::Double
);
if t_num && side_bool(e, vt) && !side_int(e, vt) {
let taken = std::mem::replace(e, Box::new(Expr::Const(ConstVal::Null)));
**e = Expr::Cond {
c: taken,
t: Box::new(Expr::Const(ConstVal::Int(1))),
f: Box::new(Expr::Const(ConstVal::Int(0))),
};
} else if matches!(t, JavaType::Boolean)
&& side_int(e, vt)
&& !side_bool(e, vt)
{
let taken = std::mem::replace(e, Box::new(Expr::Const(ConstVal::Null)));
*x = Expr::Bin {
op: BinOp::Ne,
l: taken,
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
};
}
}
if let Expr::Bin { op, l, r, .. } = x {
if matches!(op, BinOp::Eq | BinOp::Ne) {
if matches!(&**r, Expr::Const(ConstVal::Null))
&& side_int(l, vt)
&& !side_bool(l, vt)
{
**r = Expr::Const(ConstVal::Int(0));
} else if matches!(&**l, Expr::Const(ConstVal::Null))
&& side_int(r, vt)
&& !side_bool(r, vt)
{
**l = Expr::Const(ConstVal::Int(0));
} else if matches!(&**r, Expr::Const(ConstVal::Null))
&& side_bool(l, vt)
{
let taken =
std::mem::replace(l, Box::new(Expr::Const(ConstVal::Null)));
*x = if matches!(op, BinOp::Eq) {
Expr::Un { op: UnOp::Not, e: taken }
} else {
*taken
};
} else if matches!(&**l, Expr::Const(ConstVal::Null))
&& side_bool(r, vt)
{
let taken =
std::mem::replace(r, Box::new(Expr::Const(ConstVal::Null)));
*x = if matches!(op, BinOp::Eq) {
Expr::Un { op: UnOp::Not, e: taken }
} else {
*taken
};
}
}
}
if let Expr::ArrayIndex { index, .. } = x {
if side_bool(index, vt) && !side_int(index, vt) {
let taken =
std::mem::replace(index, Box::new(Expr::Const(ConstVal::Null)));
*index = wrap(taken);
}
}
if let Expr::NewArray { dims, .. } = x {
for d in dims.iter_mut() {
if side_bool(d, vt) && !side_int(d, vt) {
let taken =
std::mem::replace(d, Expr::Const(ConstVal::Null));
*d = *wrap(Box::new(taken));
}
}
}
if let Expr::Bin { op, l, r, .. } = x {
let int_kind = matches!(
op,
BinOp::Or
| BinOp::And
| BinOp::Xor
| BinOp::Add
| BinOp::Sub
| BinOp::Mul
| BinOp::Div
| BinOp::Rem
| BinOp::Shl
| BinOp::Shr
| BinOp::Ushr
| BinOp::Eq
| BinOp::Ne
| BinOp::Lt
| BinOp::Ge
| BinOp::Gt
| BinOp::Le
);
if !int_kind {
return;
}
if matches!(
op,
BinOp::Or
| BinOp::And
| BinOp::Xor
| BinOp::Add
| BinOp::Sub
| BinOp::Mul
| BinOp::Div
| BinOp::Rem
| BinOp::Shl
| BinOp::Shr
| BinOp::Ushr
) {
if matches!(&**r, Expr::Const(ConstVal::Null))
&& (side_bool(l, vt) || side_int(l, vt))
{
**r = Expr::Const(ConstVal::Int(0));
} else if matches!(&**l, Expr::Const(ConstVal::Null))
&& (side_bool(r, vt) || side_int(r, vt))
{
**l = Expr::Const(ConstVal::Int(0));
}
}
let arith = matches!(
op,
BinOp::Add
| BinOp::Sub
| BinOp::Mul
| BinOp::Div
| BinOp::Rem
| BinOp::Shl
| BinOp::Shr
| BinOp::Ushr
);
let ordered = matches!(op, BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le);
if arith || ordered {
let side_str = |e: &Expr| {
matches!(
match e {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
},
JavaType::Object(ref s) if s.as_ref() == "java/lang/String"
)
};
let concat = arith && (side_str(l) || side_str(r));
if !concat {
if side_bool(l, vt) {
let taken =
std::mem::replace(l, Box::new(Expr::Const(ConstVal::Null)));
*l = wrap(taken);
}
if side_bool(r, vt) {
let taken =
std::mem::replace(r, Box::new(Expr::Const(ConstVal::Null)));
*r = wrap(taken);
}
}
return;
}
if side_bool(l, vt) && side_int(r, vt) && !side_bool(r, vt) {
let taken = std::mem::replace(l, Box::new(Expr::Const(ConstVal::Null)));
*l = wrap(taken);
} else if side_bool(r, vt) && side_int(l, vt) && !side_bool(l, vt) {
let taken = std::mem::replace(r, Box::new(Expr::Const(ConstVal::Null)));
*r = wrap(taken);
}
}
});
});
}
pub fn fold_value_diamonds(body: &mut Stmt, vt: &VarTable) {
let Stmt::Block(stmts) = body else { return };
fold_diamonds_in(stmts, vt);
}
fn diamond_types_ok(a: &Expr, b: &Expr) -> bool {
fn num(t: &JavaType) -> bool {
matches!(
t,
JavaType::Byte
| JavaType::Short
| JavaType::Int
| JavaType::Char
| JavaType::Long
| JavaType::Float
| JavaType::Double
)
}
if let Some(other) = if matches!(a, Expr::Const(ConstVal::Null)) {
Some(b)
} else if matches!(b, Expr::Const(ConstVal::Null)) {
Some(a)
} else {
None
} {
return !num(&other.type_ref().erased());
}
let ta = a.type_ref().erased();
let tb = b.type_ref().erased();
ta == tb || (num(&ta) && num(&tb))
}
fn single_local_assign(st: &Stmt) -> Option<(u32, Expr, Expr)> {
let mut inner = st;
while let Stmt::Block(v) = inner {
if v.len() != 1 {
return None;
}
inner = &v[0];
}
match inner {
Stmt::ExprStmt(Expr::Assign { target, value, op: AssignOp::Plain }) => {
match &**target {
Expr::Local { var, .. } => Some((*var, *target.clone(), *value.clone())),
_ => None,
}
}
_ => None,
}
}
fn arm_assign_inlined(st: &Stmt) -> Option<(u32, Expr, Expr)> {
if let Some(r) = single_local_assign(st) {
return Some(r);
}
let mut inner = st;
while let Stmt::Block(v) = inner {
if v.len() == 1 {
inner = &v[0];
} else {
break;
}
}
let Stmt::Block(v) = inner else { return None };
if v.len() != 2 {
return None;
}
let Stmt::LocalDef {
var: x,
init: Some(init),
..
} = &v[0]
else {
return None;
};
let (var, tgt, val) = single_local_assign(&v[1])?;
let mut count = 0usize;
let mut c = val.clone();
deep_rewrite(&mut c, &mut |e| {
if let Expr::Local { var: lv, .. } = e {
if lv == x {
count += 1;
}
}
});
if count != 1 {
return None;
}
let mut out = val.clone();
deep_rewrite(&mut out, &mut |e| {
if let Expr::Local { var: lv, .. } = e {
if lv == x {
*e = init.clone();
}
}
});
Some((var, tgt, out))
}
fn mk_ternary_assign(tgt: Expr, c: Expr, a: Expr, b: Expr) -> Stmt {
Stmt::ExprStmt(Expr::Assign {
target: Box::new(tgt),
op: AssignOp::Plain,
value: Box::new(Expr::Cond {
c: Box::new(c),
t: Box::new(a),
f: Box::new(b),
}),
})
}
fn same_render_var(v1: u32, v2: u32, vt: &VarTable) -> bool {
let (a, b) = (vt.vars.get(v1 as usize), vt.vars.get(v2 as usize));
match (a, b) {
(Some(x), Some(y)) => x.name == y.name && x.ty.erased() == y.ty.erased(),
_ => false,
}
}
fn canon_stmts_eq(a: &[Stmt], b: &[Stmt], vt: &VarTable) -> bool {
if a.len() != b.len() {
return false;
}
let mut map: std::collections::HashMap<String, u32> = std::collections::HashMap::default();
let canon = |s: &Stmt, map: &mut std::collections::HashMap<String, u32>| -> Stmt {
let mut c = s.clone();
walk_stmt_exprs(&mut c, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, .. } = x {
let key = match vt.vars.get(*var as usize) {
Some(vi) => format!("{}#{:?}", vi.name, vi.ty.erased()),
None => format!("v{}", var),
};
let id = *map.entry(key).or_insert(*var);
*x = Expr::Local {
var: id,
ty: vt.var(id).ty.clone(),
};
}
});
});
c
};
a.iter()
.zip(b.iter())
.all(|(x, y)| canon(x, &mut map) == canon(y, &mut map))
}
fn fold_diamonds_in(stmts: &mut Vec<Stmt>, vt: &VarTable) {
let mut i = 0usize;
while i < stmts.len() {
match &mut stmts[i] {
Stmt::Block(v) => fold_diamonds_in(v, vt),
Stmt::If { then_stmt, else_stmt, .. } => {
if let Stmt::Block(v) = &mut **then_stmt {
fold_diamonds_in(v, vt);
}
if let Some(e) = else_stmt {
if let Stmt::Block(v) = &mut **e {
fold_diamonds_in(v, vt);
}
}
}
_ => {}
}
let shape1 = if let Stmt::If { cond, then_stmt, else_stmt: Some(el), .. } = &stmts[i] {
match (arm_assign_inlined(then_stmt), arm_assign_inlined(el)) {
(Some((v1, tgt, a)), Some((v2, _, b)))
if v1 == v2 && diamond_types_ok(&a, &b) =>
{
Some(mk_ternary_assign(tgt, cond.clone(), a, b))
}
_ => None,
}
} else {
None
};
if let Some(f) = shape1 {
stmts[i] = f;
i += 1;
continue;
}
let shape3 = if let Stmt::If {
cond,
then_stmt,
else_stmt: Some(el),
..
} = &stmts[i]
{
let arm = |st: &Stmt| -> Option<((u32, Expr, Expr), Vec<Stmt>)> {
let Stmt::Block(v) = st else { return None };
if v.len() < 2 {
return None;
}
let first = single_local_assign(&v[0])?;
Some((first, v[1..].to_vec()))
};
match (arm(then_stmt), arm(el)) {
(Some(((v1, tgt1, a), mut rest_t)), Some(((v2, _t2, b), rest_e)))
if !rest_t.is_empty()
&& is_bare_ctor_call(first_leaf_stmt(&rest_t[0]))
&& stmts_count_writes(&rest_t, v1) == 0
&& stmts_count_writes(&rest_t, v2) == 0
&& diamond_types_ok(&a, &b) =>
{
if v1 == v2 || same_render_var(v1, v2, vt) {
if canon_stmts_eq(&rest_t, &rest_e, vt) {
Some((mk_ternary_assign(tgt1, cond.clone(), a, b), rest_t))
} else {
None
}
} else if stmts_read_var(&rest_t, v1) == 1
&& stmts_read_var(&rest_e, v2) == 1
&& stmts_read_var(&rest_t, v2) == 0
&& stmts_read_var(&rest_e, v1) == 0
{
let mut unified = rest_e.clone();
for st in unified.iter_mut() {
let mut c2 = st.clone();
walk_stmt_exprs(&mut c2, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == v2 {
*x = Expr::Local {
var: v1,
ty: vt.var(v1).ty.clone(),
};
}
}
});
});
*st = c2;
}
if unified != rest_t {
None
} else {
let tern = Expr::Cond {
c: Box::new(cond.clone()),
t: Box::new(a.clone()),
f: Box::new(b.clone()),
};
for st in rest_t.iter_mut() {
let mut c2 = st.clone();
walk_stmt_exprs(&mut c2, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == v1 {
*x = tern.clone();
}
}
});
});
*st = c2;
}
let first = rest_t.remove(0);
Some((first, rest_t))
}
} else {
None
}
}
_ => None,
}
} else {
None
};
if let Some((folded, rest)) = shape3 {
stmts[i] = folded;
stmts.splice(i + 1..i + 1, rest);
i += 1;
continue;
}
if i + 1 < stmts.len() {
let shape2 = {
let s_i = single_local_assign(&stmts[i]);
if let Stmt::If {
cond,
then_stmt,
else_stmt,
..
} = &stmts[i + 1]
{
let then_a = single_local_assign(then_stmt);
let else_a = else_stmt.as_deref().and_then(single_local_assign);
let then_empty =
matches!(&**then_stmt, Stmt::Block(v) if v.is_empty());
let else_empty = else_stmt
.as_deref()
.map(|e| matches!(e, Stmt::Block(v) if v.is_empty()))
.unwrap_or(true);
let try_fold = |over: Option<(u32, Expr, Expr)>,
keep_over: bool|
-> Option<Stmt> {
let (v2, tgt2, a) = over?;
let (v1, _t1, b) = s_i.as_ref()?;
if !(*v1 == v2 || same_render_var(*v1, v2, vt)) {
return None;
}
let keep = if *v1 == v2 {
b.clone()
} else {
Expr::Local {
var: *v1,
ty: vt.var(*v1).ty.clone(),
}
};
if !diamond_types_ok(&a, &keep) {
return None;
}
if keep_over {
Some(mk_ternary_assign(tgt2, cond.clone(), keep, a))
} else {
Some(mk_ternary_assign(tgt2, cond.clone(), a, keep))
}
};
if then_a.is_some() && else_empty {
try_fold(then_a, false)
} else if else_a.is_some() && then_empty {
try_fold(else_a, true)
} else {
None
}
} else {
None
}
};
let mut lead_fold: Option<(Stmt, Stmt)> = None;
if shape2.is_none() && i + 1 < stmts.len() {
let s_i2 = single_local_assign(&stmts[i]);
let leading = |st: &Stmt| -> Option<((u32, Expr, Expr), Vec<Stmt>)> {
let Stmt::Block(v) = st else { return None };
if v.len() < 2 {
return None;
}
let first = single_local_assign(&v[0])?;
Some((first, v[1..].to_vec()))
};
if let (Some((v1, _t1, b)), Stmt::If { cond, then_stmt, else_stmt, .. }) =
(&s_i2, &stmts[i + 1])
{
let else_absent = else_stmt.is_none()
|| else_stmt
.as_deref()
.is_some_and(|e| matches!(e, Stmt::Block(v) if v.is_empty()));
let then_absent = matches!(&**then_stmt, Stmt::Block(v) if v.is_empty());
let try_lead = |over: Option<((u32, Expr, Expr), Vec<Stmt>)>,
keep_over: bool|
-> Option<(Stmt, Stmt)> {
let ((v2, tgt2, a), rest) = over?;
if !(*v1 == v2 || same_render_var(*v1, v2, vt)) {
return None;
}
if stmts_count_writes(&rest, *v1) != 0
|| stmts_count_writes(&rest, v2) != 0
{
return None;
}
let keep = if *v1 == v2 {
b.clone()
} else {
Expr::Local {
var: *v1,
ty: vt.var(*v1).ty.clone(),
}
};
if !diamond_types_ok(&a, &keep) {
return None;
}
let t = if keep_over {
mk_ternary_assign(tgt2, cond.clone(), keep, a)
} else {
mk_ternary_assign(tgt2, cond.clone(), a, keep)
};
let mut new_if = stmts[i + 1].clone();
if let Stmt::If { then_stmt, else_stmt, .. } = &mut new_if {
if keep_over {
*else_stmt = Some(Box::new(Stmt::Block(rest)));
} else {
**then_stmt = Stmt::Block(rest);
}
}
Some((t, new_if))
};
if else_absent {
lead_fold = try_lead(leading(then_stmt), false);
} else if then_absent {
if let Some(e) = else_stmt {
lead_fold = try_lead(leading(e), true);
}
}
}
}
if let Some((folded, new_if)) = lead_fold {
stmts[i] = folded;
stmts[i + 1] = new_if;
continue;
}
if let Some(f) = shape2 {
stmts[i] = f;
stmts.remove(i + 1);
continue; }
}
i += 1;
}
}
pub fn fold_bool_value_diamonds(body: &mut Stmt, vt: &VarTable) {
fn is_bool_var(v: u32, vt: &VarTable) -> bool {
(v as usize) < vt.vars.len()
&& matches!(vt.vars[v as usize].ty.erased(), JavaType::Boolean)
}
fn bool_const(e: &Expr) -> Option<bool> {
fn cint(e: &Expr) -> Option<i32> {
match e {
Expr::Const(ConstVal::Int(n)) => Some(*n),
_ => None,
}
}
match e {
Expr::Const(ConstVal::Int(0)) => Some(false),
Expr::Const(ConstVal::Int(1)) => Some(true),
Expr::Bin { op: op @ (BinOp::Eq | BinOp::Ne), l, r, .. } => {
let (a, b) = (cint(l)?, cint(r)?);
let eq = a == b;
Some(if matches!(op, BinOp::Eq) { eq } else { !eq })
}
_ => None,
}
}
fn reads_var(e: &Expr, v: u32) -> bool {
let mut hit = false;
visit_exprs(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == v {
hit = true;
}
}
});
hit
}
fn bool_valued(e: &Expr, vt: &VarTable) -> bool {
fn side(e: &Expr, vt: &VarTable) -> bool {
bool_const(e).is_some() || bool_valued(e, vt)
}
match e {
Expr::Local { var, .. } => is_bool_var(*var, vt),
Expr::Method { desc, .. } => desc.ret == JavaType::Boolean,
Expr::InstanceOf { .. } => true,
Expr::Un { op: UnOp::Not, e: x, .. } => bool_valued(x, vt) || bool_const(x).is_some(),
Expr::Bin { op, l, r, .. } => match op {
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le => true,
BinOp::LogAnd | BinOp::LogOr => {
side(l, vt) && side(r, vt)
}
_ => false,
},
Expr::Cond { t, f, .. } => side(t, vt) && side(f, vt),
_ => false,
}
}
fn lone_assign(
b: &Stmt,
v: u32,
vt: &VarTable,
an: &VarAnalysis,
consumed: &mut HashSet<u32>,
) -> Option<Expr> {
let l = flat_list(b);
if l.len() == 2 {
let (u, e): (u32, &Expr) = match l[0] {
Stmt::LocalDef { var, init: Some(e), .. } => (*var, e),
Stmt::ExprStmt(Expr::Assign { target, op: AssignOp::Plain, value }) => {
match &**target {
Expr::Local { var, .. } => (*var, &**value),
_ => return None,
}
}
_ => return None,
};
if u == v || consumed.contains(&u) {
return None;
}
if an.assigns.get(u as usize).copied().unwrap_or(0) != 1
|| an.reads.get(u as usize).copied().unwrap_or(0) != 1
{
return None;
}
let (tv, rv) = match l[1] {
Stmt::ExprStmt(Expr::Assign { target, op: AssignOp::Plain, value }) => {
match (&**target, &**value) {
(Expr::Local { var: t, .. }, Expr::Local { var: r, .. }) => (*t, *r),
_ => return None,
}
}
Stmt::LocalDef { var, init: Some(Expr::Local { var: r, .. }), .. } => (*var, *r),
_ => return None,
};
if tv != v || rv != u {
return None;
}
if reads_var(e, v) || !(bool_valued(e, vt) || bool_const(e).is_some()) {
return None;
}
consumed.insert(u);
return Some(e.clone());
}
if l.len() != 1 {
return None;
}
let (target, value) = match l[0] {
Stmt::ExprStmt(Expr::Assign { target, op: AssignOp::Plain, value }) => (target, value),
Stmt::LocalDef { var, init: Some(value), .. } if *var == v => {
if reads_var(value, v) || !(bool_valued(value, vt) || bool_const(value).is_some()) {
return None;
}
return Some(value.clone());
}
_ => return None,
};
let Expr::Local { var: tv, .. } = &**target else { return None };
if *tv != v || reads_var(value, v) {
return None;
}
if !(bool_valued(value, vt) || bool_const(value).is_some()) {
return None;
}
Some((**value).clone())
}
fn not(e: Expr) -> Expr {
Expr::Un { op: UnOp::Not, e: Box::new(e) }
}
fn simplify_bool(e: Expr) -> Expr {
match e {
Expr::Un { op: UnOp::Not, e: inner, .. } => match *inner {
Expr::Bin { op: BinOp::Ne, l, r, ty } => {
simplify_bool(Expr::Bin { op: BinOp::Eq, l, r, ty })
}
Expr::Bin { op: BinOp::Eq, l, r, ty } => {
simplify_bool(Expr::Bin { op: BinOp::Ne, l, r, ty })
}
other => Expr::Un { op: UnOp::Not, e: Box::new(simplify_bool(other)) },
},
Expr::Bin { op: op @ (BinOp::Eq | BinOp::Ne), l, r, ty } => {
fn cond01(e: &Expr) -> Option<Expr> {
if let Expr::Cond { c, t, f } = e {
if matches!(&**t, Expr::Const(ConstVal::Int(1)))
&& matches!(&**f, Expr::Const(ConstVal::Int(0)))
{
return Some((**c).clone());
}
}
None
}
let (lc, rc) = (cond01(&l), cond01(&r));
match (lc, rc) {
(Some(c), None) => match (&*r, op) {
(Expr::Const(ConstVal::Int(1)), BinOp::Eq) => c,
(Expr::Const(ConstVal::Int(1)), BinOp::Ne) => not(c),
(Expr::Const(ConstVal::Int(0)), BinOp::Eq) => not(c),
(Expr::Const(ConstVal::Int(0)), BinOp::Ne) => c,
_ => Expr::Bin { op, l: Box::new(c), r, ty },
},
(None, Some(c)) => match (&*l, op) {
(Expr::Const(ConstVal::Int(1)), BinOp::Eq) => c,
(Expr::Const(ConstVal::Int(1)), BinOp::Ne) => not(c),
(Expr::Const(ConstVal::Int(0)), BinOp::Eq) => not(c),
(Expr::Const(ConstVal::Int(0)), BinOp::Ne) => c,
_ => Expr::Bin { op, l, r: Box::new(c), ty },
},
_ => Expr::Bin { op, l, r, ty },
}
}
other => other,
}
}
fn bin(op: BinOp, l: Expr, r: Expr) -> Expr {
Expr::Bin { op, l: Box::new(l), r: Box::new(r), ty: None }
}
for _round in 0..4 {
let mut changed = false;
let mut analysis = VarAnalysis {
assigns: Vec::new(),
reads: Vec::new(),
values: Vec::new(),
};
analyze_vars(body, &mut analysis);
let mut consumed: HashSet<u32> = HashSet::default();
walk_mut_deep(body, &mut |st| {
let Stmt::Block(list) = st else { return };
let mut i = 0usize;
while i + 1 < list.len() {
let folded = try_at(list, i, vt, &is_bool_var, &bool_const, &reads_var, &lone_assign, &analysis, &mut consumed);
match folded {
Some(expr) => {
let is_def = matches!(&list[i], Stmt::LocalDef { .. });
let var = match &list[i] {
Stmt::LocalDef { var, .. } => *var,
Stmt::ExprStmt(Expr::Assign { target, .. }) => {
match &**target { Expr::Local { var, .. } => *var, _ => unreachable!() }
}
_ => unreachable!(),
};
let (is_final, force_type) = match &list[i] {
Stmt::LocalDef { is_final, force_type, .. } => (*is_final, *force_type),
_ => (false, false),
};
list[i] = if is_def {
Stmt::LocalDef { var, init: Some(expr), is_final, force_type }
} else {
Stmt::ExprStmt(Expr::Assign {
target: Box::new(Expr::Local {
var,
ty: TypeRef::J(JavaType::Boolean),
}),
op: AssignOp::Plain,
value: Box::new(expr),
})
};
list.remove(i + 1);
changed = true;
}
None => i += 1,
}
}
});
if !changed {
break;
}
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn try_at(
list: &[Stmt],
i: usize,
vt: &VarTable,
is_bool_var: &dyn Fn(u32, &VarTable) -> bool,
bool_const: &dyn Fn(&Expr) -> Option<bool>,
reads_var: &dyn Fn(&Expr, u32) -> bool,
lone_assign: &dyn Fn(&Stmt, u32, &VarTable, &VarAnalysis, &mut HashSet<u32>) -> Option<Expr>,
an: &VarAnalysis,
consumed: &mut HashSet<u32>,
) -> Option<Expr> {
let (v, base) = match &list[i] {
Stmt::ExprStmt(Expr::Assign { target, op: AssignOp::Plain, value }) => {
match &**target {
Expr::Local { var, .. } => {
let b = bool_const(value)?;
if !is_bool_var(*var, vt) { return None; }
(*var, b)
}
_ => return None,
}
}
Stmt::LocalDef { var, init: Some(e), .. } => {
let b = bool_const(e)?;
if !is_bool_var(*var, vt) { return None; }
(*var, b)
}
_ => return None,
};
let Stmt::If { cond, then_stmt, else_stmt } = &list[i + 1] else {
return None;
};
if reads_var(cond, v) {
return None;
}
let mut touches_v = false;
visit_exprs(cond, &mut |x| {
if let Expr::Assign { target, .. } = x {
if let Expr::Local { var, .. } = &**target {
if *var == v { touches_v = true; }
}
}
});
if touches_v {
return None;
}
let (cond_n, then_arm, else_arm): (Expr, &Stmt, Option<&Stmt>) =
if flat_list(then_stmt).is_empty() && else_stmt.is_some() {
(not(cond.clone()), else_stmt.as_ref().unwrap(), None)
} else {
(cond.clone(), then_stmt.as_ref(), else_stmt.as_deref())
};
let e1 = lone_assign(then_arm, v, vt, an, consumed)?;
let e2 = match else_arm {
None => None,
Some(b) => Some(lone_assign(b, v, vt, an, consumed)?),
};
let e1c = bool_const(&e1);
let c = simplify_bool(cond_n);
Some(match e2 {
None => {
if !base {
match e1c {
Some(true) => c,
Some(false) => return None, None => bin(BinOp::LogAnd, c, e1),
}
} else {
match e1c {
Some(false) => not(c),
Some(true) => return None,
None => bin(BinOp::LogOr, not(c), e1),
}
}
}
Some(e2) => {
if e1 == e2 {
return None; }
Expr::Cond { c: Box::new(c), t: Box::new(e1), f: Box::new(e2) }
}
})
}
}
pub fn idiom_compounds(body: &mut Stmt, vt: &VarTable) {
walk_mut_deep(body, &mut |st| {
let Stmt::ExprStmt(e) = st else { return };
let Expr::Assign { target, op: AssignOp::Plain, value } = &mut *e else {
return;
};
enum Tgt {
Loc(u32),
Fld,
}
let tgt = match &**target {
Expr::Local { var, .. } => Tgt::Loc(*var),
Expr::Field { owner, .. }
if owner.is_none()
|| matches!(&**owner.as_ref().unwrap(), Expr::This) =>
{
Tgt::Fld
}
_ => return,
};
let Expr::Bin { op, l, r, .. } = &mut **value else {
return;
};
match (&tgt, &**l) {
(Tgt::Loc(v), Expr::Local { var, .. }) if *var == *v => {}
(Tgt::Fld, Expr::Field { cls: c2, name: n2, .. }) => {
let Expr::Field { cls: c1, name: n1, .. } = &**target else {
return;
};
if c1 != c2 || n1 != n2 {
return;
}
}
_ => return,
}
let one = match &**r {
Expr::Const(ConstVal::Int(1)) => Some(1i64),
Expr::Const(ConstVal::Int(-1)) => Some(-1),
Expr::Const(ConstVal::Long(1)) => Some(1),
Expr::Const(ConstVal::Long(-1)) => Some(-1),
_ => None,
};
if *op == BinOp::Add || *op == BinOp::Sub {
if let Some(mut d) = one {
if *op == BinOp::Sub {
d = -d;
}
let tgt_ty = match &tgt {
Tgt::Loc(v) => vt.var(*v).ty.erased(),
Tgt::Fld => {
let Expr::Field { ty, .. } = &**target else {
return;
};
ty.erased()
}
};
let numeric = matches!(
tgt_ty,
JavaType::Int
| JavaType::Long
| JavaType::Short
| JavaType::Byte
| JavaType::Char
| JavaType::Float
| JavaType::Double
);
if numeric {
let wide = matches!(tgt_ty, JavaType::Long);
let inner = match &tgt {
Tgt::Loc(v) => Expr::Local {
var: *v,
ty: vt.var(*v).ty.clone(),
},
Tgt::Fld => (**target).clone(),
};
*e = Expr::PostIncDec {
e: Box::new(inner),
delta: d,
wide,
};
return;
}
}
}
let cop = match op {
BinOp::Add => AssignOp::Add,
BinOp::Sub => AssignOp::Sub,
BinOp::Mul => AssignOp::Mul,
BinOp::Div => AssignOp::Div,
BinOp::Rem => AssignOp::Rem,
BinOp::Shl => AssignOp::Shl,
BinOp::Shr => AssignOp::Shr,
BinOp::Ushr => AssignOp::Ushr,
BinOp::And => AssignOp::And,
BinOp::Or => AssignOp::Or,
BinOp::Xor => AssignOp::Xor,
_ => return,
};
let rhs = std::mem::replace(r, Box::new(Expr::Const(ConstVal::Null)));
let new_target = match &tgt {
Tgt::Loc(v) => Box::new(Expr::Local {
var: *v,
ty: vt.var(*v).ty.clone(),
}),
Tgt::Fld => target.clone(),
};
*e = Expr::Assign {
target: new_target,
op: cop,
value: rhs,
};
});
}
pub fn fix_bool_xor(body: &mut Stmt, vt: &VarTable, ret_bool: bool) {
fn is_one(e: &Expr) -> bool {
matches!(e, Expr::Const(ConstVal::Int(1)))
}
fn bty(e: &Expr, vt: &VarTable) -> bool {
match e {
Expr::Local { var, .. } => {
matches!(vt.var(*var).ty.erased(), JavaType::Boolean)
}
Expr::Bin {
op: BinOp::And | BinOp::Or | BinOp::Xor,
l,
r,
..
} => bty(l, vt) && bty(r, vt),
other => matches!(other.type_ref().erased(), JavaType::Boolean),
}
}
fn inv_of_xor(x: &Expr, vt: &VarTable) -> Option<Expr> {
if let Expr::Bin { op: BinOp::Xor, l, r, .. } = x {
let (int_side, one_side) = if is_one(r) {
(l, r)
} else if is_one(l) {
(r, l)
} else {
return None;
};
let _ = one_side;
if !bty(int_side, vt) {
return Some(Expr::Bin {
op: BinOp::Eq,
l: int_side.clone(),
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
});
}
}
None
}
let tgt_bool = |target: &Expr, vt: &VarTable| -> bool {
match target {
Expr::Local { var, .. } => matches!(vt.var(*var).ty.erased(), JavaType::Boolean),
Expr::Field { ty, .. } => matches!(ty.erased(), JavaType::Boolean),
_ => false,
}
};
walk_mut_deep(body, &mut |st| match st {
Stmt::Return(Some(e)) if ret_bool => {
if let Some(r) = inv_of_xor(e, vt) {
*e = r;
}
}
Stmt::ExprStmt(Expr::Assign { target, value, op: AssignOp::Plain, .. }) => {
if tgt_bool(target, vt) {
if let Some(r) = inv_of_xor(value, vt) {
**value = r;
}
}
}
Stmt::LocalDef { var, init: Some(value), .. }
if matches!(vt.var(*var).ty.erased(), JavaType::Boolean) =>
{
if let Some(r) = inv_of_xor(value, vt) {
*value = r;
}
}
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
if let Some(r) = inv_of_xor(cond, vt) {
*cond = r;
}
}
_ => {}
});
fn ity(e: &Expr, vt: &VarTable) -> bool {
matches!(
match e {
Expr::Local { var, .. } => vt.var(*var).ty.erased(),
other => other.type_ref().erased(),
},
JavaType::Int | JavaType::Short | JavaType::Byte | JavaType::Char
)
}
fn mixed_rewrite(e: &mut Expr, vt: &VarTable) {
deep_rewrite(e, &mut |x| {
if let Expr::Bin { op, l, r, ty } = x {
match op {
BinOp::Xor => {
if is_one(r) && bty(l, vt) {
let taken =
std::mem::replace(l, Box::new(Expr::Const(ConstVal::Null)));
*x = Expr::Un { op: UnOp::Not, e: taken };
} else if is_one(l) && bty(r, vt) {
let taken =
std::mem::replace(r, Box::new(Expr::Const(ConstVal::Null)));
*x = Expr::Un { op: UnOp::Not, e: taken };
}
}
BinOp::Or | BinOp::And => {
if bty(l, vt) && ity(r, vt) {
let taken =
std::mem::replace(r, Box::new(Expr::Const(ConstVal::Null)));
**r = Expr::Bin {
op: BinOp::Ne,
l: taken,
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
};
*ty = Some(TypeRef::J(JavaType::Boolean));
} else if ity(l, vt) && bty(r, vt) {
let taken =
std::mem::replace(l, Box::new(Expr::Const(ConstVal::Null)));
**l = Expr::Bin {
op: BinOp::Ne,
l: taken,
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
};
*ty = Some(TypeRef::J(JavaType::Boolean));
}
}
_ => {}
}
}
});
}
walk_mut_deep(body, &mut |st| match st {
Stmt::Return(Some(e)) if ret_bool => mixed_rewrite(e, vt),
Stmt::ExprStmt(Expr::Assign { target, value, op: AssignOp::Plain, .. }) => {
if tgt_bool(target, vt) {
mixed_rewrite(value, vt);
}
}
Stmt::LocalDef { var, init: Some(e), .. }
if matches!(vt.var(*var).ty.erased(), JavaType::Boolean) =>
{
mixed_rewrite(e, vt);
}
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
mixed_rewrite(cond, vt);
}
_ => {}
});
walk_stmt_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Cond { c, .. } = x {
mixed_rewrite(c, vt);
}
});
});
}
pub fn booleanize(vt: &mut VarTable, body: &mut Stmt, ret_bool: bool) -> usize {
let n0 = vt.vars.len();
let mut derefed = vec![false; n0];
mark_derefs(body, &mut derefed);
let mut total = 0usize;
for _ in 0..4 {
let n = booleanize_round(vt, body, ret_bool, &derefed);
if n == 0 {
break;
}
total += n;
}
total
}
fn mark_deref_expr(e: &Expr, derefed: &mut [bool]) {
match e {
Expr::Field { owner: Some(o), is_static: false, .. }
| Expr::Method { owner: Some(o), .. } => {
if let Expr::Local { var, .. } = &**o {
if (*var as usize) < derefed.len() {
derefed[*var as usize] = true;
}
}
}
Expr::ArrayIndex { array, .. } => {
if let Expr::Local { var, .. } = &**array {
if (*var as usize) < derefed.len() {
derefed[*var as usize] = true;
}
}
}
_ => {}
}
for_each_child(e, &mut |c| mark_deref_expr(c, derefed));
}
fn mark_derefs(body: &Stmt, derefed: &mut [bool]) {
walk_all(body, &mut |st| {
match st {
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
mark_deref_expr(e, derefed);
}
Stmt::Return(Some(e)) => mark_deref_expr(e, derefed),
Stmt::LocalDef { init: Some(e), .. } => mark_deref_expr(e, derefed),
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
mark_deref_expr(cond, derefed);
}
Stmt::For { init, .. } => {
for x in init {
mark_derefs(x, derefed);
}
}
Stmt::Switch { selector, .. } => mark_deref_expr(selector, derefed),
Stmt::ForEach { iterable, .. } => mark_deref_expr(iterable, derefed),
_ => {}
}
});
}
fn booleanize_round(
vt: &mut VarTable,
body: &mut Stmt,
ret_bool: bool,
derefed: &[bool],
) -> usize {
let n = vt.vars.len();
let mut in_cond = vec![false; n];
walk_all(body, &mut |st| {
let cond: Option<&Expr> = match st {
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
Some(cond)
}
_ => None,
};
if let Some(c) = cond {
if let Expr::Bin {
op: BinOp::Eq | BinOp::Ne,
l,
r,
..
} = c
{
for (x, other) in [(l, r), (r, l)] {
if let (Expr::Local { var, .. }, Expr::Const(ConstVal::Int(0))) =
(&**x, &**other)
{
if (*var as usize) < n {
in_cond[*var as usize] = true;
}
}
}
}
}
if ret_bool {
if let Stmt::Return(Some(Expr::Local { var, .. })) = st {
if (*var as usize) < n {
in_cond[*var as usize] = true;
}
}
}
if let Stmt::ExprStmt(Expr::Assign { target, value, .. }) = st {
if let Expr::Field { ty, .. } = &**target {
if ty.erased() == JavaType::Boolean {
if let Expr::Local { var, .. } = &**value {
if (*var as usize) < n {
in_cond[*var as usize] = true;
}
}
}
}
}
let val: Option<&Expr> = match st {
Stmt::ExprStmt(Expr::Assign { value, .. }) => Some(value),
Stmt::ExprStmt(e) => Some(e),
Stmt::LocalDef { init: Some(e), .. } => Some(e),
Stmt::Return(Some(e)) => Some(e),
_ => None,
};
if let Some(v) = val {
visit_exprs(v, &mut |x| {
if let Expr::Method { desc, args, .. } = x {
for (i, a) in args.iter().enumerate() {
if desc.args.get(i) == Some(&JavaType::Boolean) {
if let Expr::Local { var, .. } = a {
if (*var as usize) < n {
in_cond[*var as usize] = true;
}
}
}
}
}
if let Expr::Bin { op, l, r, .. } = x {
if matches!(
op,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
) {
for side in [l, r] {
if let Expr::Local { var, .. } = &**side {
if (*var as usize) < n {
in_cond[*var as usize] = true;
}
}
}
}
if matches!(op, BinOp::Or | BinOp::And) {
let l_bool = l.type_ref().erased() == JavaType::Boolean;
let r_bool = r.type_ref().erased() == JavaType::Boolean;
if l_bool != r_bool {
let side = if l_bool { r } else { l };
if let Expr::Local { var, .. } = &**side {
if (*var as usize) < n {
in_cond[*var as usize] = true;
}
}
}
}
}
});
}
});
let mut assigned_any = vec![false; n];
let mut all_bool = vec![true; n];
let mut edges: Vec<(usize, usize)> = Vec::new();
walk_all(body, &mut |st| {
let (var, value) = match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &**target {
Expr::Local { var, .. } => (var, &**value),
_ => return,
},
Stmt::LocalDef {
var, init: Some(e), ..
} => (var, e),
_ => return,
};
let i = *var as usize;
if i < n {
assigned_any[i] = true;
if !is_boolean_valued(value, vt) {
all_bool[i] = false;
}
if let Expr::Local { var: src, .. } = value {
if (*src as usize) < n {
edges.push((i, *src as usize));
}
}
}
});
loop {
let mut changed = false;
for &(tgt, src) in &edges {
if in_cond[tgt] && !in_cond[src] && all_bool[src] {
in_cond[src] = true;
changed = true;
}
if in_cond[src] && !in_cond[tgt] && all_bool[tgt] {
in_cond[tgt] = true;
changed = true;
}
}
if !changed {
break;
}
}
let reads = count_locals_stmts(std::slice::from_ref(body));
let mut boolean_vars: HashSet<u32> = HashSet::default();
for i in 0..n {
if vt.vars[i].is_param {
continue;
}
if derefed[i] {
continue;
}
if !in_cond[i] && !(reads.get(&(i as u32)).copied().unwrap_or(0) == 0) {
continue;
}
if assigned_any[i] && all_bool[i] {
boolean_vars.insert(i as u32);
}
}
if boolean_vars.is_empty() {
return 0;
}
for v in &boolean_vars {
vt.vars[*v as usize].ty = TypeRef::J(JavaType::Boolean);
}
let types: Vec<&TypeRef> = vt.vars.iter().map(|v| &v.ty).collect();
rewrite_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, ty } = x {
if let Some(want) = types.get(*var as usize) {
if ty != *want {
*ty = (*want).clone();
}
}
}
});
});
fold_bool_conditions(body, &boolean_vars);
boolean_vars.len()
}
fn is_boolean_valued(e: &Expr, vt: &VarTable) -> bool {
match e {
Expr::Const(ConstVal::Int(0)) | Expr::Const(ConstVal::Int(1)) => true,
Expr::Bin { op: BinOp::Or | BinOp::And, l, r, .. } => {
is_boolean_valued(l, vt) || is_boolean_valued(r, vt)
}
Expr::Bin {
op: BinOp::Xor,
l,
r,
..
} => is_boolean_valued(l, vt) && is_boolean_valued(r, vt),
Expr::Bin { op, .. } => matches!(
op,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
),
Expr::InstanceOf { .. } => true,
Expr::Local { var, ty } => {
let t = if (*var as usize) < vt.vars.len() {
vt.var(*var).ty.erased()
} else {
ty.erased()
};
t == JavaType::Boolean
}
Expr::Method { desc, .. } => desc.ret == JavaType::Boolean,
Expr::Cond { t, f, .. } => is_boolean_valued(t, vt) && is_boolean_valued(f, vt),
Expr::Un { op: UnOp::Not, .. } => true,
Expr::Field { ty, .. } => ty.erased() == JavaType::Boolean,
_ => false,
}
}
fn fold_bool_conditions(s: &mut Stmt, bv: &HashSet<u32>) {
let fold = |e: &mut Expr| {
deep_rewrite(e, &mut |x| {
if let Expr::Bin { op, l, r, .. } = x {
let (var_side, const_side) = match (&**l, &**r) {
(Expr::Local { .. }, Expr::Const(ConstVal::Int(_))) => (l, 0),
(Expr::Const(ConstVal::Int(_)), Expr::Local { .. }) => (r, 1),
_ => return,
};
let var = match &**var_side {
Expr::Local { var, .. } => *var,
_ => return,
};
if !bv.contains(&var) {
return;
}
let _ = const_side;
match op {
BinOp::Ne => {
let inner = var_side.clone();
*x = *inner;
}
BinOp::Eq => {
let inner = var_side.clone();
*x = Expr::Un {
op: UnOp::Not,
e: Box::new(*inner),
};
}
_ => {}
}
}
});
};
match s {
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => fold(cond),
_ => {}
}
walk_mut_deep(s, &mut |st| {
let cond = match st {
Stmt::If { cond, .. } => Some(cond),
Stmt::While { cond, .. } => Some(cond),
Stmt::DoWhile { cond, .. } => Some(cond),
_ => None,
};
if let Some(c) = cond {
fold(c);
}
});
}
pub fn flip_const_compares(s: &mut Stmt) {
rewrite_exprs(s, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Bin { op, l, r, .. } = x {
if matches!(
op,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
) {
let l_const = matches!(&**l, Expr::Const(_));
let r_var = matches!(&**r, Expr::Local { .. });
if l_const && r_var {
std::mem::swap(l, r);
}
}
}
});
});
}
pub fn null_compares(vt: &VarTable, body: &mut Stmt) {
let obj_vars: HashSet<u32> = vt
.vars
.iter()
.filter(|v| v.ty.erased().is_reference())
.map(|v| v.id)
.collect();
rewrite_exprs(body, &mut |e| {
deep_rewrite(e, &mut |x| {
null_side_rewrite(x, &|v| obj_vars.contains(&v));
});
});
}
pub fn drop_dead_locals(body: &mut Stmt) {
loop {
let mut reads: Vec<usize> = Vec::new();
count_reads(body, &mut reads);
let mut dropped = 0usize;
walk_mut_deep(body, &mut |st| match st {
Stmt::Block(items) => prune_dead_items(items, &reads, &mut dropped),
Stmt::Switch { cases, .. } => {
for c in cases {
prune_dead_items(&mut c.body, &reads, &mut dropped);
}
}
Stmt::For { init, .. } => prune_dead_items(init, &reads, &mut dropped),
_ => {}
});
if dropped == 0 {
break;
}
}
}
fn prune_dead_items(items: &mut Vec<Stmt>, reads: &[usize], dropped: &mut usize) {
let dead = |v: u32| reads.get(v as usize).copied().unwrap_or(0) == 0;
items.retain_mut(|x| match x {
Stmt::ExprStmt(Expr::Assign { target, value, op, .. }) => {
let dead_target = matches!(&**target, Expr::Local { var, .. } if dead(*var));
if dead_target && matches!(*op, AssignOp::Plain) {
*dropped += 1;
if has_side_effects(value) {
let e = std::mem::replace(value, Box::new(Expr::This));
*x = Stmt::ExprStmt(*e);
true
} else {
false
}
} else {
true
}
}
Stmt::LocalDef { var, init, .. } if dead(*var) => {
*dropped += 1;
if let Some(e) = init.take() {
if has_side_effects(&e) {
*x = Stmt::ExprStmt(e);
return true;
}
}
false
}
_ => true,
});
}
fn stmt_shallow_vars<F: FnMut(u32)>(st: &Stmt, out: &mut F) {
fn ex<F: FnMut(u32)>(e: &Expr, out: &mut F) {
visit_exprs(e, &mut |x| {
if let Expr::Local { var, .. } = x {
out(*var);
}
});
}
match st {
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
ex(e, out)
}
Stmt::TernaryValue { e } => ex(e, out),
Stmt::Return(Some(e)) => ex(e, out),
Stmt::LocalDef { var, init, .. } => {
out(*var);
if let Some(e) = init {
ex(e, out);
}
}
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
ex(cond, out)
}
Stmt::For { cond, update, .. } => {
if let Some(c) = cond {
ex(c, out);
}
for u in update {
ex(u, out);
}
}
Stmt::ForEach { iterable, .. } => ex(iterable, out),
Stmt::Switch { selector, cases, .. } => {
ex(selector, out);
for cg in cases {
if let Some(g) = &cg.guard {
ex(g, out);
}
}
}
Stmt::Assert { cond, msg } => {
ex(cond, out);
if let Some(m) = msg {
ex(m, out);
}
}
Stmt::Synchronized { lock, .. } => ex(lock, out),
_ => {}
}
}
fn scope_pass_a(st: &Stmt, cur: u32, next: &mut u32, def_block: &mut [u32], sizes: &mut Vec<u32>) {
if let Stmt::LocalDef { var, .. } = st {
let i = *var as usize;
if i < def_block.len() && def_block[i] == u32::MAX {
def_block[i] = cur;
}
}
fn scoped(c: &Stmt, next: &mut u32, def_block: &mut [u32], sizes: &mut Vec<u32>) {
let id = *next;
*next += 1;
sizes.push(0);
scope_pass_a(c, id, next, def_block, sizes);
sizes[id as usize] = *next - id;
}
match st {
Stmt::Block(v) => {
for x in v {
scope_pass_a(x, cur, next, def_block, sizes);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
scoped(then_stmt, next, def_block, sizes);
if let Some(e) = else_stmt {
scoped(e, next, def_block, sizes);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => {
scoped(body, next, def_block, sizes)
}
Stmt::For { init, body, .. } => {
let id = *next;
*next += 1;
sizes.push(0);
for x in init {
scope_pass_a(x, id, next, def_block, sizes);
}
scoped(body, next, def_block, sizes);
sizes[id as usize] = *next - id;
}
Stmt::ForEach { body, .. } => scoped(body, next, def_block, sizes),
Stmt::Switch { cases, default, .. } => {
for c in cases {
let id = *next;
*next += 1;
sizes.push(0);
for x in &c.body {
if matches!(x, Stmt::Block(_)) {
scoped(x, next, def_block, sizes);
} else {
scope_pass_a(x, id, next, def_block, sizes);
}
}
sizes[id as usize] = *next - id;
}
if let Some(d) = default {
scoped(d, next, def_block, sizes);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
scoped(body, next, def_block, sizes);
for c in catches {
scoped(&c.body, next, def_block, sizes);
}
if let Some(f) = finally {
scoped(f, next, def_block, sizes);
}
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
} => {
let id = *next;
*next += 1;
sizes.push(0);
for r in resources {
scope_pass_a(r, id, next, def_block, sizes);
}
scoped(body, next, def_block, sizes);
for c in catches {
scoped(&c.body, next, def_block, sizes);
}
if let Some(f) = finally {
scoped(f, next, def_block, sizes);
}
sizes[id as usize] = *next - id;
}
Stmt::Synchronized { body, .. } => scoped(body, next, def_block, sizes),
Stmt::Labeled { body, .. } => scope_pass_a(body, cur, next, def_block, sizes),
_ => {}
}
}
fn scope_pass_b(
st: &Stmt,
cur: u32,
next: &mut u32,
def_block: &[u32],
sizes: &[u32],
within: &mut [u32],
total: &mut [u32],
) {
fn cnt(
v: u32,
at: u32,
def_block: &[u32],
sizes: &[u32],
within: &mut [u32],
total: &mut [u32],
) {
let i = v as usize;
if i < total.len() {
total[i] += 1;
let d = def_block[i];
if d != u32::MAX && d <= at && at < d + sizes[d as usize] {
within[i] += 1;
}
}
}
fn scoped(
c: &Stmt,
next: &mut u32,
def_block: &[u32],
sizes: &[u32],
within: &mut [u32],
total: &mut [u32],
) {
let id = *next;
*next += 1;
scope_pass_b(c, id, next, def_block, sizes, within, total);
}
fn ex_at(
e: &Expr,
at: u32,
def_block: &[u32],
sizes: &[u32],
within: &mut [u32],
total: &mut [u32],
) {
visit_exprs(e, &mut |x| {
if let Expr::Local { var, .. } = x {
cnt(*var, at, def_block, sizes, within, total);
}
});
}
match st {
Stmt::For {
init,
cond,
update,
body,
} => {
let id = *next;
*next += 1;
for x in init {
scope_pass_b(x, id, next, def_block, sizes, within, total);
}
if let Some(c) = cond {
ex_at(c, id, def_block, sizes, within, total);
}
for u in update {
ex_at(u, id, def_block, sizes, within, total);
}
scoped(body, next, def_block, sizes, within, total);
}
Stmt::TryWithResources {
resources,
body,
catches,
finally,
} => {
let id = *next;
*next += 1;
for r in resources {
scope_pass_b(r, id, next, def_block, sizes, within, total);
}
scoped(body, next, def_block, sizes, within, total);
for c in catches {
scoped(&c.body, next, def_block, sizes, within, total);
}
if let Some(f) = finally {
scoped(f, next, def_block, sizes, within, total);
}
}
_ => {
stmt_shallow_vars(st, &mut |v| cnt(v, cur, def_block, sizes, within, total));
match st {
Stmt::Block(v) => {
for x in v {
scope_pass_b(x, cur, next, def_block, sizes, within, total);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
scoped(then_stmt, next, def_block, sizes, within, total);
if let Some(e) = else_stmt {
scoped(e, next, def_block, sizes, within, total);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => {
scoped(body, next, def_block, sizes, within, total)
}
Stmt::ForEach { body, .. } => {
scoped(body, next, def_block, sizes, within, total)
}
Stmt::Switch { cases, default, .. } => {
for c in cases {
let id = *next;
*next += 1;
for x in &c.body {
if matches!(x, Stmt::Block(_)) {
scoped(x, next, def_block, sizes, within, total);
} else {
scope_pass_b(x, id, next, def_block, sizes, within, total);
}
}
}
if let Some(d) = default {
scoped(d, next, def_block, sizes, within, total);
}
}
Stmt::Try {
body,
catches,
finally,
} => {
scoped(body, next, def_block, sizes, within, total);
for c in catches {
scoped(&c.body, next, def_block, sizes, within, total);
}
if let Some(f) = finally {
scoped(f, next, def_block, sizes, within, total);
}
}
Stmt::Synchronized { body, .. } => {
scoped(body, next, def_block, sizes, within, total)
}
Stmt::Labeled { body, .. } => {
scope_pass_b(body, cur, next, def_block, sizes, within, total)
}
_ => {}
}
}
}
}
pub fn strip_enum_const_inits(s: &mut Stmt, class: &crate::PoolClass) {
let const_fields: jdc_core::FxHashSet<&str> = class
.static_fields
.iter()
.filter(|f| f.access & crate::access::ACC_ENUM != 0)
.map(|f| f.name.as_ref())
.collect();
if const_fields.is_empty() {
return;
}
if let Stmt::Block(stmts) = s {
stmts.retain(|st| {
!matches!(
st,
Stmt::ExprStmt(Expr::Assign { target, value, .. })
if matches!(&**target,
Expr::Field { cls, name, is_static: true, .. }
if cls.as_ref() == class.name
&& const_fields.contains(&**name))
&& matches!(&**value, Expr::New { cls: ncls, .. } if ncls.as_ref() == class.name)
)
});
}
}
fn contains_this_access(e: &Expr) -> bool {
let mut hit = false;
visit_exprs(e, &mut |x| {
if hit {
return;
}
match x {
Expr::This => hit = true,
Expr::Field { owner, is_static: false, .. } => {
if owner.is_none() || matches!(&**owner.as_ref().unwrap(), Expr::This) {
hit = true;
}
}
Expr::Method { owner, is_static: false, .. } => {
if owner.is_none() || matches!(&**owner.as_ref().unwrap(), Expr::This) {
hit = true;
}
}
_ => {}
}
});
hit
}
fn var_occurs_in(stmts: &[Stmt], var: u32) -> bool {
stmts.iter().any(|st| {
let mut hit = false;
visit_stmt_exprs_ro(st, &mut |e| {
if !hit {
visit_exprs(e, &mut |x| {
if let Expr::Local { var: v, .. } = x {
if *v == var {
hit = true;
}
}
});
}
});
hit
})
}
pub struct CtorHelper {
pub name: String,
pub ret: JavaType,
pub params: Vec<(JavaType, String)>,
pub body: Stmt,
pub vt: VarTable,
}
fn ctor_helpers() -> &'static std::sync::Mutex<std::collections::HashMap<String, Vec<CtorHelper>>> {
static H: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<String, Vec<CtorHelper>>>> =
std::sync::OnceLock::new();
H.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::default()))
}
pub struct CtorRelay {
pub formals: Vec<JavaType>,
pub is_super: bool,
}
type RelayMap = std::collections::HashMap<String, Vec<(String, CtorRelay)>>;
fn ctor_relays() -> &'static std::sync::Mutex<RelayMap> {
static H: std::sync::OnceLock<std::sync::Mutex<RelayMap>> = std::sync::OnceLock::new();
H.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::default()))
}
pub fn take_ctor_relays(class: &str) -> Vec<(String, CtorRelay)> {
let mut m = ctor_relays().lock().unwrap_or_else(|e| e.into_inner());
let mut v = m.remove(class).unwrap_or_default();
v.sort_by(|a, b| a.0.cmp(&b.0));
v
}
fn array_init(args: Vec<Expr>) -> Expr {
Expr::NewArray {
elem: TypeRef::J(JavaType::Object(std::sync::Arc::from("java/lang/Object"))),
dims: Vec::new(),
trailing_dims: 0,
init: Some(args),
}
}
fn helper_name(class: &str, desc: &str) -> String {
let mut h: u64 = 0xcbf29ce484222325;
for b in class.as_bytes().iter().chain(b"::").chain(desc.as_bytes()) {
h ^= *b as u64;
h = h.wrapping_mul(0x100000001b3);
}
format!("resolve${:x}", (h >> 16) & 0xffff_ffff)
}
pub fn take_ctor_helpers(class: &str) -> Vec<CtorHelper> {
let mut m = ctor_helpers().lock().unwrap_or_else(|e| e.into_inner());
let mut v = m.remove(class).unwrap_or_default();
v.sort_by(|a, b| a.name.cmp(&b.name));
v
}
fn strip_delegations_deep(stmts: &mut Vec<Stmt>) {
stmts.retain(|s| !is_bare_ctor_call(s));
for s in stmts.iter_mut() {
strip_delegation_one(s);
}
}
fn strip_delegation_one(st: &mut Stmt) {
if is_bare_ctor_call(st) {
*st = Stmt::Block(Vec::new());
return;
}
match st {
Stmt::Block(v) => strip_delegations_deep(v),
Stmt::If { then_stmt, else_stmt, .. } => {
strip_delegation_one(then_stmt);
if let Some(e) = else_stmt {
strip_delegation_one(e);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => strip_delegation_one(body),
Stmt::For { init, body, .. } => {
strip_delegations_deep(init);
strip_delegation_one(body);
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
strip_delegations_deep(&mut c.body);
}
if let Some(d) = default {
strip_delegation_one(d);
}
}
Stmt::Try { body, catches, finally } | Stmt::TryWithResources { body, catches, finally, .. } => {
strip_delegation_one(body);
for c in catches.iter_mut() {
strip_delegation_one(&mut c.body);
}
if let Some(f) = finally {
strip_delegation_one(f);
}
}
_ => {}
}
}
#[allow(clippy::ptr_arg)]
fn rewrite_bare_returns(stmts: &mut Vec<Stmt>, val: &Expr) {
for s in stmts.iter_mut() {
rewrite_bare_returns_one(s, val);
}
}
fn rewrite_bare_returns_one(st: &mut Stmt, val: &Expr) {
match st {
Stmt::Return(None) => *st = Stmt::Return(Some(val.clone())),
Stmt::Block(v) => rewrite_bare_returns(v, val),
Stmt::If { then_stmt, else_stmt, .. } => {
rewrite_bare_returns_one(then_stmt, val);
if let Some(e) = else_stmt {
rewrite_bare_returns_one(e, val);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => rewrite_bare_returns_one(body, val),
Stmt::For { init, body, .. } => {
rewrite_bare_returns(init, val);
rewrite_bare_returns_one(body, val);
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
rewrite_bare_returns(&mut c.body, val);
}
if let Some(d) = default {
rewrite_bare_returns_one(d, val);
}
}
Stmt::Try { body, catches, finally } | Stmt::TryWithResources { body, catches, finally, .. } => {
rewrite_bare_returns_one(body, val);
for c in catches.iter_mut() {
rewrite_bare_returns_one(&mut c.body, val);
}
if let Some(f) = finally {
rewrite_bare_returns_one(f, val);
}
}
_ => {}
}
}
fn collect_def_vars(stmts: &[Stmt], out: &mut jdc_core::FxHashSet<u32>) {
for st in stmts {
match st {
Stmt::LocalDef { var, .. } => {
out.insert(*var);
}
Stmt::ExprStmt(Expr::Assign { target, .. }) => {
if let Expr::Local { var, .. } = &**target {
out.insert(*var);
}
}
Stmt::Block(v) => collect_def_vars(v, out),
Stmt::If { then_stmt, else_stmt, .. } => {
collect_def_vars(std::slice::from_ref(then_stmt.as_ref()), out);
if let Some(e) = else_stmt {
collect_def_vars(std::slice::from_ref(e.as_ref()), out);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => {
collect_def_vars(std::slice::from_ref(body.as_ref()), out);
}
Stmt::ForEach { var, body, .. } => {
out.insert(*var);
collect_def_vars(std::slice::from_ref(body.as_ref()), out);
}
Stmt::For { init, body, .. } => {
collect_def_vars(init, out);
collect_def_vars(std::slice::from_ref(body.as_ref()), out);
}
Stmt::Switch { cases, default, .. } => {
for c in cases {
collect_def_vars(&c.body, out);
}
if let Some(d) = default {
collect_def_vars(std::slice::from_ref(d.as_ref()), out);
}
}
Stmt::Try { body, catches, finally }
| Stmt::TryWithResources { body, catches, finally, .. } => {
collect_def_vars(std::slice::from_ref(body.as_ref()), out);
for c in catches {
out.insert(c.var);
collect_def_vars(std::slice::from_ref(c.body.as_ref()), out);
}
if let Some(f) = finally {
collect_def_vars(std::slice::from_ref(f.as_ref()), out);
}
}
_ => {}
}
}
}
fn extraction_shape_ok(stmts: &[Stmt]) -> bool {
stmts.iter().all(|st| match st {
Stmt::Block(v) => extraction_shape_ok(v),
Stmt::LocalDef { .. }
| Stmt::ExprStmt(_)
| Stmt::Return(_)
| Stmt::Throw(_)
| Stmt::Assert { .. }
| Stmt::Break(_)
| Stmt::Continue(_) => true,
Stmt::If { then_stmt, else_stmt, .. } => {
ok_one(then_stmt)
&& else_stmt.as_deref().map(ok_one).unwrap_or(true)
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. } => ok_one(body),
Stmt::Labeled { body, .. } => ok_one(body),
Stmt::For { init, body, .. } => extraction_shape_ok(init) && ok_one(body),
Stmt::Switch { cases, default, .. } => {
cases.iter().all(|c| extraction_shape_ok(&c.body))
&& default.as_deref().map(ok_one).unwrap_or(true)
}
Stmt::Try { body, catches, finally }
| Stmt::TryWithResources { body, catches, finally, .. } => {
ok_one(body)
&& catches.iter().all(|c| ok_one(&c.body))
&& finally.as_deref().map(ok_one).unwrap_or(true)
}
_ => false,
})
}
fn ok_one(st: &Stmt) -> bool {
extraction_shape_ok(std::slice::from_ref(st))
}
fn static_safe(stmts: &[Stmt], this_id: Option<u32>) -> bool {
let mut ok = true;
for st in stmts {
visit_stmt_exprs_ro(st, &mut |e| {
visit_exprs(e, &mut |x| {
match x {
Expr::This => ok = false,
Expr::Local { var, .. } => {
if Some(*var) == this_id {
ok = false;
}
}
Expr::Field { owner: None, is_static: false, .. } => ok = false,
Expr::Method { owner: None, is_static: false, .. } => ok = false,
_ => {}
}
});
});
}
ok
}
pub fn strip_clinit_hotfix_guard(body: &mut Stmt) {
if let Stmt::Block(vs) = body {
flatten_top_blocks(vs);
}
let Stmt::Block(stmts) = body else { return };
let mut gi = 0usize;
while gi < stmts.len() && matches!(&stmts[gi], Stmt::LocalDef { init: None, .. }) {
gi += 1;
}
let Some(first) = stmts.get(gi) else { return };
let Stmt::If { then_stmt, else_stmt, .. } = first else {
return;
};
if else_stmt.is_some() {
return;
}
let mut has_ret = false;
let mut has_proxy = false;
let c = then_stmt.as_ref();
walk_all(c, &mut |st| {
if matches!(st, Stmt::Return(None)) {
has_ret = true;
}
});
let mut cc = c.clone();
walk_stmt_exprs(&mut cc, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Method { cls, .. } = x {
if cls.contains("PatchProxy") {
has_proxy = true;
}
}
});
});
if has_ret && has_proxy {
stmts.remove(gi);
}
}
pub(crate) fn first_leaf_stmt(s: &Stmt) -> &Stmt {
match s {
Stmt::Block(v) if !v.is_empty() => first_leaf_stmt(&v[0]),
other => other,
}
}
pub fn strip_ctor_hotfix_guards(body: &mut Stmt) {
fn marker_hit(e: &Expr) -> bool {
let has = |n: &str| {
let l = n.to_ascii_lowercase();
l.contains("hotfix")
|| l.contains("instantrun")
|| l.contains("robust")
|| l.contains("titan")
|| l.contains("patchproxy")
|| l.contains("changequickredirect")
|| l.contains("constructorcode")
};
let mut hit = false;
visit_exprs(e, &mut |x| {
match x {
Expr::Method { cls, .. } | Expr::New { cls, .. } | Expr::Field { cls, .. } => {
if has(cls) {
hit = true;
}
}
_ => {}
}
if let Expr::Field { ty, .. } = x {
if let TypeRef::J(JavaType::Object(n)) = ty {
if has(n) {
hit = true;
}
}
}
});
hit
}
fn is_guard(st: &Stmt) -> bool {
let mut cur = st;
loop {
match cur {
Stmt::Block(v) if v.len() == 1 => cur = &v[0],
Stmt::If { .. } => break,
_ => return false,
}
}
let mut has_del = false;
walk_all(cur, &mut |s2| {
if is_bare_ctor_call(s2) {
has_del = true;
}
});
if !has_del {
return false;
}
let mut has_marker = false;
visit_stmt_exprs_ro(cur, &mut |e| {
if marker_hit(e) {
has_marker = true;
}
});
has_marker
}
fn strip_in(stmts: &mut Vec<Stmt>) {
stmts.retain(|st| !is_guard(st));
for st in stmts.iter_mut() {
if let Stmt::Block(v) = st {
strip_in(v);
}
}
}
let Stmt::Block(stmts) = body else { return };
strip_in(stmts);
}
pub fn hoist_branch_delegations(body: &mut Stmt, vt: &VarTable) {
let Stmt::Block(stmts) = body else { return };
if stmts.first().is_some_and(is_bare_ctor_call) {
return; }
let mut dels: Vec<Expr> = Vec::new();
for st in stmts.iter() {
visit_stmt_exprs_ro(st, &mut |e| {
if is_delegation_expr(e) {
dels.push(e.clone());
}
});
}
if dels.is_empty() {
return;
}
if !dels.iter().all(|d| *d == dels[0]) {
return;
}
let mut bad = false;
visit_exprs(&dels[0], &mut |x| {
if let Expr::Local { var, .. } = x {
if (*var as usize) >= vt.vars.len() || !vt.vars[*var as usize].is_param {
bad = true;
}
}
});
if bad {
return;
}
let del = dels[0].clone();
strip_delegations_deep(stmts);
stmts.insert(0, Stmt::ExprStmt(del));
}
pub fn extract_branched_delegation_helper(
body: &mut Stmt,
vt: &VarTable,
pool: &DexPool,
class: &crate::PoolClass,
desc_str: &str,
) {
if class.is_enum() {
return;
}
if class.nesting.enclosing_class.is_some() && !crate::ctx::nested_is_static(pool, class) {
return;
}
let Stmt::Block(stmts) = body else { return };
let mut dels: Vec<Expr> = Vec::new();
for st in stmts.iter() {
visit_stmt_exprs_ro(st, &mut |e| {
if is_delegation_expr(e) {
dels.push(e.clone());
}
});
}
if dels.is_empty() {
return;
}
if dels.len() == 1 && stmts.first().is_some_and(is_bare_ctor_call) {
return; }
let template = dels[0].clone();
let Expr::Method { args: t_args, .. } = &template else {
return;
};
enum Mode {
Single(u32),
Position(usize),
Full,
}
let full_gate = (|| -> Option<()> {
let Expr::Method {
args: a0,
desc: d0,
is_super: s0,
cls: c0,
..
} = &dels[0]
else {
return None;
};
if a0.is_empty() {
return None;
}
for d in &dels[1..] {
match d {
Expr::Method {
args,
desc,
is_super,
cls,
..
} if args.len() == a0.len()
&& **desc == **d0
&& is_super == s0
&& cls == c0 => {}
_ => return None,
}
}
if class
.all_methods()
.any(|m| &*m.name == "<init>" && &*m.desc == "([Ljava/lang/Object;)V")
{
return None;
}
Some(())
})()
.is_some();
let mode = if dels.iter().all(|d| *d == dels[0]) {
let mut carrier: Option<u32> = None;
let mut reads = 0usize;
let mut multi = false;
for a in t_args.iter() {
let c = a.clone();
visit_exprs(&c, &mut |x| {
if let Expr::Local { var, .. } = x {
if (*var as usize) < vt.vars.len() && !vt.vars[*var as usize].is_param {
reads += 1;
match carrier {
Some(v) if v == *var => {}
Some(_) => multi = true,
None => carrier = Some(*var),
}
}
}
});
}
match carrier {
Some(cv) if !multi && reads == 1 => Mode::Single(cv),
_ if full_gate => Mode::Full,
_ => return,
}
} else {
let positioned: Option<Mode> = (|| {
let arity = t_args.len();
if dels
.iter()
.any(|d| !matches!(d, Expr::Method { args, .. } if args.len() == arity))
{
return None;
}
let mut diff: Vec<usize> = Vec::new();
for j in 0..arity {
if dels.iter().any(|d| match d {
Expr::Method { args, .. } => args.get(j) != t_args.get(j),
_ => true,
}) {
diff.push(j);
}
}
if diff.len() != 1 {
return None;
}
let k = diff[0];
let mut base0 = dels[0].clone();
if let Expr::Method { args, .. } = &mut base0 {
args[k] = Expr::Const(ConstVal::Null);
}
for d in dels.iter() {
let mut c = d.clone();
if let Expr::Method { args, .. } = &mut c {
args[k] = Expr::Const(ConstVal::Null);
}
if c != base0 {
return None;
}
}
let mut carrier_ty: Option<JavaType> = None;
for d in dels.iter() {
let Expr::Method { args, .. } = d else {
return None;
};
let Expr::Local { var, .. } = &args[k] else {
return None;
};
if (*var as usize) >= vt.vars.len() || vt.vars[*var as usize].is_param {
return None;
}
let ty = vt.vars[*var as usize].ty.erased();
match &carrier_ty {
Some(t) if *t == ty => {}
Some(_) => return None,
None => carrier_ty = Some(ty),
}
for (j, a) in args.iter().enumerate() {
if j == k {
continue;
}
let mut bad = false;
visit_exprs(a, &mut |x| {
if let Expr::Local { var: v2, .. } = x {
if (*v2 as usize) >= vt.vars.len() || !vt.vars[*v2 as usize].is_param {
bad = true;
}
}
});
if bad {
return None;
}
}
}
Some(Mode::Position(k))
})();
match positioned {
Some(m) => m,
None if full_gate => Mode::Full,
None => return,
}
};
let l = stmts.iter().rposition(is_bare_ctor_call);
if l == Some(0) {
return;
}
let (mut extracted, mut tail): (Vec<Stmt>, Vec<Stmt>) = match l {
Some(l) => (stmts[..l].to_vec(), stmts[l + 1..].to_vec()),
None => (stmts.clone(), Vec::new()),
};
if !extraction_shape_ok(&extracted) {
return;
}
let mut site_tails: Vec<Vec<Stmt>> = Vec::new();
{
let mut probe = extracted.clone();
site_tails_walk(&mut probe, &mut site_tails, false);
}
let hoist = !site_tails.is_empty() && {
let n0 = normalize_tail(&site_tails[0]);
site_tails.iter().all(|t| normalize_tail(t) == n0)
&& (l.is_none() || normalize_tail(&tail) == n0)
};
if hoist {
site_tails_walk(&mut extracted, &mut Vec::new(), true);
if l.is_none() {
tail = normalize_tail(&site_tails[0]);
}
}
let primary_del: Expr = match l {
Some(l) => match &stmts[l] {
Stmt::ExprStmt(e) => e.clone(),
_ => return,
},
None => dels[0].clone(),
};
let ret = match mode {
Mode::Single(cv) => vt.var(cv).ty.erased(),
Mode::Position(k) => match &primary_del {
Expr::Method { args, .. } => match args.get(k) {
Some(Expr::Local { var, .. }) => vt.var(*var).ty.erased(),
_ => return,
},
_ => return,
},
Mode::Full => JavaType::Array(Box::new(JavaType::Object(std::sync::Arc::from(
"java/lang/Object",
)))),
};
match mode {
Mode::Single(cv) => {
strip_delegations_deep(&mut extracted);
let cv_local = Expr::Local {
var: cv,
ty: vt.var(cv).ty.clone(),
};
rewrite_bare_returns(&mut extracted, &cv_local);
if !always_returns(&extracted) {
extracted.push(Stmt::Return(Some(cv_local)));
}
}
Mode::Position(k) => {
fill_returns_by_position(&mut extracted, &|args: &[Expr]| args.get(k).cloned());
let fallback = match &primary_del {
Expr::Method { args, .. } => args[k].clone(),
_ => return,
};
rewrite_bare_returns(&mut extracted, &fallback);
if !always_returns(&extracted) {
extracted.push(Stmt::Return(Some(fallback)));
}
}
Mode::Full => {
fill_returns_by_position(&mut extracted, &|args: &[Expr]| {
Some(array_init(args.to_vec()))
});
if l.is_none() {
if !always_returns(&extracted) {
let cut = (1..=extracted.len())
.find(|&i| always_returns(&extracted[..i]));
match cut {
Some(i) => extracted.truncate(i),
None => return,
}
}
}
let fallback_args = match &primary_del {
Expr::Method { args, .. } => args.clone(),
_ => return,
};
let fallback = array_init(fallback_args);
rewrite_bare_returns(&mut extracted, &fallback);
if !always_returns(&extracted) {
extracted.push(Stmt::Return(Some(fallback)));
}
}
}
let this_id = vt
.vars
.iter()
.find(|v| v.is_param && v.name == "this")
.map(|v| v.id);
if !static_safe(&extracted, this_id) {
return;
}
let mut defs: jdc_core::FxHashSet<u32> = jdc_core::FxHashSet::default();
collect_def_vars(&extracted, &mut defs);
if defs.iter().any(|v| var_occurs_in(&tail, *v)) {
return;
}
let params: Vec<(u32, JavaType, String)> = vt
.vars
.iter()
.filter(|v| v.is_param && v.name != "this")
.map(|v| (v.id, v.ty.erased(), v.name.clone()))
.collect();
let name = helper_name(&class.name, desc_str);
let call = Expr::Method {
owner: None,
cls: std::sync::Arc::from(class.name.as_str()),
name: std::sync::Arc::from(name.as_str()),
desc: std::sync::Arc::new(jdc_core::types::MethodDescriptor {
ret: ret.clone(),
args: params.iter().map(|p| p.1.clone()).collect(),
}),
args: params
.iter()
.map(|(id, ty, _)| Expr::Local {
var: *id,
ty: TypeRef::J(ty.clone()),
})
.collect(),
is_static: true,
is_interface: false,
is_special: false,
is_super: false,
is_dynamic: false,
type_args: Vec::new(),
};
let mut new_del = primary_del.clone();
match mode {
Mode::Single(cv) => {
deep_rewrite(&mut new_del, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == cv {
*x = call.clone();
}
}
});
}
Mode::Position(k) => {
let Expr::Method { args, .. } = &mut new_del else {
return;
};
if k >= args.len() {
return;
}
args[k] = call.clone();
}
Mode::Full => {
new_del = Expr::Method {
owner: None,
cls: std::sync::Arc::from(class.name.as_str()),
name: "<init>".into(),
desc: std::sync::Arc::new(jdc_core::types::MethodDescriptor {
args: vec![JavaType::Array(Box::new(JavaType::Object(
std::sync::Arc::from("java/lang/Object"),
)))],
ret: JavaType::Void,
}),
args: vec![call.clone()],
is_static: false,
is_interface: false,
is_special: true,
is_super: false,
is_dynamic: false,
type_args: Vec::new(),
};
}
}
{
let mut m = ctor_helpers().lock().unwrap_or_else(|e| e.into_inner());
let v = m.entry(class.name.clone()).or_default();
if !v.iter().any(|h| h.name == name) {
let mut hbody = Stmt::Block(extracted);
invert_empty_thens(&mut hbody);
v.push(CtorHelper {
name,
ret: ret.clone(),
params: params.into_iter().map(|(_, ty, nm)| (ty, nm)).collect(),
body: hbody,
vt: vt.clone(),
});
if matches!(mode, Mode::Full) {
let Expr::Method {
desc: d0,
is_super: s0,
..
} = &primary_del
else {
return;
};
let mut rm = ctor_relays().lock().unwrap_or_else(|e| e.into_inner());
let rv = rm.entry(class.name.clone()).or_default();
if !rv.iter().any(|(d, _)| d == desc_str) {
rv.push((
desc_str.to_string(),
CtorRelay {
formals: d0.args.clone(),
is_super: *s0,
},
));
}
}
} else {
return; }
}
*stmts = std::iter::once(Stmt::ExprStmt(new_del)).chain(tail).collect();
}
fn fill_returns_by_position(
stmts: &mut Vec<Stmt>,
pick: &dyn Fn(&[Expr]) -> Option<Expr>,
) {
let mut i = 0;
while i < stmts.len() {
if is_bare_ctor_call(&stmts[i]) {
let karg = match &stmts[i] {
Stmt::ExprStmt(Expr::Method { args, .. }) => pick(args),
_ => None,
};
if let Some(v) = karg {
let mut j = i + 1;
let mut found = None;
while j < stmts.len() {
if matches!(stmts[j], Stmt::Return(None)) {
found = Some(j);
break;
}
if is_bare_ctor_call(&stmts[j]) {
break; }
j += 1;
}
match found {
Some(j) => {
stmts[j] = Stmt::Return(Some(v));
stmts.remove(i);
continue;
}
None => {
stmts[i] = Stmt::Return(Some(v));
stmts.truncate(i + 1);
break;
}
}
}
}
fill_returns_one(&mut stmts[i], pick);
i += 1;
}
}
fn fill_returns_one(st: &mut Stmt, pick: &dyn Fn(&[Expr]) -> Option<Expr>) {
match st {
Stmt::Block(v) => fill_returns_by_position(v, pick),
Stmt::If { then_stmt, else_stmt, .. } => {
fill_returns_stmt(then_stmt, pick);
if let Some(e) = else_stmt {
fill_returns_stmt(e, pick);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => fill_returns_stmt(body, pick),
Stmt::For { init, body, .. } => {
fill_returns_by_position(init, pick);
fill_returns_stmt(body, pick);
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
fill_returns_by_position(&mut c.body, pick);
}
if let Some(d) = default {
fill_returns_stmt(d, pick);
}
}
Stmt::Try { body, catches, finally } | Stmt::TryWithResources { body, catches, finally, .. } => {
fill_returns_stmt(body, pick);
for c in catches.iter_mut() {
fill_returns_stmt(&mut c.body, pick);
}
if let Some(f) = finally {
fill_returns_stmt(f, pick);
}
}
_ => {}
}
}
fn fill_returns_stmt(st: &mut Stmt, pick: &dyn Fn(&[Expr]) -> Option<Expr>) {
if is_bare_ctor_call(st) {
if let Stmt::ExprStmt(Expr::Method { args, .. }) = st {
if let Some(v) = pick(args) {
*st = Stmt::Return(Some(v));
return;
}
}
}
fill_returns_one(st, pick);
}
fn normalize_tail(t: &[Stmt]) -> Vec<Stmt> {
let mut v = t.to_vec();
while matches!(v.last(), Some(Stmt::Return(None))) {
v.pop();
}
v
}
fn site_tails_walk(stmts: &mut Vec<Stmt>, out: &mut Vec<Vec<Stmt>>, carve: bool) {
let mut i = 0;
while i < stmts.len() {
if is_bare_ctor_call(&stmts[i]) {
let mut j = i + 1;
let mut end = stmts.len();
while j < stmts.len() {
if matches!(stmts[j], Stmt::Return(None)) {
end = j;
break;
}
if is_bare_ctor_call(&stmts[j]) {
end = j;
break;
}
j += 1;
}
out.push(stmts[i + 1..end].to_vec());
if carve {
stmts.splice(i + 1..end, []);
}
i += 1;
continue;
}
site_tails_one(&mut stmts[i], out, carve);
i += 1;
}
}
fn site_tails_one(st: &mut Stmt, out: &mut Vec<Vec<Stmt>>, carve: bool) {
if is_bare_ctor_call(st) {
out.push(Vec::new());
return;
}
match st {
Stmt::Block(v) => site_tails_walk(v, out, carve),
Stmt::If { then_stmt, else_stmt, .. } => {
site_tails_one(then_stmt, out, carve);
if let Some(e) = else_stmt {
site_tails_one(e, out, carve);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => site_tails_one(body, out, carve),
Stmt::For { init, body, .. } => {
site_tails_walk(init, out, carve);
site_tails_one(body, out, carve);
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
site_tails_walk(&mut c.body, out, carve);
}
if let Some(d) = default {
site_tails_one(d, out, carve);
}
}
Stmt::Try { body, catches, finally } | Stmt::TryWithResources { body, catches, finally, .. } => {
site_tails_one(body, out, carve);
for c in catches.iter_mut() {
site_tails_one(&mut c.body, out, carve);
}
if let Some(f) = finally {
site_tails_one(f, out, carve);
}
}
_ => {}
}
}
fn always_returns(stmts: &[Stmt]) -> bool {
let Some(last) = stmts
.iter()
.rev()
.find(|s| !matches!(s, Stmt::Block(v) if v.is_empty()))
else {
return false;
};
match last {
Stmt::Return(Some(_)) | Stmt::Throw(_) => true,
Stmt::Block(v) => always_returns(v),
Stmt::If { then_stmt, else_stmt, .. } => match else_stmt {
Some(e) => always_returns_one(then_stmt) && always_returns_one(e),
None => false,
},
Stmt::Labeled { body, .. } | Stmt::Synchronized { body, .. } => {
always_returns_one(body)
}
Stmt::Switch { cases, default, .. } => {
default.as_ref().is_some_and(|d| always_returns_one(d))
&& cases.iter().all(|c| always_returns(&c.body))
}
Stmt::Try { body, catches, finally }
| Stmt::TryWithResources { body, catches, finally, .. } => {
finally.as_ref().is_some_and(|f| always_returns_one(f))
|| (always_returns_one(body)
&& catches.iter().all(|c| always_returns_one(&c.body)))
}
_ => false,
}
}
fn always_returns_one(st: &Stmt) -> bool {
always_returns(std::slice::from_ref(st))
}
fn inline_ctor_arg_defs(
call: &mut Stmt,
prefix: &[&Stmt],
vt: &VarTable,
consumed: &mut Vec<(u32, usize)>,
) -> bool {
let Stmt::ExprStmt(cexpr) = call else {
return false;
};
fn subst(e: &mut Expr, v: u32, val: &Option<Expr>) -> bool {
let mut ok = true;
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == v {
match val {
Some(e2) => *x = e2.clone(),
None => ok = false,
}
}
}
});
ok
}
let mut seen: jdc_core::FxHashSet<u32> = jdc_core::FxHashSet::default();
for _ in 0..32 {
let mut need: Option<u32> = None;
visit_exprs(cexpr, &mut |x| {
if need.is_none() {
if let Expr::Local { var, .. } = x {
if (*var as usize) < vt.vars.len() && !vt.vars[*var as usize].is_param {
need = Some(*var);
}
}
}
});
let Some(v) = need else { break };
if !seen.insert(v) {
return false; }
let mut val: Option<Expr> = None;
let mut ok = true;
for (si, st) in prefix.iter().enumerate() {
match st {
Stmt::LocalDef { var, init: None, .. } if *var == v => {}
Stmt::LocalDef { var, init, .. } if *var == v => {
let Some(e) = init else { ok = false; break };
let mut e2 = (*e).clone();
if !subst(&mut e2, v, &val) {
ok = false;
break;
}
val = Some(e2);
consumed.push((v, si));
}
Stmt::ExprStmt(Expr::Assign { target, op, .. })
if matches!(&**target, Expr::Local { var: tv, .. } if *tv == v) =>
{
if !matches!(op, AssignOp::Plain) {
ok = false; break;
}
let Stmt::ExprStmt(Expr::Assign { value, .. }) = st else {
unreachable!()
};
let mut e2 = value.as_ref().clone();
if !subst(&mut e2, v, &val) {
ok = false;
break;
}
val = Some(e2);
consumed.push((v, si));
}
_ => {
let mut touched = false;
visit_all_exprs(st, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == v {
touched = true;
}
}
});
if touched {
ok = false;
break;
}
}
}
}
if !ok {
return false;
}
let Some(de) = val else { return false };
if contains_this_access(&de) {
return false;
}
let mut de_nodes = 0usize;
visit_exprs(&de, &mut |_| de_nodes += 1);
if de_nodes > 256 {
return false;
}
deep_rewrite(cexpr, &mut |x| {
if let Expr::Local { var, .. } = x {
if *var == v {
*x = de.clone();
}
}
});
let mut nodes = 0usize;
visit_exprs(cexpr, &mut |_| nodes += 1);
if nodes > 2_048 {
return false;
}
}
true
}
fn fold_sb_chain(stmts: &mut Vec<Stmt>, pos: usize) -> usize {
let mut sbv: Option<u32> = None;
let mut def_i: Option<usize> = None;
let mut def_init: Option<Expr> = None;
for (i, st) in stmts[..pos].iter().enumerate() {
let cand: Option<(u32, &Expr)> = match st {
Stmt::LocalDef {
var,
init: Some(e),
..
} => Some((*var, e)),
Stmt::ExprStmt(Expr::Assign {
target,
value,
op: AssignOp::Plain,
..
}) => match &**target {
Expr::Local { var, .. } => Some((*var, value.as_ref())),
_ => None,
},
_ => None,
};
if let Some((v, init)) = cand {
if let Expr::New { cls, .. } = init {
if cls.as_ref() == "java/lang/StringBuilder" {
sbv = Some(v);
def_i = Some(i);
def_init = Some(init.clone());
break;
}
}
}
}
let (Some(sbv), Some(def_i), Some(init)) = (sbv, def_i, def_init) else {
return pos;
};
if contains_this_access(&init) {
return pos;
}
let mut appends: Vec<usize> = Vec::new();
for (i, st) in stmts[..pos].iter().enumerate() {
if let Stmt::ExprStmt(Expr::Method {
owner: Some(o),
cls,
name,
..
}) = st
{
if matches!(&**o, Expr::Local { var, .. } if *var == sbv)
&& cls.as_ref() == "java/lang/StringBuilder"
&& &**name == "append"
{
appends.push(i);
}
}
}
let mut post_read = false;
for st in stmts.iter().skip(pos + 1) {
visit_all_exprs(st, &mut |e| {
if let Expr::Local { var, .. } = e {
if *var == sbv {
post_read = true;
}
}
});
}
if post_read {
return pos;
}
for (i, st) in stmts[..pos].iter().enumerate() {
if i == def_i || appends.contains(&i) {
if let Stmt::ExprStmt(Expr::Method { args, .. }) = st {
if args.iter().any(contains_this_access) {
return pos;
}
}
continue;
}
let mut leak = false;
visit_all_exprs(st, &mut |e| {
if let Expr::Local { var, .. } = e {
if *var == sbv {
leak = true;
}
}
});
if leak {
return pos;
}
}
let mut local_reads = 0usize;
let mut tostring_reads = 0usize;
visit_all_exprs(&stmts[pos], &mut |e| match e {
Expr::Local { var, .. } if *var == sbv => local_reads += 1,
Expr::Method {
owner: Some(o),
name,
args,
..
} if args.is_empty()
&& &**name == "toString"
&& matches!(&**o, Expr::Local { var, .. } if *var == sbv) =>
{
tostring_reads += 1;
}
_ => {}
});
if local_reads != 1 || tostring_reads != 1 {
return pos;
}
let mut chain = init;
for ai in &appends {
if let Stmt::ExprStmt(Expr::Method {
owner: _,
cls,
name,
desc,
args,
is_static,
is_interface,
is_special,
is_super,
is_dynamic,
type_args,
..
}) = &stmts[*ai]
{
chain = Expr::Method {
owner: Some(Box::new(chain)),
cls: cls.clone(),
name: name.clone(),
desc: desc.clone(),
args: args.clone(),
is_static: *is_static,
is_interface: *is_interface,
is_special: *is_special,
is_super: *is_super,
is_dynamic: *is_dynamic,
type_args: type_args.clone(),
};
}
}
let mut replaced = false;
walk_stmt_exprs(&mut stmts[pos], &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Method {
owner: Some(o),
name,
args,
..
} = x
{
if args.is_empty()
&& &**name == "toString"
&& matches!(&**o, Expr::Local { var, .. } if *var == sbv)
{
**o = chain.clone();
replaced = true;
}
}
});
});
if !replaced {
return pos; }
let mut removed: Vec<usize> = appends.clone();
removed.push(def_i);
removed.sort_unstable();
removed.dedup();
for i in removed.iter().rev() {
stmts.remove(*i);
}
pos - removed.len()
}
pub fn fix_ctor_super_first(body: &mut Stmt, vt: &VarTable) {
let Stmt::Block(stmts) = body else { return };
if stmts.is_empty() || is_bare_ctor_call(stmts.first().unwrap()) {
return;
}
if let Some(pos0) = stmts.iter().position(is_bare_ctor_call) {
let pos = fold_sb_chain(stmts, pos0);
let mut call = stmts[pos].clone();
let prefix: Vec<&Stmt> = stmts[..pos].iter().collect();
let mut consumed: Vec<(u32, usize)> = Vec::new();
if !inline_ctor_arg_defs(&mut call, &prefix, vt, &mut consumed) {
return;
}
stmts.remove(pos);
let mut drop_idx: Vec<usize> = consumed
.iter()
.filter(|(v, _)| !var_occurs_in(&stmts[pos..], *v))
.map(|(_, k)| *k)
.collect();
drop_idx.sort_unstable();
drop_idx.dedup();
for k in drop_idx.iter().rev() {
stmts.remove(*k);
}
stmts.insert(0, call);
return;
}
let mut found: Option<(usize, usize)> = None;
for (i, st) in stmts.iter().enumerate() {
if let Stmt::Block(inner) = st {
if let Some(p) = inner.iter().position(is_bare_ctor_call) {
if inner[..p]
.iter()
.all(|s| matches!(s, Stmt::LocalDef { .. } | Stmt::ExprStmt(_)))
{
found = Some((i, p));
break;
}
}
}
}
if let Some((i, p)) = found {
let mut call = match &stmts[i] {
Stmt::Block(inner) => inner[p].clone(),
_ => unreachable!(),
};
let mut prefix: Vec<&Stmt> = stmts[..i].iter().collect();
if let Stmt::Block(inner) = &stmts[i] {
prefix.extend(inner[..p].iter());
}
let mut consumed: Vec<(u32, usize)> = Vec::new();
if !inline_ctor_arg_defs(&mut call, &prefix, vt, &mut consumed) {
return;
}
let mut ks: Vec<usize> = Vec::new();
{
let inner_tail: &[Stmt] = match &stmts[i] {
Stmt::Block(inner) if p < inner.len() => &inner[p + 1..],
_ => &[],
};
for (v, k) in &consumed {
if !var_occurs_in(inner_tail, *v) && !var_occurs_in(&stmts[i + 1..], *v) {
ks.push(*k);
}
}
}
ks.sort_unstable();
ks.dedup();
if let Stmt::Block(inner) = &mut stmts[i] {
inner.remove(p);
for k in ks.iter().rev().filter(|k| **k >= i) {
inner.remove(k - i);
}
}
for k in ks.iter().rev().filter(|k| **k < i) {
stmts.remove(*k);
}
stmts.insert(0, call);
}
}
fn is_delegation_expr(e: &Expr) -> bool {
matches!(
e,
Expr::Method { name, is_special: true, owner, .. }
if &**name == "<init>"
&& (owner.is_none() || matches!(owner.as_deref(), Some(Expr::This)))
)
}
pub(crate) fn is_bare_ctor_call(s: &Stmt) -> bool {
matches!(s, Stmt::ExprStmt(e) if is_delegation_expr(e))
}
pub(crate) fn local_is_written(body: &Stmt, var: u32) -> bool {
let mut hit = false;
visit_stmt_exprs_ro(body, &mut |e| {
if hit {
return;
}
visit_exprs(e, &mut |x| {
if hit {
return;
}
match x {
Expr::Assign { target, .. }
if matches!(&**target, Expr::Local { var: v, .. } if *v == var) =>
{
hit = true;
}
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. }
if matches!(&**e, Expr::Local { var: v, .. } if *v == var) =>
{
hit = true;
}
_ => {}
}
});
});
hit
}
pub(crate) fn rewrite_inner_ctor_outer_param(
body: &mut Stmt,
param0: u32,
outer_display: &str,
outer_ty: &TypeRef,
eligible: &[String],
) {
walk_stmt_exprs(body, &mut |e| {
if let Expr::Method { name, cls, args, .. } = e {
if &**name == "<init>"
&& eligible.iter().any(|c| c.as_str() == cls.as_ref())
&& args.first().is_some_and(|a| matches!(a, Expr::Local { var: v, .. } if *v == param0))
{
args.remove(0);
}
}
});
let text = format!("{outer_display}.this");
let ty = outer_ty.clone();
walk_stmt_exprs(body, &mut |e| {
deep_rewrite_reads(e, &mut |x| {
if let Expr::Local { var: v, .. } = x {
if *v == param0 {
*x = Expr::RawT(text.clone(), ty.clone());
}
}
});
});
}
fn stmts_count_writes(stmts: &[Stmt], var: u32) -> usize {
let mut n = 0usize;
for s in stmts {
visit_stmt_exprs_ro(s, &mut |e| {
visit_exprs(e, &mut |x| match x {
Expr::Assign { target, .. }
if matches!(&**target, Expr::Local { var: v, .. } if *v == var) =>
{
n += 1;
}
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. }
if matches!(&**e, Expr::Local { var: v, .. } if *v == var) =>
{
n += 1;
}
_ => {}
});
});
}
n
}
fn prefix_written_vars(stmts: &[Stmt]) -> jdc_core::FxHashSet<u32> {
let mut set = jdc_core::FxHashSet::default();
for s in stmts {
let mut c = s.clone();
walk_stmt_exprs(&mut c, &mut |e| {
deep_rewrite(e, &mut |x| match x {
Expr::Assign { target, .. } => {
if let Expr::Local { var, .. } = &**target {
set.insert(*var);
}
}
Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => {
if let Expr::Local { var, .. } = &**e {
set.insert(*var);
}
}
_ => {}
});
});
if let Stmt::LocalDef { var, .. } = s {
set.insert(*var);
}
}
set
}
fn pre_this_safe(e: &Expr, prefix_vars: &jdc_core::FxHashSet<u32>) -> bool {
match e {
Expr::Local { var, .. } => !prefix_vars.contains(var),
Expr::Const(_) => true,
Expr::Cast { e, .. } => pre_this_safe(e, prefix_vars),
Expr::Bin { l, r, .. } => {
pre_this_safe(l, prefix_vars) && pre_this_safe(r, prefix_vars)
}
Expr::Cond { c, t, f } => {
pre_this_safe(c, prefix_vars)
&& pre_this_safe(t, prefix_vars)
&& pre_this_safe(f, prefix_vars)
}
Expr::Field { owner, is_static, .. } => {
*is_static
&& owner
.as_ref()
.is_none_or(|o| pre_this_safe(o, prefix_vars))
}
Expr::Method {
owner,
args,
is_static,
..
} => {
args.iter().all(|a| pre_this_safe(a, prefix_vars))
&& match owner {
Some(o) => pre_this_safe(o, prefix_vars),
None => *is_static,
}
}
Expr::New { args, .. } => args.iter().all(|a| pre_this_safe(a, prefix_vars)),
Expr::NewArray { dims, init, .. } => {
dims.iter().all(|d| pre_this_safe(d, prefix_vars))
&& init
.as_ref()
.map(|l| l.iter().all(|x| pre_this_safe(x, prefix_vars)))
.unwrap_or(true)
}
Expr::Un { e, .. } | Expr::InstanceOf { e, .. } => pre_this_safe(e, prefix_vars),
Expr::ArrayIndex { array, index } => {
pre_this_safe(array, prefix_vars) && pre_this_safe(index, prefix_vars)
}
_ => false,
}
}
fn single_assign_to_v(s: &Stmt, v: u32) -> Option<Expr> {
match s {
Stmt::ExprStmt(Expr::Assign {
target,
op: AssignOp::Plain,
value,
}) if matches!(&**target, Expr::Local { var: vv, .. } if *vv == v) => {
Some((**value).clone())
}
Stmt::Block(xs) if xs.len() == 1 => single_assign_to_v(&xs[0], v),
_ => None,
}
}
pub(crate) fn flatten_top_blocks(stmts: &mut Vec<Stmt>) {
fn rec(s: Stmt, out: &mut Vec<Stmt>) {
match s {
Stmt::Block(inner) => {
for x in inner {
rec(x, out);
}
}
other => out.push(other),
}
}
if !stmts.iter().any(|s| matches!(s, Stmt::Block(_))) {
return;
}
let mut out: Vec<Stmt> = Vec::with_capacity(stmts.len());
for s in std::mem::take(stmts) {
rec(s, &mut out);
}
*stmts = out;
}
fn fold_default_arg_bridge(stmts: &mut Vec<Stmt>) -> bool {
flatten_top_blocks(stmts);
let Some(pos) = stmts.iter().position(is_bare_ctor_call) else {
return false;
};
if pos == 0 {
return false;
}
let Stmt::ExprStmt(Expr::Method { args, .. }) = &stmts[pos] else {
return false;
};
let mut arg_vars: Vec<u32> = Vec::new();
let mut reads = 0usize;
for a in args {
let mut c = a.clone();
deep_rewrite_reads(&mut c, &mut |x| {
if let Expr::Local { var, .. } = x {
reads += 1;
if !arg_vars.contains(var) {
arg_vars.push(*var);
}
}
});
}
if arg_vars.is_empty() || reads != arg_vars.len() {
return false; }
for &v in &arg_vars {
if stmts_read_var(&stmts[pos + 1..], v) != 0
|| stmts_count_writes(&stmts[pos + 1..], v) != 0
{
return false;
}
}
let prefix_vars = prefix_written_vars(&stmts[..pos]);
let mut repl: Vec<(u32, Expr)> = Vec::new();
let mut drop: Vec<usize> = Vec::new();
for &v in &arg_vars {
let mut base: Option<(usize, Expr)> = None;
let mut decl: Option<usize> = None;
let mut condf: Option<(usize, Expr, Option<Expr>, Option<Expr>)> = None;
let mut ok = true;
for (i, s) in stmts[..pos].iter().enumerate() {
match s {
Stmt::LocalDef { var, init, .. } if *var == v => match init {
None => {
if decl.is_some() {
ok = false;
break;
}
decl = Some(i);
}
Some(e) => {
if base.is_some() {
ok = false;
break;
}
base = Some((i, e.clone()));
}
},
Stmt::ExprStmt(Expr::Assign {
target,
op: AssignOp::Plain,
value,
}) if matches!(&**target, Expr::Local { var: vv, .. } if *vv == v) => {
if base.is_some() {
ok = false;
break;
}
base = Some((i, (**value).clone()));
}
Stmt::If {
cond,
then_stmt,
else_stmt,
} => {
let t = single_assign_to_v(then_stmt, v);
let f = else_stmt.as_ref().and_then(|b| single_assign_to_v(b, v));
let then_touches = t.is_some()
|| stmts_read_var(std::slice::from_ref(&**then_stmt), v) != 0
|| stmts_count_writes(std::slice::from_ref(&**then_stmt), v) != 0;
let else_touches = else_stmt.as_ref().is_some_and(|b| {
f.is_some()
|| stmts_read_var(std::slice::from_ref(b), v) != 0
|| stmts_count_writes(std::slice::from_ref(b), v) != 0
});
if !then_touches && !else_touches {
continue; }
if condf.is_some()
|| base.is_none()
|| (then_touches && t.is_none())
|| (else_touches && f.is_none())
{
ok = false;
break;
}
let cond_stmt = Stmt::ExprStmt(cond.clone());
if stmts_read_var(std::slice::from_ref(&cond_stmt), v) != 0 {
ok = false;
break;
}
condf = Some((i, cond.clone(), t, f));
}
_ => {}
}
}
if !ok {
continue;
}
let Some((bi, be)) = base else { continue };
let want_writes = 1
+ condf
.as_ref()
.map(|(_, _, t, f)| t.is_some() as usize + f.is_some() as usize)
.unwrap_or(0);
if stmts_count_writes(&stmts[..pos], v) != want_writes
|| stmts_read_var(&stmts[..pos], v) != 0
{
continue;
}
let rep = match condf {
Some((ci, c, t, f)) => {
drop.push(ci);
match (t, f) {
(Some(t), Some(f)) => Expr::Cond {
c: Box::new(c),
t: Box::new(t),
f: Box::new(f),
},
(Some(t), None) => Expr::Cond {
c: Box::new(c),
t: Box::new(t),
f: Box::new(be.clone()),
},
(None, Some(f)) => Expr::Cond {
c: Box::new(c),
t: Box::new(be.clone()),
f: Box::new(f),
},
(None, None) => unreachable!("condf requires a branch assign"),
}
}
None => be.clone(),
};
if !pre_this_safe(&rep, &prefix_vars) {
continue;
}
drop.push(bi);
if let Some(d) = decl {
drop.push(d);
}
repl.push((v, rep));
}
if repl.is_empty() {
return false;
}
if let Stmt::ExprStmt(Expr::Method { args, .. }) = &mut stmts[pos] {
for a in args.iter_mut() {
deep_rewrite_reads(a, &mut |x| {
if let Expr::Local { var, .. } = x {
if let Some((_, e)) = repl.iter().find(|(vv, _)| vv == var) {
*x = e.clone();
}
}
});
}
}
drop.sort_unstable();
drop.dedup();
for i in drop.into_iter().rev() {
stmts.remove(i);
}
true
}
pub fn rewrite_kotlin_facades(body: &mut Stmt, pool: &crate::DexPool) {
let map = pool.kotlin_facade_map();
if map.is_empty() {
return;
}
walk_stmt_exprs(body, &mut |e| {
match e {
Expr::Method {
cls,
name,
desc,
is_static,
..
} if *is_static => {
if let Some((base, cover)) = map.get(cls.as_ref()) {
let hit = match cover {
None => true,
Some(c) => c.0.contains(&(name.to_string(), desc.to_string())),
};
if hit {
*cls = std::sync::Arc::from(base.as_str());
}
}
}
Expr::Field {
cls,
name,
is_static,
..
} if *is_static => {
if let Some((base, cover)) = map.get(cls.as_ref()) {
let hit = match cover {
None => true,
Some(c) => c.1.contains(name.as_ref()),
};
if hit {
*cls = std::sync::Arc::from(base.as_str());
}
}
}
_ => {}
}
});
}
pub fn fold_enum_default_arg_bridge(body: &mut Stmt) {
let Stmt::Block(stmts) = body else { return };
fold_default_arg_bridge(stmts);
}
pub fn fix_ctor_delegation_arg_defs(body: &mut Stmt) {
let Stmt::Block(stmts) = body else { return };
fold_default_arg_bridge(stmts);
let Some(pos) = stmts.iter().position(is_bare_ctor_call) else {
return;
};
if pos == 0 {
return;
}
let mut arg_vars: Vec<u32> = Vec::new();
let mut total_reads = 0usize;
{
let Stmt::ExprStmt(Expr::Method { args, .. }) = &stmts[pos] else {
return;
};
for a in args {
let mut c = a.clone();
deep_rewrite_reads(&mut c, &mut |x| {
if let Expr::Local { var: v, .. } = x {
total_reads += 1;
if !arg_vars.contains(v) {
arg_vars.push(*v);
}
}
});
}
}
if arg_vars.is_empty() || total_reads != arg_vars.len() {
return;
}
let prefix_defs: Vec<u32> = stmts[..pos]
.iter()
.filter_map(|s| match s {
Stmt::LocalDef { var, init: Some(_), .. } => Some(*var),
_ => None,
})
.collect();
let mut plan: Vec<(u32, usize, Option<usize>)> = Vec::new();
for v in arg_vars {
let defs: Vec<usize> = stmts[..pos]
.iter()
.enumerate()
.filter(|(i, _)| {
matches!(&stmts[*i], Stmt::LocalDef { var, init: Some(_), .. } if *var == v)
})
.map(|(i, _)| i)
.collect();
if defs.len() > 1 {
return; }
let Some(&q) = defs.first() else {
continue; };
let init = match &stmts[q] {
Stmt::LocalDef { init: Some(e), .. } => e.clone(),
_ => return,
};
if stmts_read_var(&stmts[q + 1..pos], v) != 0 {
return; }
let mut init_vars: Vec<u32> = Vec::new();
deep_rewrite_reads(&mut { init.clone() }, &mut |x| {
if let Expr::Local { var: vv, .. } = x {
if !init_vars.contains(vv) {
init_vars.push(*vv);
}
}
});
if init_vars.iter().any(|iv| prefix_defs.contains(iv)) {
return;
}
match stmts[pos + 1..]
.iter()
.position(|s| {
matches!(s, Stmt::ExprStmt(Expr::Assign { target, .. })
if matches!(&**target, Expr::Local { var: vv, .. } if *vv == v))
}) {
Some(w) => {
if stmts_read_var(&stmts[pos + 1..pos + 1 + w], v) != 0 {
return;
}
plan.push((v, q, Some(pos + 1 + w)));
}
None => {
if stmts_read_var(&stmts[pos + 1..], v) != 0 {
return;
}
plan.push((v, q, None));
}
}
}
if plan.is_empty() {
return;
}
let values: Vec<(u32, Expr)> = plan
.iter()
.map(|(_v, q, _)| match &stmts[*q] {
Stmt::LocalDef { var, init: Some(e), .. } => (*var, e.clone()),
_ => unreachable!("plan entries carry a LocalDef with init"),
})
.collect();
if let Stmt::ExprStmt(Expr::Method { args, .. }) = &mut stmts[pos] {
for a in args {
deep_rewrite_reads(a, &mut |x| {
if let Expr::Local { var: v, .. } = x {
if let Some((_, e)) = values.iter().find(|(vv, _)| vv == v) {
*x = e.clone();
}
}
});
}
}
for (v, _, w) in &plan {
if let Some(w) = w {
if let Stmt::ExprStmt(Expr::Assign { target, value, .. }) = &mut stmts[*w] {
if matches!(&**target, Expr::Local { var: vv, .. } if vv == v) {
let taken = std::mem::replace(&mut **value, Expr::Const(ConstVal::Null));
stmts[*w] = Stmt::LocalDef {
var: *v,
init: Some(taken),
is_final: false,
force_type: false,
};
}
}
}
}
let mut order: Vec<usize> = plan.iter().map(|(_, q, _)| *q).collect();
order.sort_unstable();
order.reverse();
for q in order {
stmts.remove(q);
}
}
pub fn fix_bool_returns(vt: &VarTable, body: &mut Stmt) {
walk_mut_deep(body, &mut |st| {
if let Stmt::Return(Some(e)) = st {
let numeric = match &*e {
Expr::Local { var, ty } => {
let t = if (*var as usize) < vt.vars.len() {
vt.var(*var).ty.erased()
} else {
ty.erased()
};
matches!(
t,
JavaType::Int
| JavaType::Long
| JavaType::Short
| JavaType::Byte
| JavaType::Char
)
}
other => matches!(
other.type_ref().erased(),
JavaType::Int | JavaType::Long | JavaType::Short | JavaType::Byte
| JavaType::Char
),
};
if numeric {
let taken = e.clone();
*e = Expr::Bin {
op: BinOp::Ne,
l: Box::new(taken),
r: Box::new(Expr::Const(ConstVal::Int(0))),
ty: Some(TypeRef::J(JavaType::Boolean)),
};
}
}
});
}
pub fn fix_int_returns(vt: &VarTable, body: &mut Stmt) {
walk_mut_deep(body, &mut |st| {
if let Stmt::Return(Some(e)) = st {
let is_bool = match e {
Expr::Local { var, .. } => {
(*var as usize) < vt.vars.len()
&& vt.vars[*var as usize].ty.erased() == JavaType::Boolean
}
Expr::Method { desc, .. } => desc.ret == JavaType::Boolean,
_ => false,
};
if is_bool {
*e = Expr::Cond {
c: Box::new(e.clone()),
t: Box::new(Expr::Const(ConstVal::Int(1))),
f: Box::new(Expr::Const(ConstVal::Int(0))),
};
}
}
});
}
static WIDEN_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub fn split_generations(vt: &mut VarTable, body: &mut Stmt, pool: &DexPool) {
if std::env::var("DDC_DBG_PHI").is_ok() {
eprintln!("[widen] cumulative={}", WIDEN_COUNT.load(std::sync::atomic::Ordering::Relaxed));
}
let mut counter: u32 = 0;
let mut gen: std::collections::HashMap<u32, u32> = std::collections::HashMap::default();
split_walk_stmt(body, vt, &mut gen, &mut counter, pool);
}
fn split_needed(vt: &VarTable, target: u32, value: &Expr, pool: &DexPool) -> bool {
let n = vt.vars.len();
if target as usize >= n {
return false;
}
let declared = vt.vars[target as usize].ty.erased();
let vt_is_ref = |t: &JavaType| matches!(t, JavaType::Object(_) | JavaType::Array(_));
fn side_bool(e: &Expr, vt: &VarTable) -> bool {
match e {
Expr::Local { var, .. } if (*var as usize) < vt.vars.len() => {
matches!(vt.var(*var).ty.erased(), JavaType::Boolean)
}
Expr::Const(ConstVal::Int(0 | 1)) => true,
Expr::Bin { op: BinOp::Or | BinOp::And | BinOp::Xor, l, r, .. } => {
side_bool(l, vt) && side_bool(r, vt)
}
other => matches!(other.type_ref().erased(), JavaType::Boolean),
}
}
let value_ty = match value {
Expr::Local { var, ty } if (*var as usize) < vt.vars.len() => vt.var(*var).ty.erased(),
Expr::Bin {
op: BinOp::Or | BinOp::And | BinOp::Xor,
l,
r,
..
} if side_bool(l, vt) && side_bool(r, vt) => JavaType::Boolean,
other => other.type_ref().erased(),
};
if vt_is_ref(&declared) && vt_is_ref(&value_ty) {
let obj = JavaType::Object("java/lang/Object".into());
if declared == value_ty || declared == obj || value_ty == obj {
return false;
}
if let (JavaType::Object(v), JavaType::Object(d)) = (&value_ty, &declared) {
if matches!(value, Expr::New { .. })
&& (pool.get(v).is_none() || pool.get(d).is_none())
{
return false;
}
}
true
} else {
if vt_is_ref(&declared) != vt_is_ref(&value_ty) {
return true;
}
let d_bool = matches!(declared, JavaType::Boolean);
let v_bool = matches!(value_ty, JavaType::Boolean);
let zero_one = matches!(value, Expr::Const(ConstVal::Int(0 | 1)));
d_bool != v_bool && !zero_one
}
}
fn rewrite_gen_reads(e: &mut Expr, gen: &std::collections::HashMap<u32, u32>, vt: &VarTable) {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, ty } = x {
if let Some(&to) = gen.get(var) {
if to != *var {
*var = to;
*ty = vt.var(to).ty.clone();
}
}
}
});
}
fn do_split(
vt: &mut VarTable,
target: &mut Expr,
old: u32,
value_ty: &JavaType,
gen: &mut std::collections::HashMap<u32, u32>,
counter: &mut u32,
) {
*counter += 1;
let info = &vt.vars[old as usize];
let id = vt.vars.len() as u32;
let name = format!("{}_g{}", info.name, counter);
let slot = info.slot;
let is_param = false;
vt.vars.push(jdc_core::var::VarInfo {
id,
slot,
name,
ty: TypeRef::J(value_ty.clone()),
is_param,
range_start: 0,
range_end: u16::MAX,
synthetic_name: true,
});
while vt.by_slot.len() <= slot as usize {
vt.by_slot.push(Vec::new());
}
vt.by_slot[slot as usize].push((0, u16::MAX, id));
gen.insert(old, id);
if let Expr::Local { var, ty, .. } = target {
*var = id;
*ty = TypeRef::J(value_ty.clone());
}
}
fn rename_gen_back_batch(s: &mut Stmt, pairs: &[(u32, u32)], vt: &VarTable) {
use jdc_core::FxHashMap;
if pairs.is_empty() {
return;
}
let map: FxHashMap<u32, (u32, JavaType)> = pairs
.iter()
.map(|&(from, to)| (from, (to, vt.vars[to as usize].ty.erased())))
.collect();
walk_mut_deep(s, &mut |st| {
if let Stmt::LocalDef { var, .. } = st {
if let Some(&(to, _)) = map.get(var) {
*var = to;
}
}
});
rewrite_exprs(s, &mut |e| {
deep_rewrite(e, &mut |x| {
if let Expr::Local { var, ty } = x {
if let Some(&(to, ref to_ty)) = map.get(var) {
*var = to;
*ty = TypeRef::J(to_ty.clone());
}
}
});
});
}
fn drop_gen_slot(vt: &mut VarTable, dead: u32) {
if (dead as usize) >= vt.vars.len() {
return;
}
let slot = vt.vars[dead as usize].slot as usize;
if let Some(seg) = vt.by_slot.get_mut(slot) {
seg.retain(|&(_, _, id)| id != dead);
}
}
fn lub_pending() -> Option<String> {
Some(String::new())
}
enum Undo {
Direct,
Widen(String),
Refuse,
}
fn obj_name(t: &TypeRef) -> Option<&std::sync::Arc<str>> {
match t {
TypeRef::J(JavaType::Object(o)) => Some(o),
_ => None,
}
}
fn pool_lub(a: &str, b: &str, pool: &DexPool) -> Option<String> {
if a == b {
return Some(a.to_string());
}
thread_local! {
static MEMO: std::cell::RefCell<jdc_core::FxHashMap<(String, String), Option<String>>> =
std::cell::RefCell::new(jdc_core::FxHashMap::default());
}
if let Some(hit) = MEMO.with(|m| m.borrow().get(&(a.to_string(), b.to_string())).cloned()) {
return hit;
}
let r = pool_lub_uncached(a, b, pool);
MEMO.with(|m| m.borrow_mut().insert((a.to_string(), b.to_string()), r.clone()));
r
}
fn pool_lub_uncached(a: &str, b: &str, pool: &DexPool) -> Option<String> {
let mut anc: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
let mut stack: Vec<String> = vec![a.to_string()];
while let Some(c) = stack.pop() {
if !anc.insert(c.clone()) {
continue;
}
if anc.len() > 1024 {
return None;
}
if let Some(cls) = pool.get(&c) {
if let Some(s) = &cls.super_name {
stack.push(s.clone());
}
for i in &cls.interfaces {
stack.push(i.clone());
}
} else {
if let Some(s) = crate::fwdb::super_of(&c) {
stack.push(s.to_string());
}
crate::fwdb::for_each_interface(&c, |i| stack.push(i.to_string()));
}
}
let mut seen: jdc_core::FxHashSet<String> = jdc_core::FxHashSet::default();
let mut queue: std::collections::VecDeque<String> = std::collections::VecDeque::new();
queue.push_back(b.to_string());
while let Some(c) = queue.pop_front() {
if !seen.insert(c.clone()) {
continue;
}
if c != "java/lang/Object" && anc.contains(&c) {
return Some(c);
}
if seen.len() > 1024 {
return None;
}
if let Some(cls) = pool.get(&c) {
for i in &cls.interfaces {
queue.push_back(i.clone());
}
if let Some(s) = &cls.super_name {
queue.push_back(s.clone());
}
} else {
crate::fwdb::for_each_interface(&c, |i| queue.push_back(i.to_string()));
if let Some(s) = crate::fwdb::super_of(&c) {
queue.push_back(s.to_string());
}
}
}
None
}
fn undo_type_safe(vt: &VarTable, k: u32, v: u32) -> bool {
if (k as usize) >= vt.vars.len() || (v as usize) >= vt.vars.len() {
return false;
}
let kt = vt.vars[k as usize].ty.erased();
let vt_ty = vt.vars[v as usize].ty.erased();
if kt == vt_ty {
return true;
}
let is_prim = |t: &JavaType| {
matches!(
t,
JavaType::Int
| JavaType::Long
| JavaType::Short
| JavaType::Byte
| JavaType::Char
| JavaType::Float
| JavaType::Double
| JavaType::Boolean
)
};
is_prim(&kt) && is_prim(&vt_ty)
}
fn split_walk_stmt(
st: &mut Stmt,
vt: &mut VarTable,
gen: &mut std::collections::HashMap<u32, u32>,
counter: &mut u32,
pool: &DexPool,
) {
match st {
Stmt::Block(v) => {
for x in v.iter_mut() {
split_walk_stmt(x, vt, gen, counter, pool);
}
}
Stmt::ExprStmt(e) => {
rewrite_gen_reads(e, gen, vt);
if let Expr::Assign {
target,
value,
op: AssignOp::Plain,
..
} = e
{
if let Expr::Local { var, .. } = &**target {
let old = *var;
if split_needed(vt, old, value, pool) {
let value_ty = value.type_ref().erased();
do_split(vt, target, old, &value_ty, gen, counter);
}
}
}
}
Stmt::LocalDef { var, init, .. } => {
if let Some(e) = init {
rewrite_gen_reads(e, gen, vt);
if split_needed(vt, *var, e, pool) {
let value_ty = e.type_ref().erased();
let old = *var;
let mut fake = Expr::Local {
var: old,
ty: vt.vars[old as usize].ty.clone(),
};
do_split(vt, &mut fake, old, &value_ty, gen, counter);
if let Expr::Local { var: nv, .. } = fake {
*var = nv;
}
}
}
}
Stmt::If {
cond,
then_stmt,
else_stmt,
..
} => {
rewrite_gen_reads(cond, gen, vt);
let then_floor = vt.vars.len() as u32;
let mut g_then = gen.clone();
split_walk_stmt(then_stmt, vt, &mut g_then, counter, pool);
let else_floor = vt.vars.len() as u32;
let mut g_else = gen.clone();
if let Some(e) = else_stmt {
split_walk_stmt(e, vt, &mut g_else, counter, pool);
}
let mut undo_then: Vec<(u32, u32, Option<String>)> = g_then
.iter()
.filter(|(&k, &v)| {
v >= then_floor
&& v < else_floor
&& g_else.get(&k).copied() == gen.get(&k).copied()
})
.filter_map(|(&k, &v)| {
if undo_type_safe(vt, k, v) {
Some((v, k, None))
} else if let (Some(_), Some(_)) =
(obj_name(&vt.vars[k as usize].ty), obj_name(&vt.vars[v as usize].ty))
{
Some((v, k, lub_pending()))
} else {
None
}
})
.collect();
if else_floor > then_floor && !undo_then.is_empty() {
let vars: Vec<u32> = undo_then.iter().map(|&(v, _, _)| v).collect();
let mut hits = vec![false; vars.len()];
mark_read_vars(std::slice::from_ref(&**then_stmt), &vars, &mut hits);
let mut i = 0usize;
undo_then.retain(|_| {
let keep = !hits[i];
i += 1;
keep
});
}
undo_then.sort_unstable_by_key(|&(v, _, _)| std::cmp::Reverse(v));
let mut pairs: Vec<(u32, u32)> = Vec::with_capacity(undo_then.len());
for (v, k, pending) in undo_then {
let kind = match pending {
None => Undo::Direct,
Some(_) => {
let (a, b) = (
obj_name(&vt.vars[k as usize].ty),
obj_name(&vt.vars[v as usize].ty),
);
match (a, b) {
(Some(a), Some(b)) if pool.is_subtype(b, a) => Undo::Direct,
(Some(a), Some(b)) => match pool_lub(a, b, pool) {
Some(lub) => Undo::Widen(lub),
None => Undo::Refuse,
},
_ => Undo::Refuse,
}
}
};
match kind {
Undo::Refuse => continue,
Undo::Direct => pairs.push((v, k)),
Undo::Widen(lub) => {
WIDEN_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
vt.vars[k as usize].ty = TypeRef::J(JavaType::Object(lub.as_str().into()));
pairs.push((v, k));
}
}
}
rename_gen_back_batch(then_stmt, &pairs, vt);
for &(v, _) in &pairs {
drop_gen_slot(vt, v);
}
if let Some(e) = else_stmt.as_deref_mut() {
let mut undo_else: Vec<(u32, u32, Option<String>)> = g_else
.iter()
.filter(|(&k, &v)| {
v >= else_floor && g_then.get(&k).copied() == gen.get(&k).copied()
})
.filter_map(|(&k, &v)| {
if undo_type_safe(vt, k, v) {
Some((v, k, None))
} else if let (Some(_), Some(_)) =
(obj_name(&vt.vars[k as usize].ty), obj_name(&vt.vars[v as usize].ty))
{
Some((v, k, lub_pending()))
} else {
None
}
})
.collect();
if (vt.vars.len() as u32) > else_floor && !undo_else.is_empty() {
let vars: Vec<u32> = undo_else.iter().map(|&(v, _, _)| v).collect();
let mut hits = vec![false; vars.len()];
mark_read_vars(std::slice::from_ref(&*e), &vars, &mut hits);
let mut i = 0usize;
undo_else.retain(|_| {
let keep = !hits[i];
i += 1;
keep
});
}
undo_else.sort_unstable_by_key(|&(v, _, _)| std::cmp::Reverse(v));
let mut pairs: Vec<(u32, u32)> = Vec::with_capacity(undo_else.len());
for (v, k, pending) in undo_else {
let kind = match pending {
None => Undo::Direct,
Some(_) => {
let (a, b) = (
obj_name(&vt.vars[k as usize].ty),
obj_name(&vt.vars[v as usize].ty),
);
match (a, b) {
(Some(a), Some(b)) if pool.is_subtype(b, a) => Undo::Direct,
(Some(a), Some(b)) => match pool_lub(a, b, pool) {
Some(lub) => Undo::Widen(lub),
None => Undo::Refuse,
},
_ => Undo::Refuse,
}
}
};
match kind {
Undo::Refuse => continue,
Undo::Direct => pairs.push((v, k)),
Undo::Widen(lub) => {
WIDEN_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
vt.vars[k as usize].ty = TypeRef::J(JavaType::Object(lub.as_str().into()));
pairs.push((v, k));
}
}
}
rename_gen_back_batch(e, &pairs, vt);
for &(v, _) in &pairs {
drop_gen_slot(vt, v);
}
}
gen.retain(|k, v| g_then.get(k) == Some(v) && g_else.get(k) == Some(v));
}
Stmt::While { cond, body, .. } | Stmt::DoWhile { cond, body, .. } => {
let entry = gen.clone();
for _ in 0..3 {
rewrite_gen_reads(cond, gen, vt);
split_walk_stmt(body, vt, gen, counter, pool);
}
*gen = entry;
}
Stmt::For { init, body, .. } => {
for x in init.iter_mut() {
split_walk_stmt(x, vt, gen, counter, pool);
}
let entry = gen.clone();
for _ in 0..3 {
split_walk_stmt(body, vt, gen, counter, pool);
}
*gen = entry;
}
Stmt::ForEach { body, .. } => {
let entry = gen.clone();
for _ in 0..3 {
split_walk_stmt(body, vt, gen, counter, pool);
}
*gen = entry;
}
Stmt::Switch { cases, default, .. } => {
let entry = gen.clone();
for c in cases.iter_mut() {
let mut g_case = entry.clone();
for x in c.body.iter_mut() {
split_walk_stmt(x, vt, &mut g_case, counter, pool);
}
gen.retain(|k, v| g_case.get(k) == Some(v));
}
if let Some(d) = default {
let mut g_def = entry.clone();
split_walk_stmt(d, vt, &mut g_def, counter, pool);
gen.retain(|k, v| g_def.get(k) == Some(v));
}
}
Stmt::Try {
body,
catches,
finally,
} => {
let entry = gen.clone();
let mut g_try = entry.clone();
split_walk_stmt(body, vt, &mut g_try, counter, pool);
let mut g_all = g_try.clone();
for c in catches.iter_mut() {
let mut g_c = entry.clone();
split_walk_stmt(&mut c.body, vt, &mut g_c, counter, pool);
g_all.retain(|k, v| g_c.get(k) == Some(v));
}
*gen = g_all;
if let Some(f) = finally {
split_walk_stmt(f, vt, gen, counter, pool);
}
}
Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => {
split_walk_stmt(body, vt, gen, counter, pool);
}
Stmt::Return(Some(e)) | Stmt::Throw(e) => {
rewrite_gen_reads(e, gen, vt);
}
_ => {}
}
}
fn fw_super(c: &str) -> Option<&'static str> {
crate::fwdb::super_of(c)
}
fn exc_subtype(sub: &str, sup: &str, pool: &DexPool) -> bool {
if sub == sup {
return true;
}
if pool.is_subtype(sub, sup) {
return true;
}
let mut cur = sub;
for _ in 0..24 {
let Some(next) = fw_super(cur) else { break };
if next == sup {
return true;
}
cur = next;
}
false
}
pub fn dedupe_multicatch(s: &mut Stmt, pool: &DexPool) {
dedupe_multicatch_walk(s, pool);
}
fn dedupe_multicatch_walk(s: &mut Stmt, pool: &DexPool) {
match s {
Stmt::Try {
body,
catches,
finally,
}
| Stmt::TryWithResources {
body,
catches,
finally,
..
} => {
dedupe_multicatch_walk(body, pool);
for c in catches.iter_mut() {
if c.exc.len() > 1 {
let mut keep: Vec<std::sync::Arc<str>> = Vec::with_capacity(c.exc.len());
for e in c.exc.iter() {
let shadowed = c.exc.iter().any(|o| {
o.as_ref() != e.as_ref() && exc_subtype(e, o, pool)
});
if !shadowed && !keep.iter().any(|k| k.as_ref() == e.as_ref()) {
keep.push(e.clone());
}
}
c.exc = keep;
}
dedupe_multicatch_walk(&mut c.body, pool);
}
if let Some(f) = finally {
dedupe_multicatch_walk(f, pool);
}
}
Stmt::Block(v) => {
for x in v.iter_mut() {
dedupe_multicatch_walk(x, pool);
}
}
Stmt::If {
then_stmt,
else_stmt,
..
} => {
dedupe_multicatch_walk(then_stmt, pool);
if let Some(e) = else_stmt {
dedupe_multicatch_walk(e, pool);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::For { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Labeled { body, .. }
| Stmt::Synchronized { body, .. } => dedupe_multicatch_walk(body, pool),
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
for x in c.body.iter_mut() {
dedupe_multicatch_walk(x, pool);
}
}
if let Some(d) = default {
dedupe_multicatch_walk(d, pool);
}
}
_ => {}
}
}
pub fn resolve_dangling_gotos(s: &mut Stmt) {
let mut labels: jdc_core::FxHashSet<u32> = jdc_core::FxHashSet::default();
walk_all(s, &mut |st| {
if let Stmt::Label(id) = st {
labels.insert(*id);
}
});
resolve_gotos_walk(s, &labels, 0);
}
fn resolve_gotos_walk(s: &mut Stmt, labels: &jdc_core::FxHashSet<u32>, loop_depth: u32) {
let depth = match s {
Stmt::While { .. } | Stmt::DoWhile { .. } | Stmt::For { .. } | Stmt::ForEach { .. } => {
loop_depth + 1
}
_ => loop_depth,
};
match s {
Stmt::Goto(id) => {
if labels.contains(id) {
return; }
if depth > 0 {
*s = Stmt::Break(None);
} else {
*s = Stmt::Comment(format!(
"$DDC: unresolved jump to block {} (irreducible control flow)",
id
));
}
}
Stmt::Block(v) => {
let mut i = 0usize;
while i < v.len() {
let dangling_here =
depth == 0 && matches!(&v[i], Stmt::Goto(id) if !labels.contains(id));
resolve_gotos_walk(&mut v[i], labels, depth);
if dangling_here {
v.truncate(i + 1);
break;
}
i += 1;
}
}
Stmt::If { then_stmt, else_stmt, .. } => {
resolve_gotos_walk(then_stmt, labels, depth);
if let Some(e) = else_stmt {
resolve_gotos_walk(e, labels, depth);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::For { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Labeled { body, .. }
| Stmt::Synchronized { body, .. } => resolve_gotos_walk(body, labels, depth),
Stmt::Try {
body,
catches,
finally,
} => {
resolve_gotos_walk(body, labels, depth);
for c in catches.iter_mut() {
resolve_gotos_walk(&mut c.body, labels, depth);
}
if let Some(f) = finally {
resolve_gotos_walk(f, labels, depth);
}
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
for x in c.body.iter_mut() {
resolve_gotos_walk(x, labels, depth);
}
}
if let Some(d) = default {
resolve_gotos_walk(d, labels, depth);
}
}
_ => {}
}
}
fn mark_read_vars(stmts: &[Stmt], vars: &[u32], hits: &mut [bool]) {
let mut remaining = vars.len();
for s in stmts {
if remaining == 0 {
break;
}
visit_stmt_exprs_ro(s, &mut |e| {
if remaining == 0 {
return;
}
visit_exprs_reads(e, &mut |x| {
if remaining == 0 {
return;
}
if let Expr::Local { var, .. } = x {
for (i, v) in vars.iter().enumerate() {
if *v == *var && !hits[i] {
hits[i] = true;
remaining -= 1;
}
}
}
});
});
}
}
fn stmts_read_var(stmts: &[Stmt], v: u32) -> usize {
let mut n = 0usize;
for s in stmts {
visit_stmt_exprs_ro(s, &mut |e| {
visit_exprs_reads(e, &mut |x| {
if let Expr::Local { var: vv, .. } = x {
if *vv == v {
n += 1;
}
}
});
});
}
n
}
pub fn fix_ctor_conditional_super(body: &mut Stmt) {
if !matches!(body, Stmt::Block(_)) {
let inner = std::mem::replace(body, Stmt::Block(Vec::new()));
*body = Stmt::Block(vec![inner]);
}
let Stmt::Block(stmts) = body else { return };
while stmts.len() == 1 {
let Stmt::Block(inner) = &stmts[0] else { break };
*stmts = inner.clone();
}
if stmts.is_empty() {
return;
}
if is_bare_ctor_call(stmts.first().unwrap()) {
dedupe_ctor_delegations(body);
return;
}
if try_linear_super_inline(stmts) {
return;
}
try_merge_branched_super(stmts);
}
pub fn strip_secondary_delegations(body: &mut Stmt) {
let Stmt::Block(stmts) = body else { return };
if !stmts.first().is_some_and(is_bare_ctor_call) {
return;
}
fn strip_in(s: &mut Stmt) {
if is_bare_ctor_call(s) {
*s = Stmt::Block(Vec::new());
return;
}
match s {
Stmt::Block(v) => v.iter_mut().for_each(strip_in),
Stmt::If {
then_stmt,
else_stmt,
..
} => {
strip_in(then_stmt);
if let Some(e) = else_stmt {
strip_in(e);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => strip_in(body),
Stmt::For { init, body, .. } => {
init.iter_mut().for_each(strip_in);
strip_in(body);
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
c.body.iter_mut().for_each(strip_in);
}
if let Some(d) = default {
strip_in(d);
}
}
Stmt::Try {
body,
catches,
finally,
}
| Stmt::TryWithResources {
body,
catches,
finally,
..
} => {
strip_in(body);
for c in catches.iter_mut() {
strip_in(&mut c.body);
}
if let Some(f) = finally {
strip_in(f);
}
}
_ => {}
}
}
for st in stmts.iter_mut().skip(1) {
strip_in(st);
}
}
pub fn dedupe_ctor_delegations(body: &mut Stmt) {
let Stmt::Block(stmts) = body else { return };
let Some(d0) = stmts.first().and_then(|s| match s {
Stmt::ExprStmt(e) if is_delegation_expr(e) => Some(e.clone()),
_ => None,
}) else {
return;
};
fn rec(s: &mut Stmt, d0: &Expr) {
match s {
Stmt::Block(v) => {
v.retain(|x| {
!matches!(x, Stmt::ExprStmt(e) if is_delegation_expr(e) && same_delegation(e, d0))
});
for x in v.iter_mut() {
rec(x, d0);
}
}
Stmt::If { then_stmt, else_stmt, .. } => {
rec(then_stmt, d0);
if let Some(e) = else_stmt {
rec(e, d0);
}
}
_ => {}
}
}
{
let d0r = &d0;
let mut idx = 0usize;
stmts.retain(|s| {
idx += 1;
idx == 1
|| !(matches!(s, Stmt::ExprStmt(e)
if is_delegation_expr(e) && same_delegation(e, d0r)))
});
}
for s in stmts.iter_mut().skip(1) {
rec(s, &d0);
}
}
fn same_delegation(a: &Expr, b: &Expr) -> bool {
match (a, b) {
(
Expr::Method { name: n1, cls: c1, is_super: s1, args: a1, .. },
Expr::Method { name: n2, cls: c2, is_super: s2, args: a2, .. },
) => {
&**n1 == "<init>"
&& &**n2 == "<init>"
&& c1 == c2
&& s1 == s2
&& (a1 == a2 || (a1.is_empty() && a2.is_empty()))
}
_ => false,
}
}
fn ctor_call_expr(s: &Stmt) -> Option<&Expr> {
match s {
Stmt::ExprStmt(e) if is_delegation_expr(e) => Some(e),
_ => None,
}
}
fn same_ctor_kind(a: &Expr, b: &Expr) -> bool {
match (a, b) {
(
Expr::Method { cls: c1, is_super: s1, owner: o1, .. },
Expr::Method { cls: c2, is_super: s2, owner: o2, .. },
) => c1 == c2 && s1 == s2 && o1 == o2,
_ => false,
}
}
fn flat_list(s: &Stmt) -> Vec<&Stmt> {
fn rec<'a>(s: &'a Stmt, out: &mut Vec<&'a Stmt>) {
match s {
Stmt::Block(v) => {
for x in v {
rec(x, out);
}
}
other => out.push(other),
}
}
let mut out = Vec::new();
rec(s, &mut out);
out
}
fn strip_lost_alloc_owners(e: &mut Expr) {
deep_rewrite(e, &mut |x| {
if let Expr::Method { name, is_special: true, owner: Some(o), .. } = x {
if &**name == "<init>" && !matches!(**o, Expr::This) {
**o = Expr::Const(jdc_core::ir::expr::ConstVal::Int(0));
}
}
});
}
fn count_locals_expr(e: &Expr) -> jdc_core::FxHashMap<u32, usize> {
let mut m = jdc_core::FxHashMap::default();
count_expr_ro(e, &mut m);
m
}
pub(crate) fn count_locals_stmts(ss: &[Stmt]) -> jdc_core::FxHashMap<u32, usize> {
let mut m = jdc_core::FxHashMap::default();
for s in ss {
visit_stmt_exprs_ro(s, &mut |e| count_expr_ro(e, &mut m));
}
m
}
fn cnt(m: &jdc_core::FxHashMap<u32, usize>, v: u32) -> usize {
m.get(&v).copied().unwrap_or(0)
}
fn inline_locals(
e: &Expr,
map: &std::collections::HashMap<u32, Expr>,
depth: u32,
) -> Option<Expr> {
if depth > 4 {
return None;
}
let mut out = e.clone();
let mut fail = false;
deep_rewrite(&mut out, &mut |x| {
if let Expr::Local { var, .. } = x {
if let Some(def) = map.get(var) {
match inline_locals(def, map, depth + 1) {
Some(r) => *x = r,
None => fail = true,
}
}
}
});
if fail {
None
} else {
Some(out)
}
}
fn def_of(s: &Stmt) -> Option<(u32, &Expr)> {
match s {
Stmt::LocalDef { var, init: Some(e), .. } => Some((*var, e)),
Stmt::ExprStmt(Expr::Assign {
target,
value,
op: jdc_core::ir::expr::AssignOp::Plain,
..
}) => match &**target {
Expr::Local { var, .. } => Some((*var, value)),
_ => None,
},
_ => None,
}
}
fn try_linear_super_inline(stmts: &mut Vec<Stmt>) -> bool {
let Some(pos) = stmts.iter().position(is_bare_ctor_call) else {
return false;
};
if pos == 0 {
return false; }
if !stmts[..pos]
.iter()
.all(|s| matches!(s, Stmt::LocalDef { .. } | Stmt::ExprStmt(_)))
{
return false;
}
let Some(call) = ctor_call_expr(&stmts[pos]) else {
return false;
};
let Expr::Method { args, .. } = call else {
return false;
};
let mut map: std::collections::HashMap<u32, Expr> =
std::collections::HashMap::new();
let mut def_idx: jdc_core::FxHashMap<u32, usize> =
jdc_core::FxHashMap::default();
for (i, s) in stmts[..pos].iter().enumerate() {
if let Some((v, e)) = def_of(s) {
map.insert(v, e.clone());
def_idx.insert(v, i);
}
}
let mut needed: Vec<u32> = Vec::new();
for a in args {
let mut probe = a.clone();
strip_lost_alloc_owners(&mut probe);
deep_rewrite(&mut probe, &mut |x| {
if let Expr::Local { var, .. } = x {
if map.contains_key(var) && !needed.contains(var) {
needed.push(*var);
}
}
});
}
if needed.is_empty() {
return false; }
let mut consumed: Vec<usize> = Vec::new();
let use_counts = count_locals_stmts(stmts);
for &v in &needed {
let def = &map[&v];
let uses = cnt(&use_counts, v);
if jdc_core::ir::build::has_side_effects(def) && uses != 1 {
return false;
}
if uses == 1 {
consumed.push(def_idx[&v]);
}
}
let mut new_args: Vec<Expr> = Vec::with_capacity(args.len());
for a in args {
match inline_locals(a, &map, 0) {
Some(r) => new_args.push(r),
None => return false,
}
}
let mut call_stmt = stmts.remove(pos);
if let Stmt::ExprStmt(Expr::Method { args: slot, .. }) = &mut call_stmt {
*slot = new_args;
}
let mut rest: Vec<Stmt> = Vec::with_capacity(stmts.len() + 1);
rest.push(call_stmt);
for (i, s) in stmts.drain(..).enumerate() {
let orig = if i >= pos { i + 1 } else { i };
if !consumed.contains(&orig) {
rest.push(s);
}
}
*stmts = rest;
true
}
fn try_merge_branched_super(stmts: &mut Vec<Stmt>) -> bool {
let if_positions: Vec<usize> = stmts
.iter()
.enumerate()
.filter(|(_, s)| matches!(s, Stmt::If { .. }))
.map(|(i, _)| i)
.collect();
for if_pos in if_positions {
let gate = match &stmts[if_pos] {
Stmt::If { then_stmt, else_stmt, .. } => {
contains_delegation(then_stmt)
|| else_stmt.as_ref().is_some_and(|e| contains_delegation(e))
}
_ => false,
};
if gate && merge_at(stmts, if_pos) {
return true;
}
}
false
}
fn contains_delegation(s: &Stmt) -> bool {
match s {
Stmt::ExprStmt(e) => is_delegation_expr(e),
Stmt::Block(v) => v.iter().any(contains_delegation),
Stmt::If { then_stmt, else_stmt, .. } => {
contains_delegation(then_stmt)
|| else_stmt.as_ref().is_some_and(|e| contains_delegation(e))
}
_ => false,
}
}
fn merge_at(stmts: &mut Vec<Stmt>, if_pos: usize) -> bool {
enum Br {
Call(usize, Vec<(u32, Expr)>, Vec<usize>),
Clean,
Dirty,
}
fn split_branch(list: &[&Stmt]) -> Br {
let call_pos = list.iter().position(|s| ctor_call_expr(s).is_some());
let Some(c) = call_pos else {
return if list.iter().any(|s| contains_delegation(s)) {
Br::Dirty
} else {
Br::Clean
};
};
if list.iter().skip(c + 1).any(|s| ctor_call_expr(s).is_some()) {
return Br::Dirty; }
let mut defs: Vec<(u32, Expr)> = Vec::new();
let mut def_idxs: Vec<usize> = Vec::new();
for (i, s) in list.iter().enumerate().take(c) {
match def_of(s) {
Some((v, e)) => {
defs.push((v, e.clone()));
def_idxs.push(i);
}
None => match s {
Stmt::ExprStmt(_) | Stmt::LocalDef { .. } => {}
_ => return Br::Dirty, },
}
}
Br::Call(c, defs, def_idxs)
}
fn cond_def_of(s: &Stmt, scope: &[Stmt]) -> Option<(u32, Expr)> {
let Stmt::If { cond, then_stmt, else_stmt: Some(e), .. } = s else {
return None;
};
fn one<'x>(b: &'x Stmt, scope: &[Stmt]) -> Option<(u32, &'x Expr)> {
let l = flat_list(b);
if l.len() == 1 {
return def_of(l[0]);
}
if l.len() == 2 {
let (w, ex) = def_of(l[0])?;
let Stmt::ExprStmt(Expr::Assign {
target,
op: AssignOp::Plain,
value,
}) = l[1]
else {
return None;
};
let Expr::Local { var: tv, .. } = &**target else {
return None;
};
let Expr::Local { var: rv, .. } = &**value else {
return None;
};
if *rv != w || *tv == w {
return None;
}
let mut ldef_writes = 0usize;
for st in scope {
walk_all(st, &mut |x| {
if let Stmt::LocalDef {
var,
init: Some(_),
..
} = x
{
if *var == w {
ldef_writes += 1;
}
}
});
}
if stmts_read_var(scope, w) != 1
|| stmts_count_writes(scope, w) + ldef_writes != 1
{
return None;
}
return Some((*tv, ex));
}
None
}
let (v1, e1) = one(then_stmt, scope)?;
let (v2, e2) = one(e, scope)?;
if v1 != v2 {
return None;
}
Some((
v1,
Expr::Cond {
c: Box::new(cond.clone()),
t: Box::new(e1.clone()),
f: Box::new(e2.clone()),
},
))
}
let mut pmap: std::collections::HashMap<u32, Expr> =
std::collections::HashMap::new();
let mut pidx: jdc_core::FxHashMap<u32, usize> =
jdc_core::FxHashMap::default();
for (i, s) in stmts[..if_pos].iter().enumerate() {
match s {
Stmt::LocalDef { .. } | Stmt::ExprStmt(_) => {
if let Some((v, e)) = def_of(s) {
pmap.insert(v, e.clone());
pidx.insert(v, i);
}
}
Stmt::If { .. } => match cond_def_of(s, stmts) {
Some((v, e)) => {
pmap.insert(v, e);
pidx.insert(v, i);
}
None => return false,
},
_ => return false,
}
}
let (cond, then_v, else_v, had_else) = match &stmts[if_pos] {
Stmt::If { cond, then_stmt, else_stmt, .. } => (
cond.clone(),
then_stmt.as_ref().clone(),
else_stmt.as_ref().map_or_else(|| Stmt::Block(Vec::new()), |e| e.as_ref().clone()),
else_stmt.is_some(),
),
_ => return false,
};
fn normalize_branch(b: &mut Stmt) {
let slot: &mut Stmt = match b {
Stmt::Block(v) if v.len() == 1 => &mut v[0],
other => other,
};
if !matches!(slot, Stmt::If { .. }) || !contains_delegation(slot) {
return;
}
let taken = std::mem::replace(slot, Stmt::Block(Vec::new()));
let mut tmp = vec![taken];
if merge_at(&mut tmp, 0) {
*slot = Stmt::Block(tmp);
} else {
*slot = tmp.pop().unwrap();
}
}
let mut then_v = then_v;
let mut else_v = else_v;
normalize_branch(&mut then_v);
normalize_branch(&mut else_v);
let then_list = flat_list(&then_v);
let else_list = flat_list(&else_v);
enum Side {
Call {
at: usize,
defs: Vec<(u32, Expr)>,
def_idxs: Vec<usize>,
},
Clean,
}
let (t_side, e_side, two_sided) = match (split_branch(&then_list), split_branch(&else_list)) {
(Br::Call(tc, td, ti), Br::Call(ec, ed, ei)) => (
Side::Call { at: tc, defs: td, def_idxs: ti },
Side::Call { at: ec, defs: ed, def_idxs: ei },
true,
),
(Br::Call(tc, td, ti), Br::Clean) => (
Side::Call { at: tc, defs: td, def_idxs: ti },
Side::Clean,
false,
),
(Br::Clean, Br::Call(ec, ed, ei)) => (
Side::Clean,
Side::Call { at: ec, defs: ed, def_idxs: ei },
false,
),
_ => return false,
};
let (t_call, e_call) = match (&t_side, &e_side) {
(Side::Call { at: ta, .. }, Side::Call { at: ea, .. }) => (
ctor_call_expr(then_list[*ta]),
ctor_call_expr(else_list[*ea]),
),
(Side::Call { at: ta, .. }, Side::Clean) => {
(ctor_call_expr(then_list[*ta]), None)
}
(Side::Clean, Side::Call { at: ea, .. }) => {
(None, ctor_call_expr(else_list[*ea]))
}
_ => return false,
};
let (call_list, call_at) = match &t_side {
Side::Call { at, .. } => (&then_list, *at),
Side::Clean => match &e_side {
Side::Call { at, .. } => (&else_list, *at),
Side::Clean => return false,
},
};
let (base_args, other_args) = if two_sided {
let (Some(tc), Some(ec)) = (t_call, e_call) else {
return false;
};
if !same_ctor_kind(tc, ec) {
return false;
}
let (Expr::Method { args: a1, .. }, Expr::Method { args: a2, .. }) = (tc, ec) else {
return false;
};
if a1.len() != a2.len() {
return false;
}
(a1, Some(a2))
} else {
let c = t_call.or(e_call);
let Some(c) = c else { return false };
let Expr::Method { args, .. } = c else {
return false;
};
(args, None)
};
let mut tmap: std::collections::HashMap<u32, Expr> = pmap.clone();
let mut emap: std::collections::HashMap<u32, Expr> = pmap.clone();
if let Side::Call { defs, .. } = &t_side {
for (v, e) in defs {
tmap.insert(*v, e.clone());
}
}
if let Side::Call { defs, .. } = &e_side {
for (v, e) in defs {
emap.insert(*v, e.clone());
}
}
let Some(cond_i) = inline_locals(&cond, &pmap, 0) else {
return false;
};
let mut then_args: Vec<Expr> = Vec::with_capacity(base_args.len());
let mut else_args: Vec<Expr> = Vec::new();
if two_sided {
for a in base_args {
match inline_locals(a, &tmap, 0) {
Some(r) => then_args.push(r),
None => return false,
}
}
for a in other_args.unwrap() {
match inline_locals(a, &emap, 0) {
Some(r) => else_args.push(r),
None => return false,
}
}
} else {
let from_then = matches!(&t_side, Side::Call { .. });
for a in base_args {
let r = if from_then {
inline_locals(a, &tmap, 0)
} else {
inline_locals(a, &emap, 0)
};
match r {
Some(r) => then_args.push(r),
None => return false,
}
}
}
let primary_args = base_args;
let mut merged: Vec<Expr> = Vec::with_capacity(then_args.len());
let mut differ = 0usize;
for (j, t) in then_args.iter().enumerate() {
if two_sided {
let e = &else_args[j];
if t == e {
merged.push(t.clone());
} else {
differ += 1;
merged.push(Expr::Cond {
c: Box::new(cond_i.clone()),
t: Box::new(t.clone()),
f: Box::new(e.clone()),
});
}
} else {
merged.push(t.clone());
}
}
let vars_in = |e: &Expr| -> Vec<u32> {
let mut vs = Vec::new();
let mut probe = e.clone();
strip_lost_alloc_owners(&mut probe);
deep_rewrite(&mut probe, &mut |x| {
if let Expr::Local { var, .. } = x {
if !vs.contains(var) {
vs.push(*var);
}
}
});
vs
};
let branch_remainder = |list: &[&Stmt], side: &Side| -> Vec<Stmt> {
match side {
Side::Clean => list.iter().map(|s| (*s).clone()).collect(),
Side::Call { at, def_idxs, .. } => {
list.iter()
.enumerate()
.filter(|(i, _)| *i != *at && !def_idxs.contains(i))
.map(|(_, s)| (*s).clone())
.collect()
}
}
};
let tail_then_v = branch_remainder(&then_list, &t_side);
let tail_else_v = branch_remainder(&else_list, &e_side);
let post = &stmts[if_pos + 1..];
let mut inlined: Vec<u32> = vars_in(&cond);
for a in primary_args.iter().chain(else_args.iter()) {
for v in vars_in(a) {
if !inlined.contains(&v) {
inlined.push(v);
}
}
}
inlined.retain(|v| pmap.contains_key(v) || tmap.contains_key(v) || emap.contains_key(v));
for &v in &inlined {
let in_branch = match (&t_side, &e_side) {
(Side::Call { defs: td, .. }, Side::Call { defs: ed, .. }) => {
td.iter().any(|(d, _)| *d == v) || ed.iter().any(|(d, _)| *d == v)
}
(Side::Call { defs, .. }, Side::Clean)
| (Side::Clean, Side::Call { defs, .. }) => {
defs.iter().any(|(d, _)| *d == v)
}
_ => false,
};
if pmap.contains_key(&v) && in_branch {
return false;
}
}
type Counts = jdc_core::FxHashMap<u32, usize>;
type GuardCounts = (
Counts,
Vec<Counts>,
Vec<Counts>,
Counts,
Counts,
Counts,
Vec<(u32, Counts)>,
);
let (cond_counts, a1_counts, a2_counts, tail1_counts, tail2_counts, post_counts, def_counts): GuardCounts =
if inlined.is_empty() {
Default::default()
} else {
let all_defs: Vec<(u32, &Expr)> = pmap
.iter()
.map(|(k, v)| (*k, v))
.chain(match &t_side {
Side::Call { defs, .. } => defs.iter().map(|(k, v)| (*k, v)).collect(),
Side::Clean => Vec::new(),
})
.chain(match &e_side {
Side::Call { defs, .. } => defs.iter().map(|(k, v)| (*k, v)).collect(),
Side::Clean => Vec::new(),
})
.collect();
(
count_locals_expr(&cond),
primary_args.iter().map(count_locals_expr).collect(),
else_args.iter().map(count_locals_expr).collect(),
count_locals_stmts(&tail_then_v),
count_locals_stmts(&tail_else_v),
count_locals_stmts(post),
all_defs.into_iter().map(|(v, e)| (v, count_locals_expr(e))).collect(),
)
};
for &v in &inlined {
let in_t = matches!(&t_side, Side::Call { defs, .. } if defs.iter().any(|(d, _)| *d == v));
let in_e = matches!(&e_side, Side::Call { defs, .. } if defs.iter().any(|(d, _)| *d == v));
let effectful = [
in_t.then(|| tmap.get(&v)).flatten(),
in_e.then(|| emap.get(&v)).flatten(),
pmap.get(&v),
]
.into_iter()
.flatten()
.any(jdc_core::ir::build::has_side_effects);
if !effectful {
continue;
}
let u_cond = cnt(&cond_counts, v);
let u_post = cnt(&post_counts, v);
let u_t1 = cnt(&tail1_counts, v);
let u_t2 = cnt(&tail2_counts, v);
let mut u_odef = 0usize;
for (dv, dc) in &def_counts {
if *dv != v && inlined.contains(dv) {
u_odef += cnt(dc, v);
}
}
let mut emit = u_cond * (1 + differ);
let mut uses_args = 0usize;
for j in 0..primary_args.len() {
let c1 = cnt(&a1_counts[j], v);
let c2 = if two_sided && j < a2_counts.len() {
cnt(&a2_counts[j], v)
} else {
0
};
uses_args += c1 + c2;
emit += if two_sided && then_args[j] != else_args[j] {
c1 + c2
} else {
c1.max(c2)
};
}
let ok = if in_t || in_e {
u_cond == 0
&& u_post == 0
&& u_t1 == 0
&& u_t2 == 0
&& u_odef == 0
&& a1_counts.iter().map(|c| cnt(c, v)).sum::<usize>() <= 1
&& a2_counts.iter().map(|c| cnt(c, v)).sum::<usize>() <= 1
&& emit <= 2
&& (in_t || a1_counts.iter().all(|c| cnt(c, v) == 0))
&& (in_e || a2_counts.iter().all(|c| cnt(c, v) == 0))
} else {
u_post == 0
&& u_t1 == 0
&& u_t2 == 0
&& u_odef == 0
&& ((u_cond == 1 && uses_args == 0 && emit <= 2)
|| (u_cond == 0 && emit <= 1))
};
if !ok {
return false;
}
}
let mut call_stmt = call_list[call_at].clone();
if let Stmt::ExprStmt(Expr::Method { args: slot, .. }) = &mut call_stmt {
*slot = merged;
}
let residual = |v: u32| -> usize {
cnt(&tail1_counts, v) + cnt(&tail2_counts, v) + cnt(&post_counts, v)
};
let mut kept_prelude: Vec<Stmt> = Vec::new();
for (i, s) in stmts[..if_pos].iter().enumerate() {
let consumed = match s {
Stmt::LocalDef { var, .. } => Some(*var),
Stmt::ExprStmt(Expr::Assign { target, .. }) => match &**target {
Expr::Local { var, .. } => Some(*var),
_ => None,
},
Stmt::If { .. } => cond_def_of(s, stmts).map(|(v, _)| v),
_ => None,
};
if let Some(v) = consumed {
if inlined.contains(&v) && pidx.get(&v) == Some(&i) && residual(v) == 0 {
continue;
}
}
kept_prelude.push(s.clone());
}
let build_keep = |list: &[&Stmt], side: &Side| -> Vec<Stmt> {
match side {
Side::Clean => list.iter().map(|s| (*s).clone()).collect(),
Side::Call { at, .. } => {
let mut out = Vec::new();
for (i, s) in list.iter().enumerate() {
if i == *at {
continue;
}
if i < *at {
if let Some((v, _)) = def_of(s) {
if inlined.contains(&v) && residual(v) == 0 {
continue;
}
}
}
out.push((*s).clone());
}
out
}
}
};
let then_keep = build_keep(&then_list, &t_side);
let else_keep = build_keep(&else_list, &e_side);
let else_part = if else_keep.is_empty() && !had_else {
None
} else {
Some(Box::new(Stmt::Block(else_keep)))
};
let new_if = Stmt::If {
cond: cond_i,
then_stmt: Box::new(Stmt::Block(then_keep)),
else_stmt: else_part,
};
let if_empty = match &new_if {
Stmt::If { then_stmt, else_stmt, .. } => {
let be = |s: &Stmt| matches!(s, Stmt::Block(v) if v.is_empty());
be(then_stmt) && else_stmt.as_ref().is_none_or(|e| be(e))
}
_ => false,
};
let mut out: Vec<Stmt> = Vec::with_capacity(stmts.len());
out.push(call_stmt);
out.extend(kept_prelude);
if !if_empty {
out.push(new_if);
}
out.extend(stmts[if_pos + 1..].to_vec());
*stmts = out;
true
}
pub fn strip_enum_ctor_super(body: &mut Stmt) {
fn is_enum_super(s: &Stmt) -> bool {
matches!(
s,
Stmt::ExprStmt(Expr::Method { name, is_super: true, .. }) if &**name == "<init>"
)
}
fn strip_deep(stmts: &mut Vec<Stmt>) {
stmts.retain(|s| !is_enum_super(s));
for st in stmts.iter_mut() {
strip_one(st);
}
}
fn strip_one(st: &mut Stmt) {
match st {
Stmt::Block(v) => strip_deep(v),
Stmt::If { then_stmt, else_stmt, .. } => {
strip_one(then_stmt);
if let Some(e) = else_stmt {
strip_one(e);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::ForEach { body, .. }
| Stmt::Synchronized { body, .. }
| Stmt::Labeled { body, .. } => strip_one(body),
Stmt::For { init, body, .. } => {
strip_deep(init);
strip_one(body);
}
Stmt::Switch { cases, default, .. } => {
for c in cases.iter_mut() {
strip_deep(&mut c.body);
}
if let Some(d) = default {
strip_one(d);
}
}
Stmt::Try { body, catches, finally }
| Stmt::TryWithResources { body, catches, finally, .. } => {
strip_one(body);
for c in catches.iter_mut() {
strip_one(&mut c.body);
}
if let Some(f) = finally {
strip_one(f);
}
}
_ => {}
}
}
let Stmt::Block(stmts) = body else {
return;
};
strip_deep(stmts);
}
pub fn ensure_declared(body: &mut Stmt, vt: &VarTable) {
let n = vt.vars.len().max(1);
let mut seen: HashSet<u32> = HashSet::default();
demote_dup_decls(body, &mut seen, vt);
let mut def_block: Vec<u32> = vec![u32::MAX; n];
let mut sizes: Vec<u32> = vec![0];
let mut next_id: u32 = 1;
scope_pass_a(body, 0, &mut next_id, &mut def_block, &mut sizes);
sizes[0] = next_id;
let mut within: Vec<u32> = vec![0; n];
let mut total: Vec<u32> = vec![0; n];
let mut next_b: u32 = 1;
scope_pass_b(body, 0, &mut next_b, &def_block, &sizes, &mut within, &mut total);
let mut hoist: HashSet<u32> = HashSet::default();
for v in 0..n {
if def_block[v] != u32::MAX && def_block[v] != 0 && within[v] < total[v] {
hoist.insert(v as u32);
}
}
if !hoist.is_empty() {
let mut seed: HashSet<u32> = hoist.iter().copied().collect();
demote_dup_decls(body, &mut seed, vt);
}
let mut declared: Vec<bool> = vec![false; n];
let mut is_param: Vec<bool> = vec![false; n];
for v in &vt.vars {
if v.is_param && (v.id as usize) < n {
is_param[v.id as usize] = true;
}
}
let mut is_catch: Vec<bool> = vec![false; n];
walk_all(body, &mut |st| {
if let Stmt::Try { catches, .. } = st {
for c in catches {
if (c.var as usize) < n {
is_catch[c.var as usize] = true;
}
}
}
});
walk_all(body, &mut |st| {
if let Stmt::LocalDef { var, .. } = st {
if (*var as usize) < n {
declared[*var as usize] = true;
}
}
});
let mut used: HashSet<u32> = HashSet::default();
stmt_collect_vars(body, &mut used, true);
let mut needs_decl: Vec<u32> = used
.into_iter()
.filter(|v| {
let i = *v as usize;
i >= n
|| ((!declared[i] && !is_param[i] && !is_catch[i]) || hoist.contains(v))
})
.collect();
needs_decl.sort_unstable();
needs_decl.dedup();
if !needs_decl.is_empty() {
let mut decls: Vec<Stmt> = needs_decl
.into_iter()
.map(|v| Stmt::LocalDef {
var: v,
init: None,
is_final: false,
force_type: true,
})
.collect();
decls.reverse();
match body {
Stmt::Block(v) => {
for d in decls {
v.insert(0, d);
}
}
other => {
let inner = std::mem::replace(other, Stmt::Block(vec![]));
*other = Stmt::Block({
let mut vs = decls;
vs.push(inner);
vs
});
}
}
}
}
fn demote_dup_decls(s: &mut Stmt, seen: &mut HashSet<u32>, vt: &VarTable) {
match s {
Stmt::Block(v) => {
for x in v.iter_mut() {
demote_dup_decls(x, seen, vt);
}
}
Stmt::LocalDef { var, init, .. } => {
if seen.contains(var) {
let ty = vt.var(*var).ty.clone();
if let Some(e) = init.take() {
*s = Stmt::ExprStmt(Expr::Assign {
target: Box::new(Expr::Local { var: *var, ty }),
op: AssignOp::Plain,
value: Box::new(e),
});
} else {
*s = Stmt::Block(vec![]);
}
} else {
seen.insert(*var);
}
}
other => {
walk_mut(other, &mut |x| demote_dup_decls(x, seen, vt));
}
}
}
#[allow(dead_code)]
fn unused(_: &Vec<CaseGroup>, _: &Catch) {}
pub fn apply_local_names(vt: &mut VarTable, body: &Stmt) {
use std::collections::{HashMap, HashSet};
let mut by_call: HashMap<u32, Vec<String>> = HashMap::new();
let mut by_intrinsics: HashMap<u32, String> = HashMap::new();
visit_all_exprs(body, &mut |e| {
if let Expr::Method { cls, name, args, .. } = e {
if (cls.as_ref() == "kotlin/jvm/internal/Intrinsics"
|| cls.as_ref() == "kotlin/jvm/internal/IntrinsicsKt")
&& matches!(name.as_ref(), "checkNotNullParameter" | "checkParameterIsNotNull")
&& args.len() == 2
{
if let (Expr::Local { var, .. }, Expr::Const(ConstVal::Str(s))) = (&args[0], &args[1])
{
by_intrinsics
.entry(*var)
.or_insert_with(|| sanitize_name(s).unwrap_or_default());
}
}
}
});
walk_all(body, &mut |st| {
let (var, value) = match st {
Stmt::LocalDef { var, init: Some(e), .. } => (*var, e),
Stmt::ExprStmt(Expr::Assign { target, value, .. })
if matches!(&**target, Expr::Local { .. }) =>
{
let Expr::Local { var, .. } = &**target else { return };
(*var, value.as_ref())
}
_ => return,
};
if let Some(n) = defining_call_name(value) {
by_call.entry(var).or_default().push(n);
}
});
let sanit = |n: &str| crate::classdec::java_ident(n).into_owned();
let mut taken: HashSet<String> = HashSet::default();
for v in vt.vars.iter() {
if v.synthetic_name {
taken.insert(sanit(&v.name));
}
}
for v in vt.vars.iter_mut() {
if v.synthetic_name {
continue;
}
if !taken.insert(sanit(&v.name)) {
let base = sanit(&v.name);
for i in 2.. {
let cand = format!("{base}{i}");
if taken.insert(cand.clone()) {
v.name = cand;
break;
}
}
}
}
let claim = |taken: &mut HashSet<String>, want: &str| -> String {
let w = crate::classdec::java_ident(want).into_owned();
if taken.insert(w.clone()) {
return w;
}
for i in 2.. {
let cand = format!("{w}{i}");
if taken.insert(cand.clone()) {
return cand;
}
}
w
};
for info in vt.vars.iter_mut() {
if !info.synthetic_name {
continue;
}
if let Some(n) = by_intrinsics.get(&info.id) {
if !n.is_empty() {
info.name = claim(&mut taken, n);
continue;
}
}
if let Some(names) = by_call.get(&info.id) {
let unique: HashSet<&String> = names.iter().collect();
if unique.len() == 1 {
let n = unique.into_iter().next().unwrap();
if !is_java_keyword(n) {
info.name = claim(&mut taken, n);
continue;
}
}
}
let want = type_alias(&info.ty).map(str::to_string).or_else(|| simple_type_name(&info.ty));
if let Some(n) = want {
info.name = claim(&mut taken, &n);
}
}
let mut seen: HashSet<String> = HashSet::default();
for v in vt.vars.iter_mut() {
if !seen.insert(sanit(&v.name)) {
let base = sanit(&v.name);
for i in 2.. {
let cand = format!("{base}{i}");
if seen.insert(cand.clone()) {
v.name = cand;
break;
}
}
}
}
}
fn simple_type_name(ty: &TypeRef) -> Option<String> {
let TypeRef::J(JavaType::Object(n)) = ty else {
return None;
};
let simple = n.rsplit('/').next().unwrap_or(n);
let simple = simple.rsplit('$').next().unwrap_or(simple);
let mut chars = simple.chars();
let mut out = String::with_capacity(simple.len());
let mut lowered = false;
for c in chars.by_ref() {
if c.is_ascii_alphabetic() && !lowered {
out.push(c.to_ascii_lowercase());
lowered = true;
} else {
out.push(c);
}
if lowered {
break;
}
}
out.extend(chars);
sanitize_name(&out)
}
fn defining_call_name(e: &Expr) -> Option<String> {
match e {
Expr::Method { name, .. } => {
let base = name
.strip_prefix("get")
.or_else(|| name.strip_prefix("is"))
.filter(|rest| rest.chars().next().is_some_and(|c| c.is_ascii_uppercase()))
.unwrap_or(name);
let first = base.chars().next()?;
let mut out = String::with_capacity(base.len());
out.push(first.to_ascii_lowercase());
out.extend(base.chars().skip(1));
sanitize_name(&out)
}
Expr::New { cls, .. } => {
let simple = cls.rsplit('/').next().unwrap_or(cls);
let simple = simple.rsplit('$').next().unwrap_or(simple);
let first = simple.chars().next()?;
let mut out = String::with_capacity(simple.len());
out.push(first.to_ascii_lowercase());
out.extend(simple.chars().skip(1));
sanitize_name(&out)
}
_ => None,
}
}
fn sanitize_name(s: &str) -> Option<String> {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
out.push(c);
} else {
out.push('_');
}
}
if out.len() < 2
|| out.chars().next().is_some_and(|c| c.is_ascii_digit())
|| is_java_keyword(&out)
{
return None;
}
Some(out)
}
fn type_alias(ty: &TypeRef) -> Option<&'static str> {
let TypeRef::J(JavaType::Object(n)) = ty else {
return None;
};
Some(match n.as_ref() {
"java/lang/String" | "kotlin/String" => "str",
"java/lang/Class" => "cls",
"java/lang/Throwable" => "th",
"java/lang/Object" | "kotlin/Any" => "obj",
"java/util/Iterator" | "kotlin/collections/Iterator" => "it",
"java/lang/Boolean" => "bool",
"java/lang/Integer" => "num",
"java/lang/Character" => "ch",
"java/lang/Byte" => "b",
"java/lang/Short" => "sh",
"java/lang/Float" => "f",
"java/lang/Double" => "d",
"java/lang/Long" => "i",
"java/lang/StringBuilder" => "sb",
"java/util/ArrayList" => "list",
"java/util/HashMap" => "map",
"android/content/Context" => "context",
"android/content/Intent" => "intent",
"android/os/Bundle" => "bundle",
"android/view/View" => "view",
"android/graphics/Bitmap" => "bitmap",
"android/view/ViewGroup" => "viewGroup",
_ => return None,
})
}
pub(crate) fn visit_all_exprs<F: FnMut(&Expr)>(s: &Stmt, f: &mut F) {
walk_all(s, &mut |st| {
let exprs: Vec<&Expr> = match st {
Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
let mut v: Vec<&Expr> = vec![value];
if !matches!(&**target, Expr::Local { .. }) {
v.push(target);
}
v
}
Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
vec![e]
}
Stmt::Return(Some(e)) => vec![e],
Stmt::LocalDef { init: Some(e), .. } => vec![e],
Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
vec![cond]
}
_ => Vec::new(),
};
for e in exprs {
visit_exprs(e, f);
}
});
}
pub fn platform_constants(body: &mut Stmt) {
if !crate::platform::installed() {
return;
}
rewrite_exprs(body, &mut |e| {
if let Expr::Method { cls, name, desc, args, .. } = e {
for a in args.iter_mut() {
let (value, is_long) = match a {
Expr::Const(ConstVal::Int(v)) => (*v as i64, false),
Expr::Const(ConstVal::Long(v)) => (*v, true),
_ => continue,
};
if let Some(named) =
crate::platform::named_constant(cls, name, &desc_to_string(desc), 0, value)
{
let _ = is_long;
*a = Expr::Raw(named);
}
}
}
});
}
fn desc_to_string(d: &jdc_core::types::MethodDescriptor) -> String {
let mut out = String::from("(");
for a in &d.args {
type_to_desc(a, &mut out);
}
out.push(')');
type_to_desc(&d.ret, &mut out);
out
}
fn type_to_desc(t: &jdc_core::types::JavaType, out: &mut String) {
match t {
JavaType::Void => out.push('V'),
JavaType::Boolean => out.push('Z'),
JavaType::Byte => out.push('B'),
JavaType::Char => out.push('C'),
JavaType::Short => out.push('S'),
JavaType::Int => out.push('I'),
JavaType::Float => out.push('F'),
JavaType::Long => out.push('J'),
JavaType::Double => out.push('D'),
JavaType::Object(n) => {
out.push('L');
out.push_str(n);
out.push(';');
}
JavaType::Array(inner) => {
out.push('[');
type_to_desc(inner, out);
}
}
}
pub fn remove_kotlin_checks(s: &mut Stmt) {
walk_mut_deep(s, &mut |st| {
let Stmt::Block(v) = st else { return };
v.retain(|x| !is_kotlin_check(x));
});
}
fn is_kotlin_check(s: &Stmt) -> bool {
let Stmt::ExprStmt(e) = s else { return false };
let Expr::Method { cls, name, .. } = e else { return false };
(cls.as_ref() == "kotlin/jvm/internal/Intrinsics" || cls.as_ref() == "kotlin/jvm/internal/IntrinsicsKt__Jdk7Kt")
&& matches!(
name.as_ref(),
"checkNotNullParameter"
| "checkParameterIsNotNull"
| "checkExpressionValueIsNotNull"
| "checkNotNullExpressionValue"
| "checkReturnedValueIsNotNull"
| "checkFieldIsNotNull"
| "checkNotNull"
)
}
fn inline_ref_ok(pool: &DexPool, host: &str, target: &str, member: Option<(&str, bool)>) -> bool {
let hpkg = match host.rfind('/') {
Some(i) => &host[..i],
None => "",
};
let mut cur = target;
loop {
let tpkg = match cur.rfind('/') {
Some(i) => &cur[..i],
None => "",
};
if tpkg != hpkg {
if let Some(pc) = pool.get(cur) {
if pc.access & crate::access::ACC_PUBLIC == 0 {
return false;
}
if cur == target {
if let Some((mname, is_method)) = member {
let ok = if is_method {
pc.all_methods().any(|m| {
&*m.name == mname
&& m.access & crate::access::ACC_PUBLIC != 0
})
} else {
pc.static_fields
.iter()
.chain(pc.instance_fields.iter())
.any(|f| {
f.name.as_ref() == mname
&& f.access & crate::access::ACC_PUBLIC != 0
})
};
if !ok
&& (if is_method {
pc.all_methods().any(|m| &*m.name == mname)
} else {
pc.static_fields
.iter()
.chain(pc.instance_fields.iter())
.any(|f| f.name.as_ref() == mname)
})
{
return false;
}
}
}
}
}
match cur.rfind('$') {
Some(i) if i > 0 => cur = &cur[..i],
_ => break,
}
}
true
}
pub fn inline_accessors(s: &mut Stmt, pool: &DexPool, host: &str) {
rewrite_exprs(s, &mut |e| {
let Expr::Method { cls, name, desc, args, is_static, .. } = e else { return };
if !*is_static || args.is_empty() {
return;
}
let Some(target) = pool.get(cls) else { return };
let desc_s = desc_to_string(desc);
let Some(m) = target.find_method(name, &desc_s) else { return };
if !(m.is_static() && m.access & access::ACC_SYNTHETIC != 0) {
return;
}
let Some(dex) = pool.dex(m.dex_idx) else { return };
let code = match pool.accessor_code(m.dex_idx, m.code_off) {
Some(bytes) => ddc_dex::CodeItem::parse(&bytes, 0),
None => dex.code_at(m.code_off),
};
let Some(code) = code else { return };
let insns: Vec<&Insn> =
code.insns.iter().filter(|i| !matches!(i.kind, InsnKind::Nop)).collect();
let core: Vec<&Insn> = insns
.iter()
.copied()
.filter(|i| !is_const_only_trace(&i.kind, &insns))
.collect();
match accessor_shape(&dex, &core, &code) {
Some(Shape::Identity(arg_i)) => {
if let Some(a) = args.get(arg_i) {
*e = a.clone();
}
}
Some(Shape::FieldRead { arg, cls: field_cls, field, field_ty }) => {
if !inline_ref_ok(pool, host, &field_cls, Some((&field, false))) {
return;
}
if let Some(owner) = args.get(arg).cloned().map(Box::new) {
let ty = TypeRef::J(desc_type(&field_ty));
let name = jdc_core::rename::field_display(
&field_cls,
&field,
&field_ty,
)
.map(std::sync::Arc::from)
.unwrap_or_else(|| field.into());
*e = Expr::Field {
owner: Some(owner),
cls: field_cls.into(),
name,
ty,
is_static: false,
};
}
}
Some(Shape::Forward { cls: tcls, name: tname, desc: tdesc, instance }) => {
if !inline_ref_ok(pool, host, &tcls, Some((&tname, true))) {
return;
}
let disp = jdc_core::rename::field_display(
&tcls,
&tname,
&tdesc.to_string(),
)
.map(std::sync::Arc::from)
.unwrap_or_else(|| tname.into());
*cls = tcls.into();
*name = disp;
*desc = std::sync::Arc::new(tdesc);
if instance && !args.is_empty() {
let recv = args.remove(0);
if let Expr::Method { owner, .. } = e {
*owner = Some(Box::new(recv));
}
}
}
None => {}
}
});
}
enum Shape {
Identity(usize),
FieldRead { arg: usize, cls: String, field: String, field_ty: String },
Forward { cls: String, name: String, desc: MethodDescriptor, instance: bool },
}
fn field_of(dex: &ddc_dex::DexFile, field_idx: u32) -> Option<(String, String, String)> {
let f = dex.field(field_idx);
Some((
dex.class_name(f.class_idx),
dex.string(f.name_idx).to_string(),
dex.type_name(f.type_idx).to_string(),
))
}
fn param_index(code: &ddc_dex::CodeItem, reg: u16) -> Option<usize> {
let rs = code.registers_size as usize;
let ins = code.ins_size as usize;
let r = reg as usize;
let first = rs.checked_sub(ins)?;
(r >= first && r < rs).then(|| r - first)
}
fn accessor_shape(
dex: &ddc_dex::DexFile,
core: &[&Insn],
code: &ddc_dex::CodeItem,
) -> Option<Shape> {
let reads = |rest: &[&Insn], r: u16| {
rest.iter()
.any(|i| crate::lift::src_regs(&i.kind).contains(&r))
};
let core: &[&Insn] = match core.split_first() {
Some((first, rest))
if matches!(first.kind, InsnKind::Const { dst, .. } if !reads(rest, dst)) =>
{
rest
}
_ => core,
};
let core: &[&Insn] = match core.split_last() {
Some((last, rest))
if matches!(last.kind, InsnKind::Const { dst, .. } if !reads(rest, dst)) =>
{
rest
}
_ => core,
};
match core {
[Insn { kind: InsnKind::Return { src }, .. }] => {
let idx = param_index(code, *src)?;
Some(Shape::Identity(idx))
}
[
Insn { kind: InsnKind::IGet { dst, obj, field_idx }, .. },
Insn { kind: InsnKind::Return { src }, .. },
] if dst == src => {
let arg = param_index(code, *obj)?;
let (cls, field, field_ty) = field_of(dex, *field_idx)?;
Some(Shape::FieldRead { arg, cls, field, field_ty })
}
[Insn { kind: InsnKind::Invoke { method_idx, regs, kind, .. }, .. }, rest @ ..] if rest.len() <= 2 => {
if !regs.iter().all(|r| param_index(code, *r).is_some()) || regs.is_empty() {
return None;
}
if rest.len() == 2 {
let ok = matches!(rest[0].kind, InsnKind::MoveResult { .. })
&& matches!(rest[1].kind, InsnKind::Return { .. } | InsnKind::ReturnVoid);
if !ok {
return None;
}
}
let mid = dex.method(*method_idx);
let cls = dex.class_name(mid.class_idx);
let name = dex.string(mid.name_idx).to_string();
let mut s = String::from("(");
for t in dex.proto_params(mid.proto_idx) {
s.push_str(dex.type_name(*t));
}
s.push(')');
s.push_str(dex.type_name(dex.proto(mid.proto_idx).return_type_idx));
let desc = parse_method_descriptor(&s)?;
Some(Shape::Forward { cls, name, desc, instance: !matches!(kind, InvokeKind::Static) })
}
_ => None,
}
}
fn is_const_only_trace(kind: &InsnKind, insns: &[&Insn]) -> bool {
let InsnKind::Invoke { kind, regs, .. } = kind else { return false };
if !matches!(kind, InvokeKind::Static) || regs.is_empty() {
return false;
}
regs.iter().all(|r| const_loaded(*r, insns, 0))
}
fn const_loaded(r: u16, insns: &[&Insn], depth: u8) -> bool {
if depth > 3 {
return false;
}
insns.iter().any(|i| match &i.kind {
InsnKind::Const { dst, .. } => *dst == r,
InsnKind::Move { dst, src } => *dst == r && const_loaded(*src, insns, depth + 1),
_ => false,
})
}