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);
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 if crate::ssa_census::enabled() {
123 crate::ssa_census::census_method(&cfg, &env);
124 }
125
126 let core_for_order = cfg.to_core();
129 let universe: jdc_core::FxHashSet<usize> = (0..n).collect();
130 let mut order = reverse_postorder(&core_for_order, core_for_order.entry, &universe);
131 {
132 let mut seen: jdc_core::FxHashSet<usize> = order.iter().copied().collect();
133 for b in &cfg.blocks {
134 if seen.insert(b.id) {
135 order.push(b.id);
136 }
137 }
138 }
139
140 let mut handler_entry_block: HashMap<usize, usize> = HashMap::default();
143 for b in &cfg.blocks {
144 if b.handlers.is_empty() {
145 continue;
146 }
147 let mut best: Option<(u32, usize)> = None;
148 for &ri in &b.handlers {
149 if let Some(r) = cfg.exc_ranges.get(ri) {
150 if best.map(|(s, _)| r.start < s).unwrap_or(true) {
151 if let Some(eb) = cfg.block_at(r.start) {
152 best = Some((r.start, eb));
153 }
154 }
155 }
156 }
157 if let Some((_, eb)) = best {
158 handler_entry_block.insert(b.id, eb);
159 }
160 }
161
162 let mut results: Vec<BlockResult> = Vec::with_capacity(n);
164 results.resize_with(n, || BlockResult {
165 stmts: vec![],
166 out_stack: vec![],
167 term: jdc_core::ir::build::Term::Return(None),
168 });
169 let mut out_states: Vec<Option<OutState>> = vec![None; n];
170 let mut stable_vars: HashMap<(usize, u16, u64), u32> = HashMap::default();
174 let mut mflags = crate::lift::MethodFlags::none();
178 let mut try_entry_snapshots: HashMap<usize, Vec<Reg>> = HashMap::default();
182 let mut built_once = vec![false; n];
183 let mut errors: HashMap<usize, String> = HashMap::default();
184 let mut phis: HashMap<(usize, u16), u32> = HashMap::default();
186 let entry_is_handler = handler_entry_block.contains_key(&0);
189
190 let try_entry_flags: Vec<bool> = (0..n)
195 .map(|b| {
196 !cfg.blocks[b].handlers.is_empty()
197 || cfg
198 .exc_ranges
199 .iter()
200 .any(|r| cfg.block_at(r.start) == Some(b))
201 })
202 .collect();
203 let handler_types: Vec<Vec<Option<std::sync::Arc<str>>>> =
204 (0..n).map(|b| handler_types_for(&cfg, b)).collect();
205
206 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 {
215 let mut h: u64 = 1;
216 for &p in &cfg.blocks[bid].pred {
217 h = h
218 .wrapping_mul(31)
219 .wrapping_add(p as u64)
220 .wrapping_mul(31)
221 .wrapping_add(ver[p]);
222 }
223 h
225 }
226 let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
227 let mut queued: Vec<bool> = vec![false; n];
228 for &bid in &order {
229 queue.push_back(bid);
230 queued[bid] = true;
231 }
232 let mut appends: HashMap<usize, Vec<(usize, u32, u32, Expr)>> = HashMap::default();
235 let mut total_visits: usize = 0;
236 let visit_cap: usize = 64 * n + 256;
237
238 while let Some(bid) = queue.pop_front() {
239 queued[bid] = false;
240 total_visits += 1;
241 if total_visits > visit_cap {
242 CAP_HITS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
243 CAP_INSNS.fetch_add(
244 insn_count as u64 * 1000 + n as u64,
245 std::sync::atomic::Ordering::Relaxed,
246 );
247 break; }
249 let is_handler = handler_entry_block.contains_key(&bid);
250 let sig = input_sig(&cfg, bid, &ver)
254 ^ (if is_handler {
255 let eb = handler_entry_block[&bid];
256 (eb as u64) << 32
257 } else {
258 0
259 });
260 let need_build = !built_once[bid] || built_ver[bid] != sig;
261 if !need_build {
262 continue;
266 }
267 let ins: Vec<Reg> = if bid == cfg.entry {
269 if cfg.blocks[0].pred.is_empty() || entry_is_handler {
270 entry.clone()
271 } else {
272 let mut sides: Vec<&[Reg]> = vec![entry.as_slice()];
276 for p in cfg.blocks[bid].pred.clone() {
277 if let Some(o) = &out_states[p] {
278 sides.push(o.regs.as_slice());
279 }
280 }
281 let merged =
282 merge_states(&mut vt, &mut phis, bid, &sides, &env.debug_locals, cfg.blocks[bid].start);
283 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
288 for (r, st) in entry.iter().enumerate() {
289 if let Some(&phi) = phis.get(&(bid, r as u16)) {
290 let value = match st {
291 Reg::Live(v) if *v == phi => continue,
292 Reg::Live(v) => Expr::Local {
293 var: *v,
294 ty: vt.var(*v).ty.clone(),
295 },
296 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
297 Reg::Undef | Reg::WideHi => continue,
298 };
299 recs.push((usize::MAX, 0, phi, value));
300 }
301 }
302 appends.insert(bid, recs);
303 merged
304 }
305 } else if is_handler {
306 let eb = handler_entry_block[&bid];
307 try_entry_snapshots
308 .get(&eb)
309 .cloned()
310 .unwrap_or_else(|| vec![Reg::Undef; env.code.registers_size as usize])
311 } else {
312 let mut sides: Vec<&[Reg]> = Vec::new();
315 for p in cfg.blocks[bid].pred.clone() {
316 if let Some(o) = &out_states[p] {
317 sides.push(o.regs.as_slice());
318 }
319 }
320 if sides.is_empty() {
321 vec![Reg::Undef; env.code.registers_size as usize]
322 } else if sides.len() == 1 {
323 sides[0].to_vec()
324 } else {
325 merge_states(
326 &mut vt,
327 &mut phis,
328 bid,
329 &sides,
330 &env.debug_locals,
331 cfg.blocks[bid].start,
332 )
333 }
334 };
335
336 if phis_for_merge(bid, &phis) {
340 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
341 record_appends(&cfg, bid, &out_states, &phis, &vt, &mut recs);
342 if bid == cfg.entry {
345 if let Some(prev) = appends.get(&bid) {
346 for (p, pc, v, e) in prev.iter() {
347 if *p == usize::MAX {
348 recs.push((*p, *pc, *v, e.clone()));
349 }
350 }
351 }
352 }
353 appends.insert(bid, recs);
354 }
355
356 built_once[bid] = true;
360 built_ver[bid] = sig;
361 if try_entry_flags[bid] {
362 try_entry_snapshots.insert(bid, ins.clone());
363 }
364 BUILD_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
365 INSN_LIFTED.fetch_add(
366 cfg.block_ins(&cfg.blocks[bid]).len() as u64,
367 std::sync::atomic::Ordering::Relaxed,
368 );
369 let htypes = &handler_types[bid];
370 let block_ins = cfg.block_ins(&cfg.blocks[bid]);
371 let lifter = Lifter::new(&env, &mut vt, ins, bid, &mut stable_vars, &mut mflags);
372 let rebuilt = match lifter.build_block(block_ins, htypes, method_final_reads.clone()) {
373 Ok((r, out)) => {
374 results[bid] = r;
375 let changed = match &out_states[bid] {
376 Some(prev) => prev.regs != out.regs,
377 None => true,
378 };
379 out_states[bid] = Some(out);
380 changed
381 }
382 Err(msg) => {
383 results[bid] = BlockResult {
384 stmts: vec![Stmt::Comment(format!("$DDC-BLOCK-ERROR: {msg}"))],
385 out_stack: vec![],
386 term: jdc_core::ir::build::Term::Return(None),
387 };
388 out_states[bid] = Some(OutState {
389 regs: vec![Reg::Undef; env.code.registers_size as usize],
390 write_pc: vec![0; env.code.registers_size as usize],
391 });
392 errors.insert(bid, msg);
393 false
394 }
395 };
396 if rebuilt {
397 ver[bid] += 1;
398 for &succ in &cfg.blocks[bid].succ {
400 if !queued[succ] && succ < n {
401 queue.push_back(succ);
402 queued[succ] = true;
403 }
404 }
405 if is_handler {
409 let eb = handler_entry_block[&bid];
410 if !queued[eb] && eb < n {
411 queue.push_back(eb);
412 queued[eb] = true;
413 }
414 }
415 for &ri in &cfg.blocks[bid].handlers {
418 if let Some(r) = cfg.exc_ranges.get(ri) {
419 if let Some(hb) = cfg.block_at(r.handler) {
420 if !queued[hb] && hb < n {
421 queue.push_back(hb);
422 queued[hb] = true;
423 }
424 }
425 }
426 }
427 }
428 }
429
430 let mut entry_phi_inits: Vec<(u32, Expr)> = Vec::new();
439 if let Some(recs) = appends.get_mut(&cfg.entry) {
440 let mut entry_side: Vec<(u32, Expr)> = Vec::new();
441 recs.retain(|(p, _, v, e)| {
442 if *p == usize::MAX {
443 entry_side.push((*v, e.clone()));
444 false
445 } else {
446 true
447 }
448 });
449 entry_side.sort_by_key(|(v, _)| *v);
450 entry_phi_inits = entry_side;
451 }
452
453 if std::env::var("DDC_DBG_PHI").is_ok() {
467 let mut ks: Vec<_> = phis.keys().copied().collect();
468 ks.sort();
469 for (b, r) in ks {
470 eprintln!("[phi] merge={} reg={} var={} name={}", b, r, phis[&(b, r)], vt.var(phis[&(b, r)]).name);
471 }
472 let mut mo: Vec<usize> = appends.keys().copied().collect();
473 mo.sort_unstable();
474 for bid in mo {
475 for (p, pc, v, e) in &appends[&bid] {
476 eprintln!("[append] merge={} pred={} pc={} var={}({}) val={:?}", bid, p, pc, v, vt.var(*v).name, e);
477 }
478 }
479 }
480 let mut merge_order: Vec<usize> = appends.keys().copied().collect();
481 merge_order.sort_unstable();
482 let mut per_pred: HashMap<usize, Vec<(u32, u32, Expr)>> = HashMap::default();
483 for bid in merge_order {
484 for (p, pc, v, e) in &appends[&bid] {
485 per_pred.entry(*p).or_default().push((*pc, *v, e.clone()));
486 }
487 }
488 let mut pred_order: Vec<usize> = per_pred.keys().copied().collect();
489 pred_order.sort_unstable();
490 for p in pred_order {
491 let mut adds = per_pred.remove(&p).unwrap();
492 if p == usize::MAX {
493 continue;
494 }
495 adds.sort_by_key(|(pc, _, _)| *pc);
496 let term_is_exit = matches!(
497 results[p].term,
498 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
499 );
500 if term_is_exit {
501 continue;
502 }
503 let phi_set: std::collections::HashSet<u32> = adds.iter().map(|(_, v, _)| *v).collect();
504 let mut snapshots: Vec<Stmt> = Vec::new();
505 let mut emitted: Vec<(u32, Expr)> = Vec::new();
507 for (_, v, e) in &adds {
508 let mut e = e.clone();
509 let mut refs_phi = false;
510 visit_phi_refs(&e, &phi_set, &mut refs_phi);
511 if refs_phi {
512 let ty = e.type_ref();
514 let id = vt.vars.len() as u32;
515 let name = format!("v{}", id);
516 vt.vars.push(jdc_core::var::VarInfo {
517 id,
518 slot: u16::MAX,
519 name,
520 ty: ty.clone(),
521 is_param: false,
522 range_start: 0,
523 range_end: u16::MAX,
524 synthetic_name: true,
525 });
526 snapshots.push(Stmt::LocalDef {
527 var: id,
528 init: Some(e),
529 is_final: false,
530 force_type: true,
531 });
532 e = Expr::Local { var: id, ty };
533 }
534 emitted.push((*v, e));
535 }
536 for st in snapshots {
537 results[p].stmts.push(st);
538 }
539 for (v, e) in emitted {
540 results[p].stmts.push(Stmt::ExprStmt(Expr::Assign {
541 target: Box::new(Expr::Local {
542 var: v,
543 ty: vt.var(v).ty.clone(),
544 }),
545 op: jdc_core::ir::expr::AssignOp::Plain,
546 value: Box::new(e),
547 }));
548 }
549 }
550
551 let t_fix = t0;
552 phase_hit_n(0, n, t_fix);
553 if n == 1 && !entry_is_handler && cfg.blocks[0].pred.is_empty() {
559 let r = std::mem::replace(
560 &mut results[0],
561 BlockResult {
562 stmts: vec![],
563 out_stack: vec![],
564 term: jdc_core::ir::build::Term::Goto,
565 },
566 );
567 let mut body = r.stmts;
568 match r.term {
569 jdc_core::ir::build::Term::Return(Some(e)) => {
570 body.push(Stmt::Return(Some(e)));
571 }
572 jdc_core::ir::build::Term::Return(None) => {}
573 jdc_core::ir::build::Term::Throw(e) => body.push(Stmt::Throw(e)),
574 _ => {}
575 }
576 let mut body = Stmt::Block(body);
577 passes::fused_expr_rewrites(&mut body, &vt);
578 if mflags.has_sb() {
579 passes::fold_string_builders(&mut body, &vt);
580 }
581 passes::forward_single_use(&mut body, &vt);
582 passes::cleanup(&mut body);
583 passes::inline_accessors(&mut body, pool, &class.name);
584 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
585 passes::fix_ref_null_assigns(&vt, &mut body);
586 passes::fix_null_sentinels(&mut body, &vt, &desc.ret);
587 passes::split_generations(&mut vt, &mut body, pool);
588 passes::insert_object_narrowing_casts(&vt, &mut body, pool, &desc.ret);
589 passes::fix_field_owner_downcasts(&mut body, &vt, pool);
590 passes::fix_shadowed_super_fields(&mut body, &vt, pool, &class.name);
591 passes::fix_incomparable_equality(&mut body, &vt, pool);
592 passes::fix_primitive_assign_casts(&vt, &mut body, &desc.ret);
593 passes::fix_primitive_arg_bridges(&mut body, &vt, pool);
594 passes::fix_bool_xor(&mut body, &vt, matches!(desc.ret, JavaType::Boolean));
595 passes::fix_int_operand_bridges(&mut body, &vt);
596 passes::fix_ref_array_null_consts(&mut body);
597 passes::idiom_compounds(&mut body, &vt);
598 passes::rescue_primitive_receivers(&mut body, &vt, pool);
599 passes::fix_this0_owners(&mut body, &class.name);
600 passes::apply_local_names(&mut vt, &body);
601 passes::deshadow_locals(&mut vt, pool, &class.name, &body);
602 passes::remove_kotlin_checks(&mut body);
603 passes::rewrite_kotlin_facades(&mut body, pool);
604 passes::platform_constants(&mut body);
605 passes::drop_dead_locals(&mut body);
606 passes::drop_dead_raw_news(&mut body);
607 passes::drop_empty_finallies(&mut body);
608 passes::drop_pure_value_stmts(&mut body);
609 passes::mark_field_owner_concrete(&mut vt, &mut body);
610 passes::ensure_declared(&mut body, &vt);
611 passes::rescue_arg_return_swaps(&mut body, &vt, pool, &desc.ret);
612 if &*m.name == "<init>" {
616 passes::fold_bool_value_diamonds(&mut body, &vt);
624 passes::fold_value_diamonds(&mut body, &vt);
625 passes::fix_ctor_this_aliases(&mut body, &vt);
626 passes::strip_ctor_hotfix_guards(&mut body);
632 if class.is_enum() {
633 passes::fold_enum_default_arg_bridge(&mut body);
634 let const_subclass = class
644 .super_name
645 .as_ref()
646 .map(|s| s != "java/lang/Object" && s != "java/lang/Enum")
647 .unwrap_or(false);
648 if !const_subclass {
649 passes::strip_enum_ctor_super(&mut body);
650 }
651 } else {
652 passes::fix_ctor_delegation_arg_defs(&mut body);
653 passes::fix_ctor_conditional_super(&mut body);
654 passes::fix_ctor_super_first(&mut body, &vt);
655 passes::dedupe_ctor_delegations(&mut body);
656 passes::strip_secondary_delegations(&mut body);
657 passes::hoist_branch_delegations(&mut body, &vt);
658 passes::extract_branched_delegation_helper(&mut body, &vt, pool, class, &desc.to_string());
659 }
660 passes::drop_dead_locals(&mut body);
664 }
665 passes::strip_trailing_void_return(&mut body);
666 passes::cleanup(&mut body);
667 passes::invert_empty_thens(&mut body);
668 passes::fold_short_circuits(&mut body);
669 passes::resolve_dangling_gotos(&mut body);
670 passes::dedupe_multicatch(&mut body, pool);
671 passes::strip_phantom_field_writes(&mut body, pool);
672 passes::init_bare_decls(&mut body, &vt);
673 passes::final_dead_assigns(&mut body);
674 if !errors.is_empty() {
675 passes::prepend_comment(
676 &mut body,
677 format!("$DDC: {} block(s) failed to decompile", errors.len()),
678 );
679 }
680 phase_hit_n(2, n, t_fix);
681 record_bucket(n, t0);
682 return Ok(Some(MethodBody { body, vt, desc }));
683 }
684
685 const TREE_GUARD: usize = 20_000;
690 let core_cfg = cfg.to_core();
691 #[allow(unused_assignments)]
692 let mut body: Option<Stmt> = None;
693 let mut prev_size: Option<usize> = None;
694 let mut budget: u32 = if n > 100 {
699 64
700 } else if n > 20 {
701 128
702 } else {
703 512
704 };
705 let groups = jdc_core::structure::group_exceptions_with(&core_cfg, Some(&results));
711 let dom_universe: jdc_core::FxHashSet<usize> = (0..core_cfg.blocks.len()).collect();
712 let dom = jdc_core::structure::compute_dominators(&core_cfg, &dom_universe, core_cfg.entry);
713 if trace_on() {
714 eprintln!("[mshape] {}.{} blocks={}", class.name, m.name, n);
715 }
716 if n >= 2_100 {
727 return Ok(Some(stub_body(
728 format!(
729 "$DDC: method too large ({} blocks) — body elided for determinism",
730 n
731 ),
732 desc,
733 )));
734 }
735 #[cfg(feature = "visit-stats")]
736 {
737 jdc_core::structure::reset_visit_stats();
738 }
739 let walk_budget: u64 = walk_budget_override().unwrap_or(8 * n as u64 + 128);
749 let walk_work_budget: u64 = walk_work_override()
759 .unwrap_or(600_000 + 128 * n as u64);
760 loop {
767 jdc_core::structure::set_budget_override(Some(budget));
768 jdc_core::structure::set_walk_visit_budget(Some(walk_budget));
769 jdc_core::structure::set_walk_work_budget(Some(walk_work_budget));
770 let mut st = Structurer::with_shared_groups(
771 &core_cfg,
772 &results,
773 &groups,
774 Default::default(),
775 Default::default(),
776 );
777 let tw0 = std::time::Instant::now();
778 let region = st.structure_method();
779 let tw1 = std::time::Instant::now();
780 let mut converter = Converter::with_precomputed_ref(&core_cfg, &results, &groups, &dom);
781 let dump_ir = std::env::var("DDC_DUMP_IR").ok().filter(|f| m.name.contains(f));
784 let saved_region = dump_ir.as_deref().map(|_| region.clone());
785 let candidate = converter.convert(region);
786 if let (Some(_), Some(r)) = (dump_ir.as_deref(), saved_region) {
787 eprintln!("[region-dump] {}.{}\n{:#?}", class.name, m.name, r);
788 eprintln!("[stmt-dump] {}.{}\n{:#?}", class.name, m.name, candidate);
789 }
790 let tw2 = std::time::Instant::now();
791 if trace_on() {
792 eprintln!(
793 "[rung] {}.{} budget={} walk={:?} convert={:?}",
794 class.name, m.name, budget, tw1 - tw0, tw2 - tw1
795 );
796 }
797 jdc_core::structure::set_budget_override(None);
798 jdc_core::structure::set_walk_visit_budget(None);
799 jdc_core::structure::set_walk_work_budget(None);
800 let size = {
801 let mut c = 0usize;
802 passes::count_stmts_deep(&candidate, &mut c);
803 c
804 };
805 if trace_on() {
806 eprintln!("[guard] {}.{} size={} budget={}", class.name, m.name, size, budget);
807 }
808 if size <= TREE_GUARD {
809 body = Some(candidate);
810 break;
811 }
812 if budget <= 64 && size > 3 * TREE_GUARD {
820 return Ok(Some(stub_body(
821 format!(
822 "$DDC: statement tree exploded ({} nodes) — pathological CFG, body elided",
823 size
824 ),
825 desc,
826 )));
827 }
828 if budget <= 32 && prev_size.is_some_and(|p| size >= p) {
834 return Ok(Some(stub_body(
835 format!(
836 "$DDC: statement tree exploded ({} nodes) — pathological CFG, body elided",
837 size
838 ),
839 desc,
840 )));
841 }
842 prev_size = Some(size);
843 if budget <= 16 {
844 return Ok(Some(stub_body(
845 format!(
846 "$DDC: statement tree exploded ({} nodes) even at copy budget 16",
847 size
848 ),
849 desc,
850 )));
851 }
852 budget /= 2;
853 }
854 #[cfg(feature = "visit-stats")]
855 if trace_on() && n > 3 {
856 let used = jdc_core::structure::walk_visits_consumed();
857 eprintln!(
858 "[visits] {}.{} blocks={} consumed={} ratio={:.1}",
859 class.name,
860 m.name,
861 n,
862 used,
863 used as f64 / n as f64
864 );
865 }
866 phase_hit_n(1, n, t_fix);
867 let mut body = body.unwrap();
868 if !entry_phi_inits.is_empty() {
869 let head: Vec<Stmt> = entry_phi_inits
870 .into_iter()
871 .map(|(v, e)| Stmt::LocalDef {
872 var: v,
873 init: Some(e),
874 is_final: false,
875 force_type: true,
876 })
877 .collect();
878 body = match body {
879 Stmt::Block(v) => {
880 let mut nv = head;
881 nv.extend(v);
882 Stmt::Block(nv)
883 }
884 other => {
885 let mut nv = head;
886 nv.push(other);
887 Stmt::Block(nv)
888 }
889 };
890 }
891
892 passes::bind_catches(&mut body, &mut vt);
893 passes::prune_unreachable(&mut body);
894 passes::fused_expr_rewrites(&mut body, &vt);
897 if mflags.has_sb() {
898 passes::fold_string_builders(&mut body, &vt);
899 }
900 passes::ternary_fold(&mut body);
901 passes::drop_empty_finallies(&mut body);
902 passes::drop_pure_value_stmts(&mut body);
903 if mflags.has_monitor() {
904 passes::fold_synchronized(&mut body);
905 }
906 passes::forward_single_use(&mut body, &vt);
907 passes::cleanup(&mut body);
908 passes::inline_accessors(&mut body, pool, &class.name);
909 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
910 passes::fix_ref_null_assigns(&vt, &mut body);
911 passes::fix_null_sentinels(&mut body, &vt, &desc.ret);
912 passes::split_generations(&mut vt, &mut body, pool);
913 {
926 let rb = matches!(desc.ret, JavaType::Boolean);
927 let mut rounds = 0;
928 loop {
929 let c = passes::booleanize(&mut vt, &mut body, rb);
930 rounds += 1;
931 if c == 0 || rounds >= 3 {
932 break;
933 }
934 passes::split_generations(&mut vt, &mut body, pool);
935 }
936 }
937 if matches!(
938 desc.ret,
939 JavaType::Int | JavaType::Long | JavaType::Short | JavaType::Byte | JavaType::Float | JavaType::Double
940 ) {
941 passes::fix_int_returns(&vt, &mut body);
942 }
943 if matches!(desc.ret, JavaType::Boolean) {
944 passes::fix_bool_returns(&vt, &mut body);
945 }
946 passes::insert_object_narrowing_casts(&vt, &mut body, pool, &desc.ret);
947 passes::fix_field_owner_downcasts(&mut body, &vt, pool);
948 passes::fix_shadowed_super_fields(&mut body, &vt, pool, &class.name);
949 passes::fix_incomparable_equality(&mut body, &vt, pool);
950 passes::fix_primitive_assign_casts(&vt, &mut body, &desc.ret);
951 passes::fix_primitive_arg_bridges(&mut body, &vt, pool);
952 passes::fix_bool_xor(&mut body, &vt, matches!(desc.ret, JavaType::Boolean));
953 passes::fix_int_operand_bridges(&mut body, &vt);
954 passes::fix_ref_array_null_consts(&mut body);
955 passes::idiom_compounds(&mut body, &vt);
956 passes::rescue_primitive_receivers(&mut body, &vt, pool);
957 passes::fix_this0_owners(&mut body, &class.name);
958 passes::apply_local_names(&mut vt, &body);
959 passes::deshadow_locals(&mut vt, pool, &class.name, &body);
960 passes::remove_kotlin_checks(&mut body);
961 passes::rewrite_kotlin_facades(&mut body, pool);
962 passes::platform_constants(&mut body);
963 passes::drop_dead_locals(&mut body);
964 passes::drop_dead_raw_news(&mut body);
965 passes::mark_field_owner_concrete(&mut vt, &mut body);
966 passes::ensure_declared(&mut body, &vt);
967 passes::rescue_arg_return_swaps(&mut body, &vt, pool, &desc.ret);
968 if &*m.name == "<init>" {
972 passes::fold_bool_value_diamonds(&mut body, &vt);
980 passes::fold_value_diamonds(&mut body, &vt);
981 passes::fix_ctor_this_aliases(&mut body, &vt);
982 passes::strip_ctor_hotfix_guards(&mut body);
988 if class.is_enum() {
989 passes::fold_enum_default_arg_bridge(&mut body);
990 let const_subclass = class
1000 .super_name
1001 .as_ref()
1002 .map(|s| s != "java/lang/Object" && s != "java/lang/Enum")
1003 .unwrap_or(false);
1004 if !const_subclass {
1005 passes::strip_enum_ctor_super(&mut body);
1006 }
1007 } else {
1008 passes::fix_ctor_delegation_arg_defs(&mut body);
1009 passes::fix_ctor_conditional_super(&mut body);
1010 passes::fix_ctor_super_first(&mut body, &vt);
1011 passes::dedupe_ctor_delegations(&mut body);
1012 passes::strip_secondary_delegations(&mut body);
1013 passes::hoist_branch_delegations(&mut body, &vt);
1014 passes::extract_branched_delegation_helper(&mut body, &vt, pool, class, &desc.to_string());
1015 }
1016 passes::drop_dead_locals(&mut body);
1020 }
1021 if &*m.name == "<clinit>" {
1022 passes::strip_clinit_returns(&mut body);
1023 }
1024 passes::strip_trailing_void_return(&mut body);
1025 passes::cleanup(&mut body);
1026 passes::invert_empty_thens(&mut body);
1029 passes::fold_short_circuits(&mut body);
1030 passes::resolve_dangling_gotos(&mut body);
1031 passes::dedupe_multicatch(&mut body, pool);
1032 passes::strip_phantom_field_writes(&mut body, pool);
1033 passes::init_bare_decls(&mut body, &vt);
1034 passes::final_dead_assigns(&mut body);
1035
1036 if std::env::var("DDC_DBG_PHI").is_ok() {
1037 for (id, v) in vt.vars.iter().enumerate() {
1038 if matches!(v.name.as_str(), "p02x" | "p03x" | "linkedList10" | "linkedList" | "p0x") {
1039 eprintln!("[vt-final] v{} name={} ty={} slot={} synth={}", id, v.name, v.ty.erased(), v.slot, v.synthetic_name);
1040 }
1041 }
1042 }
1043 if !errors.is_empty() {
1044 passes::prepend_comment(
1045 &mut body,
1046 format!("$DDC: {} block(s) failed to decompile", errors.len()),
1047 );
1048 }
1049
1050 phase_hit_n(2, n, t_fix);
1051 record_bucket(n, t0);
1052 Ok(Some(MethodBody { body, vt, desc }))
1053}
1054
1055static PHASE_MICROS: [std::sync::atomic::AtomicU64; 5] = [
1056 std::sync::atomic::AtomicU64::new(0),
1057 std::sync::atomic::AtomicU64::new(0),
1058 std::sync::atomic::AtomicU64::new(0),
1059 std::sync::atomic::AtomicU64::new(0),
1060 std::sync::atomic::AtomicU64::new(0),
1061];
1062static PHASE_BUCKET_MICROS: [std::sync::atomic::AtomicU64; 25] = [
1064 std::sync::atomic::AtomicU64::new(0),
1065 std::sync::atomic::AtomicU64::new(0),
1066 std::sync::atomic::AtomicU64::new(0),
1067 std::sync::atomic::AtomicU64::new(0),
1068 std::sync::atomic::AtomicU64::new(0),
1069 std::sync::atomic::AtomicU64::new(0),
1070 std::sync::atomic::AtomicU64::new(0),
1071 std::sync::atomic::AtomicU64::new(0),
1072 std::sync::atomic::AtomicU64::new(0),
1073 std::sync::atomic::AtomicU64::new(0),
1074 std::sync::atomic::AtomicU64::new(0),
1075 std::sync::atomic::AtomicU64::new(0),
1076 std::sync::atomic::AtomicU64::new(0),
1077 std::sync::atomic::AtomicU64::new(0),
1078 std::sync::atomic::AtomicU64::new(0),
1079 std::sync::atomic::AtomicU64::new(0),
1080 std::sync::atomic::AtomicU64::new(0),
1081 std::sync::atomic::AtomicU64::new(0),
1082 std::sync::atomic::AtomicU64::new(0),
1083 std::sync::atomic::AtomicU64::new(0),
1084 std::sync::atomic::AtomicU64::new(0),
1085 std::sync::atomic::AtomicU64::new(0),
1086 std::sync::atomic::AtomicU64::new(0),
1087 std::sync::atomic::AtomicU64::new(0),
1088 std::sync::atomic::AtomicU64::new(0),
1089];
1090static PHASE_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1091
1092fn bucket_of(n: usize) -> usize {
1093 if n <= 1 {
1094 0
1095 } else if n <= 5 {
1096 1
1097 } else if n <= 20 {
1098 2
1099 } else if n <= 100 {
1100 3
1101 } else {
1102 4
1103 }
1104}
1105
1106pub(crate) fn phase_hit_n(i: usize, n: usize, t: std::time::Instant) {
1107 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
1108 return;
1109 }
1110 let us = t.elapsed().as_micros() as u64;
1111 PHASE_MICROS[i].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
1112 PHASE_BUCKET_MICROS[i * 5 + bucket_of(n)].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
1113}
1114
1115pub(crate) fn phase_hit(i: usize, t: std::time::Instant) {
1116 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
1117 return;
1118 }
1119 PHASE_MICROS[i].fetch_add(
1120 t.elapsed().as_micros() as u64,
1121 std::sync::atomic::Ordering::Relaxed,
1122 );
1123}
1124
1125pub fn phases_enable() {
1127 PHASE_ON.store(true, std::sync::atomic::Ordering::Relaxed);
1128}
1129
1130pub fn phases_dump() -> [u64; 5] {
1132 let mut out = [0u64; 5];
1133 for i in 0..5 {
1134 out[i] = PHASE_MICROS[i].load(std::sync::atomic::Ordering::Relaxed);
1135 }
1136 out
1137}
1138
1139pub fn phases_bucket_dump() -> [[u64; 5]; 5] {
1141 let mut out = [[0u64; 5]; 5];
1142 for i in 0..5 {
1143 for b in 0..5 {
1144 out[i][b] = PHASE_BUCKET_MICROS[i * 5 + b].load(std::sync::atomic::Ordering::Relaxed);
1145 }
1146 }
1147 out
1148}
1149
1150static CAP_HITS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1151static CAP_INSNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1152
1153static BUILD_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1154static INSN_LIFTED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1155
1156static BUCKET_COUNT: [std::sync::atomic::AtomicU64; 5] = [
1157 std::sync::atomic::AtomicU64::new(0),
1158 std::sync::atomic::AtomicU64::new(0),
1159 std::sync::atomic::AtomicU64::new(0),
1160 std::sync::atomic::AtomicU64::new(0),
1161 std::sync::atomic::AtomicU64::new(0),
1162];
1163static BUCKET_MICROS: [std::sync::atomic::AtomicU64; 5] = [
1164 std::sync::atomic::AtomicU64::new(0),
1165 std::sync::atomic::AtomicU64::new(0),
1166 std::sync::atomic::AtomicU64::new(0),
1167 std::sync::atomic::AtomicU64::new(0),
1168 std::sync::atomic::AtomicU64::new(0),
1169];
1170
1171pub fn dump_builds() -> (u64, u64) {
1173 (
1174 BUILD_COUNT.load(std::sync::atomic::Ordering::Relaxed),
1175 INSN_LIFTED.load(std::sync::atomic::Ordering::Relaxed),
1176 )
1177}
1178
1179pub fn dump_caps() -> (u64, u64) {
1181 (
1182 CAP_HITS.load(std::sync::atomic::Ordering::Relaxed),
1183 CAP_INSNS.load(std::sync::atomic::Ordering::Relaxed),
1184 )
1185}
1186
1187pub fn dump_buckets() -> [(u64, u64); 5] {
1189 let mut out = [(0u64, 0u64); 5];
1190 for i in 0..5 {
1191 out[i] = (
1192 BUCKET_COUNT[i].load(std::sync::atomic::Ordering::Relaxed),
1193 BUCKET_MICROS[i].load(std::sync::atomic::Ordering::Relaxed),
1194 );
1195 }
1196 out
1197}
1198
1199pub fn dom_counters() -> (u64, u64) {
1201 (
1202 jdc_core::structure::DOM_CALLS.load(std::sync::atomic::Ordering::Relaxed),
1203 jdc_core::structure::DOM_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
1204 )
1205}
1206
1207pub fn dom_counters_enable() {
1209 jdc_core::structure::set_dom_counters(true);
1210}
1211
1212fn stub_body(msg: String, desc: MethodDescriptor) -> MethodBody {
1214 MethodBody {
1215 body: Stmt::Block(vec![Stmt::Comment(msg)]),
1216 vt: VarTable::default(),
1217 desc,
1218 }
1219}
1220
1221fn phis_for_merge(bid: usize, phis: &HashMap<(usize, u16), u32>) -> bool {
1223 phis.keys().any(|(b, _)| *b == bid)
1224}
1225
1226fn record_appends(
1228 cfg: &DexCfg,
1229 bid: usize,
1230 out_states: &[Option<OutState>],
1231 phis: &HashMap<(usize, u16), u32>,
1232 vt: &jdc_core::var::VarTable,
1233 out: &mut Vec<(usize, u32, u32, Expr)>,
1234) {
1235 for p in cfg.blocks[bid].pred.clone() {
1236 let Some(o) = &out_states[p] else { continue };
1237 for (r, st) in o.regs.iter().enumerate() {
1238 if let Some(&phi) = phis.get(&(bid, r as u16)) {
1239 let value = match st {
1240 Reg::Live(v) if *v == phi => continue,
1241 Reg::Live(v) => Expr::Local {
1242 var: *v,
1243 ty: vt.var(*v).ty.clone(),
1244 },
1245 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
1246 Reg::Undef | Reg::WideHi => continue,
1247 };
1248 out.push((p, o.write_pc.get(r).copied().unwrap_or(0), phi, value));
1249 }
1250 }
1251 }
1252}
1253
1254#[allow(unused)]
1255fn record_bucket(n: usize, t0: std::time::Instant) {
1256 static BUCKETS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1259 if !*BUCKETS_ON.get_or_init(|| std::env::var("DDC_BUCKETS").is_ok()) {
1260 return;
1261 }
1262 let bucket = if n <= 1 {
1263 0
1264 } else if n <= 5 {
1265 1
1266 } else if n <= 20 {
1267 2
1268 } else if n <= 100 {
1269 3
1270 } else {
1271 4
1272 };
1273 BUCKET_COUNT[bucket].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1274 BUCKET_MICROS[bucket].fetch_add(
1275 t0.elapsed().as_micros() as u64,
1276 std::sync::atomic::Ordering::Relaxed,
1277 );
1278}
1279
1280fn visit_phi_refs(e: &Expr, phis: &std::collections::HashSet<u32>, hit: &mut bool) {
1282 if *hit {
1283 return;
1284 }
1285 if let Expr::Local { var, .. } = e {
1286 if phis.contains(var) {
1287 *hit = true;
1288 }
1289 return;
1290 }
1291 let mut kids: Vec<&Expr> = Vec::new();
1292 match e {
1293 Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => kids.push(e),
1294 Expr::Bin { l, r, .. } => {
1295 kids.push(l);
1296 kids.push(r);
1297 }
1298 Expr::Cond { c, t, f } => {
1299 kids.push(c);
1300 kids.push(t);
1301 kids.push(f);
1302 }
1303 Expr::Assign { target, value, .. } => {
1304 kids.push(target);
1305 kids.push(value);
1306 }
1307 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => kids.push(e),
1308 Expr::Field { owner: Some(o), .. } => kids.push(o),
1309 Expr::Method {
1310 owner: Some(o),
1311 args,
1312 ..
1313 } => {
1314 kids.push(o);
1315 kids.extend(args.iter());
1316 }
1317 Expr::ArrayIndex { array, index } => {
1318 kids.push(array);
1319 kids.push(index);
1320 }
1321 Expr::New { args, .. } => kids.extend(args.iter()),
1322 Expr::NewArray { dims, init, .. } => {
1323 kids.extend(dims.iter());
1324 if let Some(v) = init {
1325 kids.extend(v.iter());
1326 }
1327 }
1328 Expr::NewMultiArray { dims, .. } => kids.extend(dims.iter()),
1329 _ => {}
1330 }
1331 for k in kids {
1332 visit_phi_refs(k, phis, hit);
1333 if *hit {
1334 return;
1335 }
1336 }
1337}
1338
1339fn debug_name_at(
1344 locals: &[ddc_dex::DebugLocal],
1345 slot: u16,
1346 pc: u32,
1347 ty: &jdc_core::types::JavaType,
1348) -> Option<String> {
1349 if locals.is_empty() {
1350 return None;
1351 }
1352 let hit = locals.iter().find(|l| {
1353 l.reg == slot
1354 && pc >= l.start
1355 && pc < l.end
1356 && l.ty.as_deref().map(|d| &crate::desc_type(d) == ty).unwrap_or(true)
1357 })?;
1358 let n = crate::classdec::java_ident(&hit.name);
1359 if n.is_empty() {
1360 None
1361 } else {
1362 Some(n.into_owned())
1363 }
1364}
1365
1366fn merge_states(
1367 vt: &mut VarTable,
1368 phis: &mut HashMap<(usize, u16), u32>,
1369 bid: usize,
1370 sides: &[&[Reg]],
1371 debug_locals: &[ddc_dex::DebugLocal],
1372 merge_pc: u32,
1373) -> Vec<Reg> {
1374 let len = sides.iter().map(|s| s.len()).max().unwrap_or(0);
1375 let mut out = vec![Reg::Undef; len];
1376 for (r, slot) in out.iter_mut().enumerate() {
1377 let mut all_eq = true;
1378 let mut first: Option<&Reg> = None;
1379 for s in sides {
1380 let v = s.get(r);
1381 match (first, v) {
1382 (None, v) => first = v,
1383 (Some(f), Some(v)) if f == v => {}
1384 _ => all_eq = false,
1385 }
1386 }
1387 if all_eq {
1388 *slot = first.cloned().unwrap_or(Reg::Undef);
1389 continue;
1390 }
1391 if std::env::var("DDC_DBG_PHI").is_ok() {
1393 let sides_dbg: Vec<String> = sides
1394 .iter()
1395 .map(|s| match s.get(r) {
1396 Some(Reg::Live(v)) => format!("L{}({})", v, vt.var(*v).ty.erased()),
1397 Some(Reg::Pending(e) | Reg::PendingCall(e)) => format!("P({})", e.type_ref().erased()),
1398 _ => "U".into(),
1399 })
1400 .collect();
1401 eprintln!("[phi-div] merge={} reg={} sides={:?}", bid, r, sides_dbg);
1402 }
1403 let key = (bid, r as u16);
1404 let phi = match phis.get(&key) {
1405 Some(&v) => v,
1406 None => {
1407 let ty = join_side_types(vt, sides, r);
1408 if let Some(n) = debug_name_at(debug_locals, r as u16, merge_pc, &ty.erased()) {
1416 let unified = vt
1417 .vars
1418 .iter()
1419 .find(|v| {
1420 v.slot == r as u16
1421 && !v.synthetic_name
1422 && v.name == n
1423 && v.ty.erased() == ty.erased()
1424 })
1425 .map(|v| v.id);
1426 let id = match unified {
1427 Some(id) => id,
1428 None => {
1429 let id = vt.vars.len() as u32;
1430 vt.vars.push(jdc_core::var::VarInfo {
1431 id,
1432 slot: r as u16,
1433 name: n,
1434 ty,
1435 is_param: false,
1436 range_start: 0,
1437 range_end: u16::MAX,
1438 synthetic_name: false,
1439 });
1440 while vt.by_slot.len() <= r {
1441 vt.by_slot.push(Vec::new());
1442 }
1443 vt.by_slot[r].push((0, u16::MAX, id));
1444 id
1445 }
1446 };
1447 phis.insert(key, id);
1448 id
1449 } else {
1450 let id = vt.vars.len() as u32;
1451 vt.vars.push(jdc_core::var::VarInfo {
1452 id,
1453 slot: r as u16,
1454 name: format!("v{}", id),
1455 ty,
1456 is_param: false,
1457 range_start: 0,
1458 range_end: u16::MAX,
1459 synthetic_name: true,
1460 });
1461 while vt.by_slot.len() <= r {
1462 vt.by_slot.push(Vec::new());
1463 }
1464 vt.by_slot[r].push((0, u16::MAX, id));
1465 phis.insert(key, id);
1466 id
1467 }
1468 }
1469 };
1470 *slot = Reg::Live(phi);
1471 }
1472 out
1473}
1474
1475fn join_side_types(vt: &VarTable, sides: &[&[Reg]], r: usize) -> TypeRef {
1477 let mut ty: Option<JavaType> = None;
1478 for s in sides {
1479 let st = s.get(r).cloned().unwrap_or(Reg::Undef);
1480 let e = match st {
1481 Reg::Pending(e) | Reg::PendingCall(e) => e,
1482 Reg::Live(v) => Expr::Local {
1483 var: v,
1484 ty: vt.var(v).ty.clone(),
1485 },
1486 _ => continue,
1487 };
1488 let t = e.type_ref().erased();
1489 ty = Some(match ty {
1490 None => t,
1491 Some(prev) => join_types(&prev, &t),
1492 });
1493 }
1494 TypeRef::J(ty.unwrap_or(JavaType::Int))
1495}
1496
1497fn join_types(a: &JavaType, b: &JavaType) -> JavaType {
1498 if a == b {
1499 return a.clone();
1500 }
1501 let num = |t: &JavaType| {
1502 matches!(
1503 t,
1504 JavaType::Boolean
1505 | JavaType::Byte
1506 | JavaType::Char
1507 | JavaType::Short
1508 | JavaType::Int
1509 | JavaType::Long
1510 | JavaType::Float
1511 | JavaType::Double
1512 )
1513 };
1514 if num(a) && num(b) {
1515 return match (a, b) {
1517 (JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
1518 (JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
1519 (JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
1520 _ => JavaType::Int,
1521 };
1522 }
1523 if let (JavaType::Object(x), JavaType::Object(y)) = (a, b) {
1524 if x == y {
1525 return a.clone();
1526 }
1527 return JavaType::Object("java/lang/Object".into());
1529 }
1530 JavaType::Object("java/lang/Object".into())
1531}
1532
1533#[allow(dead_code)]
1535fn is_exit_term(term: &jdc_core::ir::build::Term) -> bool {
1536 matches!(
1537 term,
1538 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
1539 )
1540}
1541
1542#[allow(dead_code)]
1543fn unused(_: &InsnKind) {}