1use jdc_core::FxHashMap as HashMap;
6
7use ddc_dex::insn::InsnKind;
8use jdc_core::convert::Converter;
9use jdc_core::ir::build::BlockResult;
10use jdc_core::ir::expr::{Expr, TypeRef};
11use jdc_core::ir::stmt::Stmt;
12use jdc_core::structure::{reverse_postorder, Structurer};
13use jdc_core::types::{JavaType, MethodDescriptor};
14use jdc_core::var::VarTable;
15
16use crate::cfg::DexCfg;
17use crate::lift::{entry_regs, Lifter, MethodEnv, OutState, Reg};
18use crate::passes;
19use crate::{DexPool, PoolClass, PoolMethod};
20
21pub struct MethodBody {
22 pub body: Stmt,
23 pub vt: VarTable,
24 pub desc: MethodDescriptor,
25}
26
27fn trace_on() -> bool {
32 static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
33 *T.get_or_init(|| std::env::var("DDC_TRACE").is_ok())
34}
35
36fn walk_budget_override() -> Option<u64> {
37 static T: std::sync::OnceLock<Option<u64>> = std::sync::OnceLock::new();
38 *T.get_or_init(|| {
39 std::env::var("DDC_WALKBUDGET")
40 .ok()
41 .and_then(|v| v.parse().ok())
42 })
43}
44
45fn walk_work_override() -> Option<u64> {
46 static T: std::sync::OnceLock<Option<u64>> = std::sync::OnceLock::new();
47 *T.get_or_init(|| {
48 std::env::var("DDC_WALKWORK")
49 .ok()
50 .and_then(|v| v.parse().ok())
51 })
52}
53
54fn handler_types_for(cfg: &DexCfg, bid: usize) -> Vec<Option<std::sync::Arc<str>>> {
55 cfg.blocks[bid]
56 .handlers
57 .iter()
58 .filter_map(|&ri| cfg.exc_ranges.get(ri).map(|r| r.catch_type.clone()))
59 .collect()
60}
61
62pub fn decompile_method(
65 pool: &DexPool,
66 class: &PoolClass,
67 m: &PoolMethod,
68) -> Result<Option<MethodBody>, String> {
69 let t0 = std::time::Instant::now();
70 if m.is_abstract_or_native() {
71 return Ok(None);
72 }
73 let desc = m
74 .parsed_desc()
75 .ok_or_else(|| format!("bad descriptor {}", m.desc))?;
76 let dex = pool.dex(m.dex_idx).ok_or("missing dex image")?;
77 let dex = &*dex;
78 let Some(mut code) = dex.code_at(m.code_off) else {
79 return Ok(None);
80 };
81 let insn_count = code.insns.len();
82 let param_names = dex.parameter_names(m.debug_info_off);
83 let cfg = DexCfg::build(&mut code, &|ty| pool.type_name_arc(m.dex_idx, ty));
86 let method_final_reads = crate::lift::compute_final_reads(&cfg.insns);
91 let n = cfg.blocks.len();
92 let env = MethodEnv {
93 pool,
94 di: m.dex_idx,
95 dex,
96 code: &code,
97 class_name: class.name.clone(),
98 method_name: m.name.clone(),
99 desc: desc.clone(),
100 debug_locals: dex.debug_locals(m.debug_info_off),
101 is_static: m.is_static(),
102 code_units: cfg.code_units,
103 };
104 let mut vt = VarTable::default();
105 let entry = entry_regs(&mut vt, &env, ¶m_names);
106 if n == 0 {
107 return Ok(None);
108 }
109 if insn_count > 30_000 || n > 8_000 {
112 return Ok(Some(stub_body(
113 format!(
114 "$DDC: method too large to decompile ({} insns, {} blocks)",
115 insn_count, n
116 ),
117 desc,
118 )));
119 }
120
121 let core_for_order = cfg.to_core();
124 let universe: jdc_core::FxHashSet<usize> = (0..n).collect();
125 let mut order = reverse_postorder(&core_for_order, core_for_order.entry, &universe);
126 {
127 let mut seen: jdc_core::FxHashSet<usize> = order.iter().copied().collect();
128 for b in &cfg.blocks {
129 if seen.insert(b.id) {
130 order.push(b.id);
131 }
132 }
133 }
134
135 let mut handler_entry_block: HashMap<usize, usize> = HashMap::default();
138 for b in &cfg.blocks {
139 if b.handlers.is_empty() {
140 continue;
141 }
142 let mut best: Option<(u32, usize)> = None;
143 for &ri in &b.handlers {
144 if let Some(r) = cfg.exc_ranges.get(ri) {
145 if best.map(|(s, _)| r.start < s).unwrap_or(true) {
146 if let Some(eb) = cfg.block_at(r.start) {
147 best = Some((r.start, eb));
148 }
149 }
150 }
151 }
152 if let Some((_, eb)) = best {
153 handler_entry_block.insert(b.id, eb);
154 }
155 }
156
157 let mut results: Vec<BlockResult> = Vec::with_capacity(n);
159 results.resize_with(n, || BlockResult {
160 stmts: vec![],
161 out_stack: vec![],
162 term: jdc_core::ir::build::Term::Return(None),
163 });
164 let mut out_states: Vec<Option<OutState>> = vec![None; n];
165 let mut stable_vars: HashMap<(usize, u16, u64), u32> = HashMap::default();
169 let mut mflags = crate::lift::MethodFlags::none();
173 let mut try_entry_snapshots: HashMap<usize, Vec<Reg>> = HashMap::default();
177 let mut built_once = vec![false; n];
178 let mut errors: HashMap<usize, String> = HashMap::default();
179 let mut phis: HashMap<(usize, u16), u32> = HashMap::default();
181 let entry_is_handler = handler_entry_block.contains_key(&0);
184
185 let try_entry_flags: Vec<bool> = (0..n)
190 .map(|b| {
191 !cfg.blocks[b].handlers.is_empty()
192 || cfg
193 .exc_ranges
194 .iter()
195 .any(|r| cfg.block_at(r.start) == Some(b))
196 })
197 .collect();
198 let handler_types: Vec<Vec<Option<std::sync::Arc<str>>>> =
199 (0..n).map(|b| handler_types_for(&cfg, b)).collect();
200
201 let mut ver: Vec<u64> = vec![0; n]; let mut built_ver: Vec<u64> = vec![u64::MAX; n]; fn input_sig(cfg: &DexCfg, bid: usize, ver: &[u64]) -> u64 {
210 let mut h: u64 = 1;
211 for &p in &cfg.blocks[bid].pred {
212 h = h
213 .wrapping_mul(31)
214 .wrapping_add(p as u64)
215 .wrapping_mul(31)
216 .wrapping_add(ver[p]);
217 }
218 h
220 }
221 let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
222 let mut queued: Vec<bool> = vec![false; n];
223 for &bid in &order {
224 queue.push_back(bid);
225 queued[bid] = true;
226 }
227 let mut appends: HashMap<usize, Vec<(usize, u32, u32, Expr)>> = HashMap::default();
230 let mut total_visits: usize = 0;
231 let visit_cap: usize = 64 * n + 256;
232
233 while let Some(bid) = queue.pop_front() {
234 queued[bid] = false;
235 total_visits += 1;
236 if total_visits > visit_cap {
237 CAP_HITS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
238 CAP_INSNS.fetch_add(
239 insn_count as u64 * 1000 + n as u64,
240 std::sync::atomic::Ordering::Relaxed,
241 );
242 break; }
244 let is_handler = handler_entry_block.contains_key(&bid);
245 let sig = input_sig(&cfg, bid, &ver)
249 ^ (if is_handler {
250 let eb = handler_entry_block[&bid];
251 (eb as u64) << 32
252 } else {
253 0
254 });
255 let need_build = !built_once[bid] || built_ver[bid] != sig;
256 if !need_build {
257 continue;
261 }
262 let ins: Vec<Reg> = if bid == cfg.entry {
264 if cfg.blocks[0].pred.is_empty() || entry_is_handler {
265 entry.clone()
266 } else {
267 let mut sides: Vec<&[Reg]> = vec![entry.as_slice()];
271 for p in cfg.blocks[bid].pred.clone() {
272 if let Some(o) = &out_states[p] {
273 sides.push(o.regs.as_slice());
274 }
275 }
276 let merged =
277 merge_states(&mut vt, &mut phis, bid, &sides, &env.debug_locals, cfg.blocks[bid].start);
278 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
283 for (r, st) in entry.iter().enumerate() {
284 if let Some(&phi) = phis.get(&(bid, r as u16)) {
285 let value = match st {
286 Reg::Live(v) if *v == phi => continue,
287 Reg::Live(v) => Expr::Local {
288 var: *v,
289 ty: vt.var(*v).ty.clone(),
290 },
291 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
292 Reg::Undef | Reg::WideHi => continue,
293 };
294 recs.push((usize::MAX, 0, phi, value));
295 }
296 }
297 appends.insert(bid, recs);
298 merged
299 }
300 } else if is_handler {
301 let eb = handler_entry_block[&bid];
302 try_entry_snapshots
303 .get(&eb)
304 .cloned()
305 .unwrap_or_else(|| vec![Reg::Undef; env.code.registers_size as usize])
306 } else {
307 let mut sides: Vec<&[Reg]> = Vec::new();
310 for p in cfg.blocks[bid].pred.clone() {
311 if let Some(o) = &out_states[p] {
312 sides.push(o.regs.as_slice());
313 }
314 }
315 if sides.is_empty() {
316 vec![Reg::Undef; env.code.registers_size as usize]
317 } else if sides.len() == 1 {
318 sides[0].to_vec()
319 } else {
320 merge_states(
321 &mut vt,
322 &mut phis,
323 bid,
324 &sides,
325 &env.debug_locals,
326 cfg.blocks[bid].start,
327 )
328 }
329 };
330
331 if phis_for_merge(bid, &phis) {
335 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
336 record_appends(&cfg, bid, &out_states, &phis, &vt, &mut recs);
337 if bid == cfg.entry {
340 if let Some(prev) = appends.get(&bid) {
341 for (p, pc, v, e) in prev.iter() {
342 if *p == usize::MAX {
343 recs.push((*p, *pc, *v, e.clone()));
344 }
345 }
346 }
347 }
348 appends.insert(bid, recs);
349 }
350
351 built_once[bid] = true;
355 built_ver[bid] = sig;
356 if try_entry_flags[bid] {
357 try_entry_snapshots.insert(bid, ins.clone());
358 }
359 BUILD_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
360 INSN_LIFTED.fetch_add(
361 cfg.block_ins(&cfg.blocks[bid]).len() as u64,
362 std::sync::atomic::Ordering::Relaxed,
363 );
364 let htypes = &handler_types[bid];
365 let block_ins = cfg.block_ins(&cfg.blocks[bid]);
366 let lifter = Lifter::new(&env, &mut vt, ins, bid, &mut stable_vars, &mut mflags);
367 let rebuilt = match lifter.build_block(block_ins, htypes, method_final_reads.clone()) {
368 Ok((r, out)) => {
369 results[bid] = r;
370 let changed = match &out_states[bid] {
371 Some(prev) => prev.regs != out.regs,
372 None => true,
373 };
374 out_states[bid] = Some(out);
375 changed
376 }
377 Err(msg) => {
378 results[bid] = BlockResult {
379 stmts: vec![Stmt::Comment(format!("$DDC-BLOCK-ERROR: {msg}"))],
380 out_stack: vec![],
381 term: jdc_core::ir::build::Term::Return(None),
382 };
383 out_states[bid] = Some(OutState {
384 regs: vec![Reg::Undef; env.code.registers_size as usize],
385 write_pc: vec![0; env.code.registers_size as usize],
386 });
387 errors.insert(bid, msg);
388 false
389 }
390 };
391 if rebuilt {
392 ver[bid] += 1;
393 for &succ in &cfg.blocks[bid].succ {
395 if !queued[succ] && succ < n {
396 queue.push_back(succ);
397 queued[succ] = true;
398 }
399 }
400 if is_handler {
404 let eb = handler_entry_block[&bid];
405 if !queued[eb] && eb < n {
406 queue.push_back(eb);
407 queued[eb] = true;
408 }
409 }
410 for &ri in &cfg.blocks[bid].handlers {
413 if let Some(r) = cfg.exc_ranges.get(ri) {
414 if let Some(hb) = cfg.block_at(r.handler) {
415 if !queued[hb] && hb < n {
416 queue.push_back(hb);
417 queued[hb] = true;
418 }
419 }
420 }
421 }
422 }
423 }
424
425 let mut entry_phi_inits: Vec<(u32, Expr)> = Vec::new();
434 if let Some(recs) = appends.get_mut(&cfg.entry) {
435 let mut entry_side: Vec<(u32, Expr)> = Vec::new();
436 recs.retain(|(p, _, v, e)| {
437 if *p == usize::MAX {
438 entry_side.push((*v, e.clone()));
439 false
440 } else {
441 true
442 }
443 });
444 entry_side.sort_by_key(|(v, _)| *v);
445 entry_phi_inits = entry_side;
446 }
447
448 let mut merge_order: Vec<usize> = appends.keys().copied().collect();
462 merge_order.sort_unstable();
463 let mut per_pred: HashMap<usize, Vec<(u32, u32, Expr)>> = HashMap::default();
464 for bid in merge_order {
465 for (p, pc, v, e) in &appends[&bid] {
466 per_pred.entry(*p).or_default().push((*pc, *v, e.clone()));
467 }
468 }
469 let mut pred_order: Vec<usize> = per_pred.keys().copied().collect();
470 pred_order.sort_unstable();
471 for p in pred_order {
472 let mut adds = per_pred.remove(&p).unwrap();
473 if p == usize::MAX {
474 continue;
475 }
476 adds.sort_by_key(|(pc, _, _)| *pc);
477 let term_is_exit = matches!(
478 results[p].term,
479 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
480 );
481 if term_is_exit {
482 continue;
483 }
484 let phi_set: std::collections::HashSet<u32> = adds.iter().map(|(_, v, _)| *v).collect();
485 let mut snapshots: Vec<Stmt> = Vec::new();
486 let mut emitted: Vec<(u32, Expr)> = Vec::new();
488 for (_, v, e) in &adds {
489 let mut e = e.clone();
490 let mut refs_phi = false;
491 visit_phi_refs(&e, &phi_set, &mut refs_phi);
492 if refs_phi {
493 let ty = e.type_ref();
495 let id = vt.vars.len() as u32;
496 let name = format!("v{}", id);
497 vt.vars.push(jdc_core::var::VarInfo {
498 id,
499 slot: u16::MAX,
500 name,
501 ty: ty.clone(),
502 is_param: false,
503 range_start: 0,
504 range_end: u16::MAX,
505 synthetic_name: true,
506 });
507 snapshots.push(Stmt::LocalDef {
508 var: id,
509 init: Some(e),
510 is_final: false,
511 force_type: true,
512 });
513 e = Expr::Local { var: id, ty };
514 }
515 emitted.push((*v, e));
516 }
517 for st in snapshots {
518 results[p].stmts.push(st);
519 }
520 for (v, e) in emitted {
521 results[p].stmts.push(Stmt::ExprStmt(Expr::Assign {
522 target: Box::new(Expr::Local {
523 var: v,
524 ty: vt.var(v).ty.clone(),
525 }),
526 op: jdc_core::ir::expr::AssignOp::Plain,
527 value: Box::new(e),
528 }));
529 }
530 }
531
532 let t_fix = t0;
533 phase_hit_n(0, n, t_fix);
534 if n == 1 && !entry_is_handler && cfg.blocks[0].pred.is_empty() {
540 let r = std::mem::replace(
541 &mut results[0],
542 BlockResult {
543 stmts: vec![],
544 out_stack: vec![],
545 term: jdc_core::ir::build::Term::Goto,
546 },
547 );
548 let mut body = r.stmts;
549 match r.term {
550 jdc_core::ir::build::Term::Return(Some(e)) => {
551 body.push(Stmt::Return(Some(e)));
552 }
553 jdc_core::ir::build::Term::Return(None) => {}
554 jdc_core::ir::build::Term::Throw(e) => body.push(Stmt::Throw(e)),
555 _ => {}
556 }
557 let mut body = Stmt::Block(body);
558 passes::fused_expr_rewrites(&mut body, &vt);
559 if mflags.has_sb() {
560 passes::fold_string_builders(&mut body, &vt);
561 }
562 passes::forward_single_use(&mut body, &vt);
563 passes::cleanup(&mut body);
564 passes::inline_accessors(&mut body, pool, &class.name);
565 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
566 passes::fix_ref_null_assigns(&vt, &mut body);
567 passes::fix_null_sentinels(&mut body, &vt, &desc.ret);
568 passes::split_generations(&mut vt, &mut body, pool);
569 passes::insert_object_narrowing_casts(&vt, &mut body, pool, &desc.ret);
570 passes::fix_field_owner_downcasts(&mut body, &vt, pool);
571 passes::fix_incomparable_equality(&mut body, &vt, pool);
572 passes::fix_primitive_assign_casts(&vt, &mut body, &desc.ret);
573 passes::fix_primitive_arg_bridges(&mut body, &vt, pool);
574 passes::fix_bool_xor(&mut body, &vt, matches!(desc.ret, JavaType::Boolean));
575 passes::fix_int_operand_bridges(&mut body, &vt);
576 passes::fix_ref_array_null_consts(&mut body);
577 passes::idiom_compounds(&mut body, &vt);
578 passes::rescue_primitive_receivers(&mut body, &vt, pool);
579 passes::fix_this0_owners(&mut body, &class.name);
580 passes::apply_local_names(&mut vt, &body);
581 passes::deshadow_locals(&mut vt, pool, &class.name, &body);
582 passes::remove_kotlin_checks(&mut body);
583 passes::rewrite_kotlin_facades(&mut body, pool);
584 passes::platform_constants(&mut body);
585 passes::drop_dead_locals(&mut body);
586 passes::drop_dead_raw_news(&mut body);
587 passes::mark_field_owner_concrete(&mut vt, &mut body);
588 passes::ensure_declared(&mut body, &vt);
589 passes::rescue_arg_return_swaps(&mut body, &vt, pool, &desc.ret);
590 if &*m.name == "<init>" {
594 passes::fold_bool_value_diamonds(&mut body, &vt);
602 passes::fold_value_diamonds(&mut body, &vt);
603 passes::fix_ctor_this_aliases(&mut body, &vt);
604 if class.is_enum() {
605 passes::fold_enum_default_arg_bridge(&mut body);
606 let const_subclass = class
616 .super_name
617 .as_ref()
618 .map(|s| s != "java/lang/Object" && s != "java/lang/Enum")
619 .unwrap_or(false);
620 if !const_subclass {
621 passes::strip_enum_ctor_super(&mut body);
622 }
623 } else {
624 passes::fix_ctor_delegation_arg_defs(&mut body);
625 passes::fix_ctor_conditional_super(&mut body);
626 passes::fix_ctor_super_first(&mut body, &vt);
627 passes::dedupe_ctor_delegations(&mut body);
628 passes::strip_secondary_delegations(&mut body);
629 passes::hoist_branch_delegations(&mut body, &vt);
630 passes::extract_branched_delegation_helper(&mut body, &vt, pool, class, &desc.to_string());
631 }
632 passes::drop_dead_locals(&mut body);
636 }
637 passes::strip_trailing_void_return(&mut body);
638 passes::cleanup(&mut body);
639 passes::invert_empty_thens(&mut body);
640 passes::fold_short_circuits(&mut body);
641 passes::resolve_dangling_gotos(&mut body);
642 passes::dedupe_multicatch(&mut body, pool);
643 if !errors.is_empty() {
644 passes::prepend_comment(
645 &mut body,
646 format!("$DDC: {} block(s) failed to decompile", errors.len()),
647 );
648 }
649 phase_hit_n(2, n, t_fix);
650 record_bucket(n, t0);
651 return Ok(Some(MethodBody { body, vt, desc }));
652 }
653
654 const TREE_GUARD: usize = 20_000;
659 let core_cfg = cfg.to_core();
660 #[allow(unused_assignments)]
661 let mut body: Option<Stmt> = None;
662 let mut prev_size: Option<usize> = None;
663 let mut budget: u32 = if n > 100 {
668 64
669 } else if n > 20 {
670 128
671 } else {
672 512
673 };
674 let groups = jdc_core::structure::group_exceptions_with(&core_cfg, Some(&results));
680 let dom_universe: jdc_core::FxHashSet<usize> = (0..core_cfg.blocks.len()).collect();
681 let dom = jdc_core::structure::compute_dominators(&core_cfg, &dom_universe, core_cfg.entry);
682 if trace_on() {
683 eprintln!("[mshape] {}.{} blocks={}", class.name, m.name, n);
684 }
685 if n >= 2_100 {
696 return Ok(Some(stub_body(
697 format!(
698 "$DDC: method too large ({} blocks) — body elided for determinism",
699 n
700 ),
701 desc,
702 )));
703 }
704 #[cfg(feature = "visit-stats")]
705 {
706 jdc_core::structure::reset_visit_stats();
707 }
708 let walk_budget: u64 = walk_budget_override().unwrap_or(8 * n as u64 + 128);
718 let walk_work_budget: u64 = walk_work_override()
728 .unwrap_or(600_000 + 128 * n as u64);
729 loop {
736 jdc_core::structure::set_budget_override(Some(budget));
737 jdc_core::structure::set_walk_visit_budget(Some(walk_budget));
738 jdc_core::structure::set_walk_work_budget(Some(walk_work_budget));
739 let mut st = Structurer::with_shared_groups(
740 &core_cfg,
741 &results,
742 &groups,
743 Default::default(),
744 Default::default(),
745 );
746 let tw0 = std::time::Instant::now();
747 let region = st.structure_method();
748 let tw1 = std::time::Instant::now();
749 let mut converter = Converter::with_precomputed_ref(&core_cfg, &results, &groups, &dom);
750 let candidate = converter.convert(region);
751 let tw2 = std::time::Instant::now();
752 if trace_on() {
753 eprintln!(
754 "[rung] {}.{} budget={} walk={:?} convert={:?}",
755 class.name, m.name, budget, tw1 - tw0, tw2 - tw1
756 );
757 }
758 jdc_core::structure::set_budget_override(None);
759 jdc_core::structure::set_walk_visit_budget(None);
760 jdc_core::structure::set_walk_work_budget(None);
761 let size = {
762 let mut c = 0usize;
763 passes::count_stmts_deep(&candidate, &mut c);
764 c
765 };
766 if trace_on() {
767 eprintln!("[guard] {}.{} size={} budget={}", class.name, m.name, size, budget);
768 }
769 if size <= TREE_GUARD {
770 body = Some(candidate);
771 break;
772 }
773 if budget <= 64 && size > 3 * TREE_GUARD {
781 return Ok(Some(stub_body(
782 format!(
783 "$DDC: statement tree exploded ({} nodes) — pathological CFG, body elided",
784 size
785 ),
786 desc,
787 )));
788 }
789 if budget <= 32 && prev_size.is_some_and(|p| size >= p) {
795 return Ok(Some(stub_body(
796 format!(
797 "$DDC: statement tree exploded ({} nodes) — pathological CFG, body elided",
798 size
799 ),
800 desc,
801 )));
802 }
803 prev_size = Some(size);
804 if budget <= 16 {
805 return Ok(Some(stub_body(
806 format!(
807 "$DDC: statement tree exploded ({} nodes) even at copy budget 16",
808 size
809 ),
810 desc,
811 )));
812 }
813 budget /= 2;
814 }
815 #[cfg(feature = "visit-stats")]
816 if trace_on() && n > 3 {
817 let used = jdc_core::structure::walk_visits_consumed();
818 eprintln!(
819 "[visits] {}.{} blocks={} consumed={} ratio={:.1}",
820 class.name,
821 m.name,
822 n,
823 used,
824 used as f64 / n as f64
825 );
826 }
827 phase_hit_n(1, n, t_fix);
828 let mut body = body.unwrap();
829 if !entry_phi_inits.is_empty() {
830 let head: Vec<Stmt> = entry_phi_inits
831 .into_iter()
832 .map(|(v, e)| Stmt::LocalDef {
833 var: v,
834 init: Some(e),
835 is_final: false,
836 force_type: true,
837 })
838 .collect();
839 body = match body {
840 Stmt::Block(v) => {
841 let mut nv = head;
842 nv.extend(v);
843 Stmt::Block(nv)
844 }
845 other => {
846 let mut nv = head;
847 nv.push(other);
848 Stmt::Block(nv)
849 }
850 };
851 }
852
853 passes::bind_catches(&mut body, &mut vt);
854 passes::prune_unreachable(&mut body);
855 passes::fused_expr_rewrites(&mut body, &vt);
858 if mflags.has_sb() {
859 passes::fold_string_builders(&mut body, &vt);
860 }
861 passes::ternary_fold(&mut body);
862 if mflags.has_monitor() {
863 passes::fold_synchronized(&mut body);
864 }
865 passes::forward_single_use(&mut body, &vt);
866 passes::cleanup(&mut body);
867 passes::inline_accessors(&mut body, pool, &class.name);
868 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
869 passes::fix_ref_null_assigns(&vt, &mut body);
870 passes::fix_null_sentinels(&mut body, &vt, &desc.ret);
871 passes::split_generations(&mut vt, &mut body, pool);
872 {
885 let rb = matches!(desc.ret, JavaType::Boolean);
886 let mut rounds = 0;
887 loop {
888 let c = passes::booleanize(&mut vt, &mut body, rb);
889 rounds += 1;
890 if c == 0 || rounds >= 3 {
891 break;
892 }
893 passes::split_generations(&mut vt, &mut body, pool);
894 }
895 }
896 if matches!(
897 desc.ret,
898 JavaType::Int | JavaType::Long | JavaType::Short | JavaType::Byte | JavaType::Float | JavaType::Double
899 ) {
900 passes::fix_int_returns(&vt, &mut body);
901 }
902 if matches!(desc.ret, JavaType::Boolean) {
903 passes::fix_bool_returns(&vt, &mut body);
904 }
905 passes::insert_object_narrowing_casts(&vt, &mut body, pool, &desc.ret);
906 passes::fix_field_owner_downcasts(&mut body, &vt, pool);
907 passes::fix_incomparable_equality(&mut body, &vt, pool);
908 passes::fix_primitive_assign_casts(&vt, &mut body, &desc.ret);
909 passes::fix_primitive_arg_bridges(&mut body, &vt, pool);
910 passes::fix_bool_xor(&mut body, &vt, matches!(desc.ret, JavaType::Boolean));
911 passes::fix_int_operand_bridges(&mut body, &vt);
912 passes::fix_ref_array_null_consts(&mut body);
913 passes::idiom_compounds(&mut body, &vt);
914 passes::rescue_primitive_receivers(&mut body, &vt, pool);
915 passes::fix_this0_owners(&mut body, &class.name);
916 passes::apply_local_names(&mut vt, &body);
917 passes::deshadow_locals(&mut vt, pool, &class.name, &body);
918 passes::remove_kotlin_checks(&mut body);
919 passes::rewrite_kotlin_facades(&mut body, pool);
920 passes::platform_constants(&mut body);
921 passes::drop_dead_locals(&mut body);
922 passes::drop_dead_raw_news(&mut body);
923 passes::mark_field_owner_concrete(&mut vt, &mut body);
924 passes::ensure_declared(&mut body, &vt);
925 passes::rescue_arg_return_swaps(&mut body, &vt, pool, &desc.ret);
926 if &*m.name == "<init>" {
930 passes::fold_bool_value_diamonds(&mut body, &vt);
938 passes::fold_value_diamonds(&mut body, &vt);
939 passes::fix_ctor_this_aliases(&mut body, &vt);
940 if class.is_enum() {
941 passes::fold_enum_default_arg_bridge(&mut body);
942 let const_subclass = class
952 .super_name
953 .as_ref()
954 .map(|s| s != "java/lang/Object" && s != "java/lang/Enum")
955 .unwrap_or(false);
956 if !const_subclass {
957 passes::strip_enum_ctor_super(&mut body);
958 }
959 } else {
960 passes::fix_ctor_delegation_arg_defs(&mut body);
961 passes::fix_ctor_conditional_super(&mut body);
962 passes::fix_ctor_super_first(&mut body, &vt);
963 passes::dedupe_ctor_delegations(&mut body);
964 passes::strip_secondary_delegations(&mut body);
965 passes::hoist_branch_delegations(&mut body, &vt);
966 passes::extract_branched_delegation_helper(&mut body, &vt, pool, class, &desc.to_string());
967 }
968 passes::drop_dead_locals(&mut body);
972 }
973 if &*m.name == "<clinit>" {
974 passes::strip_clinit_returns(&mut body);
975 }
976 passes::strip_trailing_void_return(&mut body);
977 passes::cleanup(&mut body);
978 passes::invert_empty_thens(&mut body);
981 passes::fold_short_circuits(&mut body);
982 passes::resolve_dangling_gotos(&mut body);
983 passes::dedupe_multicatch(&mut body, pool);
984
985 if !errors.is_empty() {
986 passes::prepend_comment(
987 &mut body,
988 format!("$DDC: {} block(s) failed to decompile", errors.len()),
989 );
990 }
991
992 phase_hit_n(2, n, t_fix);
993 record_bucket(n, t0);
994 Ok(Some(MethodBody { body, vt, desc }))
995}
996
997static PHASE_MICROS: [std::sync::atomic::AtomicU64; 5] = [
998 std::sync::atomic::AtomicU64::new(0),
999 std::sync::atomic::AtomicU64::new(0),
1000 std::sync::atomic::AtomicU64::new(0),
1001 std::sync::atomic::AtomicU64::new(0),
1002 std::sync::atomic::AtomicU64::new(0),
1003];
1004static PHASE_BUCKET_MICROS: [std::sync::atomic::AtomicU64; 25] = [
1006 std::sync::atomic::AtomicU64::new(0),
1007 std::sync::atomic::AtomicU64::new(0),
1008 std::sync::atomic::AtomicU64::new(0),
1009 std::sync::atomic::AtomicU64::new(0),
1010 std::sync::atomic::AtomicU64::new(0),
1011 std::sync::atomic::AtomicU64::new(0),
1012 std::sync::atomic::AtomicU64::new(0),
1013 std::sync::atomic::AtomicU64::new(0),
1014 std::sync::atomic::AtomicU64::new(0),
1015 std::sync::atomic::AtomicU64::new(0),
1016 std::sync::atomic::AtomicU64::new(0),
1017 std::sync::atomic::AtomicU64::new(0),
1018 std::sync::atomic::AtomicU64::new(0),
1019 std::sync::atomic::AtomicU64::new(0),
1020 std::sync::atomic::AtomicU64::new(0),
1021 std::sync::atomic::AtomicU64::new(0),
1022 std::sync::atomic::AtomicU64::new(0),
1023 std::sync::atomic::AtomicU64::new(0),
1024 std::sync::atomic::AtomicU64::new(0),
1025 std::sync::atomic::AtomicU64::new(0),
1026 std::sync::atomic::AtomicU64::new(0),
1027 std::sync::atomic::AtomicU64::new(0),
1028 std::sync::atomic::AtomicU64::new(0),
1029 std::sync::atomic::AtomicU64::new(0),
1030 std::sync::atomic::AtomicU64::new(0),
1031];
1032static PHASE_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1033
1034fn bucket_of(n: usize) -> usize {
1035 if n <= 1 {
1036 0
1037 } else if n <= 5 {
1038 1
1039 } else if n <= 20 {
1040 2
1041 } else if n <= 100 {
1042 3
1043 } else {
1044 4
1045 }
1046}
1047
1048pub(crate) fn phase_hit_n(i: usize, n: usize, t: std::time::Instant) {
1049 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
1050 return;
1051 }
1052 let us = t.elapsed().as_micros() as u64;
1053 PHASE_MICROS[i].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
1054 PHASE_BUCKET_MICROS[i * 5 + bucket_of(n)].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
1055}
1056
1057pub(crate) fn phase_hit(i: usize, t: std::time::Instant) {
1058 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
1059 return;
1060 }
1061 PHASE_MICROS[i].fetch_add(
1062 t.elapsed().as_micros() as u64,
1063 std::sync::atomic::Ordering::Relaxed,
1064 );
1065}
1066
1067pub fn phases_enable() {
1069 PHASE_ON.store(true, std::sync::atomic::Ordering::Relaxed);
1070}
1071
1072pub fn phases_dump() -> [u64; 5] {
1074 let mut out = [0u64; 5];
1075 for i in 0..5 {
1076 out[i] = PHASE_MICROS[i].load(std::sync::atomic::Ordering::Relaxed);
1077 }
1078 out
1079}
1080
1081pub fn phases_bucket_dump() -> [[u64; 5]; 5] {
1083 let mut out = [[0u64; 5]; 5];
1084 for i in 0..5 {
1085 for b in 0..5 {
1086 out[i][b] = PHASE_BUCKET_MICROS[i * 5 + b].load(std::sync::atomic::Ordering::Relaxed);
1087 }
1088 }
1089 out
1090}
1091
1092static CAP_HITS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1093static CAP_INSNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1094
1095static BUILD_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1096static INSN_LIFTED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1097
1098static BUCKET_COUNT: [std::sync::atomic::AtomicU64; 5] = [
1099 std::sync::atomic::AtomicU64::new(0),
1100 std::sync::atomic::AtomicU64::new(0),
1101 std::sync::atomic::AtomicU64::new(0),
1102 std::sync::atomic::AtomicU64::new(0),
1103 std::sync::atomic::AtomicU64::new(0),
1104];
1105static BUCKET_MICROS: [std::sync::atomic::AtomicU64; 5] = [
1106 std::sync::atomic::AtomicU64::new(0),
1107 std::sync::atomic::AtomicU64::new(0),
1108 std::sync::atomic::AtomicU64::new(0),
1109 std::sync::atomic::AtomicU64::new(0),
1110 std::sync::atomic::AtomicU64::new(0),
1111];
1112
1113pub fn dump_builds() -> (u64, u64) {
1115 (
1116 BUILD_COUNT.load(std::sync::atomic::Ordering::Relaxed),
1117 INSN_LIFTED.load(std::sync::atomic::Ordering::Relaxed),
1118 )
1119}
1120
1121pub fn dump_caps() -> (u64, u64) {
1123 (
1124 CAP_HITS.load(std::sync::atomic::Ordering::Relaxed),
1125 CAP_INSNS.load(std::sync::atomic::Ordering::Relaxed),
1126 )
1127}
1128
1129pub fn dump_buckets() -> [(u64, u64); 5] {
1131 let mut out = [(0u64, 0u64); 5];
1132 for i in 0..5 {
1133 out[i] = (
1134 BUCKET_COUNT[i].load(std::sync::atomic::Ordering::Relaxed),
1135 BUCKET_MICROS[i].load(std::sync::atomic::Ordering::Relaxed),
1136 );
1137 }
1138 out
1139}
1140
1141pub fn dom_counters() -> (u64, u64) {
1143 (
1144 jdc_core::structure::DOM_CALLS.load(std::sync::atomic::Ordering::Relaxed),
1145 jdc_core::structure::DOM_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
1146 )
1147}
1148
1149pub fn dom_counters_enable() {
1151 jdc_core::structure::set_dom_counters(true);
1152}
1153
1154fn stub_body(msg: String, desc: MethodDescriptor) -> MethodBody {
1156 MethodBody {
1157 body: Stmt::Block(vec![Stmt::Comment(msg)]),
1158 vt: VarTable::default(),
1159 desc,
1160 }
1161}
1162
1163fn phis_for_merge(bid: usize, phis: &HashMap<(usize, u16), u32>) -> bool {
1165 phis.keys().any(|(b, _)| *b == bid)
1166}
1167
1168fn record_appends(
1170 cfg: &DexCfg,
1171 bid: usize,
1172 out_states: &[Option<OutState>],
1173 phis: &HashMap<(usize, u16), u32>,
1174 vt: &jdc_core::var::VarTable,
1175 out: &mut Vec<(usize, u32, u32, Expr)>,
1176) {
1177 for p in cfg.blocks[bid].pred.clone() {
1178 let Some(o) = &out_states[p] else { continue };
1179 for (r, st) in o.regs.iter().enumerate() {
1180 if let Some(&phi) = phis.get(&(bid, r as u16)) {
1181 let value = match st {
1182 Reg::Live(v) if *v == phi => continue,
1183 Reg::Live(v) => Expr::Local {
1184 var: *v,
1185 ty: vt.var(*v).ty.clone(),
1186 },
1187 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
1188 Reg::Undef | Reg::WideHi => continue,
1189 };
1190 out.push((p, o.write_pc.get(r).copied().unwrap_or(0), phi, value));
1191 }
1192 }
1193 }
1194}
1195
1196#[allow(unused)]
1197fn record_bucket(n: usize, t0: std::time::Instant) {
1198 static BUCKETS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1201 if !*BUCKETS_ON.get_or_init(|| std::env::var("DDC_BUCKETS").is_ok()) {
1202 return;
1203 }
1204 let bucket = if n <= 1 {
1205 0
1206 } else if n <= 5 {
1207 1
1208 } else if n <= 20 {
1209 2
1210 } else if n <= 100 {
1211 3
1212 } else {
1213 4
1214 };
1215 BUCKET_COUNT[bucket].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1216 BUCKET_MICROS[bucket].fetch_add(
1217 t0.elapsed().as_micros() as u64,
1218 std::sync::atomic::Ordering::Relaxed,
1219 );
1220}
1221
1222fn visit_phi_refs(e: &Expr, phis: &std::collections::HashSet<u32>, hit: &mut bool) {
1224 if *hit {
1225 return;
1226 }
1227 if let Expr::Local { var, .. } = e {
1228 if phis.contains(var) {
1229 *hit = true;
1230 }
1231 return;
1232 }
1233 let mut kids: Vec<&Expr> = Vec::new();
1234 match e {
1235 Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => kids.push(e),
1236 Expr::Bin { l, r, .. } => {
1237 kids.push(l);
1238 kids.push(r);
1239 }
1240 Expr::Cond { c, t, f } => {
1241 kids.push(c);
1242 kids.push(t);
1243 kids.push(f);
1244 }
1245 Expr::Assign { target, value, .. } => {
1246 kids.push(target);
1247 kids.push(value);
1248 }
1249 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => kids.push(e),
1250 Expr::Field { owner: Some(o), .. } => kids.push(o),
1251 Expr::Method {
1252 owner: Some(o),
1253 args,
1254 ..
1255 } => {
1256 kids.push(o);
1257 kids.extend(args.iter());
1258 }
1259 Expr::ArrayIndex { array, index } => {
1260 kids.push(array);
1261 kids.push(index);
1262 }
1263 Expr::New { args, .. } => kids.extend(args.iter()),
1264 Expr::NewArray { dims, init, .. } => {
1265 kids.extend(dims.iter());
1266 if let Some(v) = init {
1267 kids.extend(v.iter());
1268 }
1269 }
1270 Expr::NewMultiArray { dims, .. } => kids.extend(dims.iter()),
1271 _ => {}
1272 }
1273 for k in kids {
1274 visit_phi_refs(k, phis, hit);
1275 if *hit {
1276 return;
1277 }
1278 }
1279}
1280
1281fn debug_name_at(
1286 locals: &[ddc_dex::DebugLocal],
1287 slot: u16,
1288 pc: u32,
1289 ty: &jdc_core::types::JavaType,
1290) -> Option<String> {
1291 if locals.is_empty() {
1292 return None;
1293 }
1294 let hit = locals.iter().find(|l| {
1295 l.reg == slot
1296 && pc >= l.start
1297 && pc < l.end
1298 && l.ty.as_deref().map(|d| &crate::desc_type(d) == ty).unwrap_or(true)
1299 })?;
1300 let n = crate::classdec::java_ident(&hit.name);
1301 if n.is_empty() {
1302 None
1303 } else {
1304 Some(n.into_owned())
1305 }
1306}
1307
1308fn merge_states(
1309 vt: &mut VarTable,
1310 phis: &mut HashMap<(usize, u16), u32>,
1311 bid: usize,
1312 sides: &[&[Reg]],
1313 debug_locals: &[ddc_dex::DebugLocal],
1314 merge_pc: u32,
1315) -> Vec<Reg> {
1316 let len = sides.iter().map(|s| s.len()).max().unwrap_or(0);
1317 let mut out = vec![Reg::Undef; len];
1318 for (r, slot) in out.iter_mut().enumerate() {
1319 let mut all_eq = true;
1320 let mut first: Option<&Reg> = None;
1321 for s in sides {
1322 let v = s.get(r);
1323 match (first, v) {
1324 (None, v) => first = v,
1325 (Some(f), Some(v)) if f == v => {}
1326 _ => all_eq = false,
1327 }
1328 }
1329 if all_eq {
1330 *slot = first.cloned().unwrap_or(Reg::Undef);
1331 continue;
1332 }
1333 let key = (bid, r as u16);
1335 let phi = match phis.get(&key) {
1336 Some(&v) => v,
1337 None => {
1338 let ty = join_side_types(vt, sides, r);
1339 if let Some(n) = debug_name_at(debug_locals, r as u16, merge_pc, &ty.erased()) {
1347 let unified = vt
1348 .vars
1349 .iter()
1350 .find(|v| {
1351 v.slot == r as u16
1352 && !v.synthetic_name
1353 && v.name == n
1354 && v.ty.erased() == ty.erased()
1355 })
1356 .map(|v| v.id);
1357 let id = match unified {
1358 Some(id) => id,
1359 None => {
1360 let id = vt.vars.len() as u32;
1361 vt.vars.push(jdc_core::var::VarInfo {
1362 id,
1363 slot: r as u16,
1364 name: n,
1365 ty,
1366 is_param: false,
1367 range_start: 0,
1368 range_end: u16::MAX,
1369 synthetic_name: false,
1370 });
1371 while vt.by_slot.len() <= r {
1372 vt.by_slot.push(Vec::new());
1373 }
1374 vt.by_slot[r].push((0, u16::MAX, id));
1375 id
1376 }
1377 };
1378 phis.insert(key, id);
1379 id
1380 } else {
1381 let id = vt.vars.len() as u32;
1382 vt.vars.push(jdc_core::var::VarInfo {
1383 id,
1384 slot: r as u16,
1385 name: format!("v{}", id),
1386 ty,
1387 is_param: false,
1388 range_start: 0,
1389 range_end: u16::MAX,
1390 synthetic_name: true,
1391 });
1392 while vt.by_slot.len() <= r {
1393 vt.by_slot.push(Vec::new());
1394 }
1395 vt.by_slot[r].push((0, u16::MAX, id));
1396 phis.insert(key, id);
1397 id
1398 }
1399 }
1400 };
1401 *slot = Reg::Live(phi);
1402 }
1403 out
1404}
1405
1406fn join_side_types(vt: &VarTable, sides: &[&[Reg]], r: usize) -> TypeRef {
1408 let mut ty: Option<JavaType> = None;
1409 for s in sides {
1410 let st = s.get(r).cloned().unwrap_or(Reg::Undef);
1411 let e = match st {
1412 Reg::Pending(e) | Reg::PendingCall(e) => e,
1413 Reg::Live(v) => Expr::Local {
1414 var: v,
1415 ty: vt.var(v).ty.clone(),
1416 },
1417 _ => continue,
1418 };
1419 let t = e.type_ref().erased();
1420 ty = Some(match ty {
1421 None => t,
1422 Some(prev) => join_types(&prev, &t),
1423 });
1424 }
1425 TypeRef::J(ty.unwrap_or(JavaType::Int))
1426}
1427
1428fn join_types(a: &JavaType, b: &JavaType) -> JavaType {
1429 if a == b {
1430 return a.clone();
1431 }
1432 let num = |t: &JavaType| {
1433 matches!(
1434 t,
1435 JavaType::Boolean
1436 | JavaType::Byte
1437 | JavaType::Char
1438 | JavaType::Short
1439 | JavaType::Int
1440 | JavaType::Long
1441 | JavaType::Float
1442 | JavaType::Double
1443 )
1444 };
1445 if num(a) && num(b) {
1446 return match (a, b) {
1448 (JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
1449 (JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
1450 (JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
1451 _ => JavaType::Int,
1452 };
1453 }
1454 if let (JavaType::Object(x), JavaType::Object(y)) = (a, b) {
1455 if x == y {
1456 return a.clone();
1457 }
1458 return JavaType::Object("java/lang/Object".into());
1460 }
1461 JavaType::Object("java/lang/Object".into())
1462}
1463
1464#[allow(dead_code)]
1466fn is_exit_term(term: &jdc_core::ir::build::Term) -> bool {
1467 matches!(
1468 term,
1469 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
1470 )
1471}
1472
1473#[allow(dead_code)]
1474fn unused(_: &InsnKind) {}