Skip to main content

ddc_dec/
lift.rs

1//! Register-machine expression lifting: Dalvik instructions → jdc-core IR.
2//!
3//! The register file is modeled as per-register *views* of values:
4//! * `Live(v)` — the register equals local `v`, whose defining statement was
5//!   already emitted (SSA-ish: every materialization allocates a fresh var,
6//!   so a `Local` reference can never go stale);
7//! * `Pending(e)` — the register holds the expression `e`, whose store is
8//!   deferred so consumers can nest it (`return a + b`);
9//! * `PendingCall(e)` — the value of a just-executed invoke /
10//!   filled-new-array whose statement has not been emitted yet: consumed
11//!   exactly once (a materialization, a term, or a discarded-value
12//!   statement), never duplicated;
13//! * `WideHi` — the high half of a J/D pair.
14//!
15//! At block exit the terminator is built FIRST (terms may carry pending
16//! call expressions: `return foo();`), then unconsumed calls become
17//! statements and impure pendings materialize, so cross-block states only
18//! carry pure values, locals, and open `new`/`new-array` views.
19
20use ddc_dex::insn::{ArithOp, CmpKind, CmpOp, Insn, InsnKind, InvokeKind, Payload};
21use ddc_dex::{CodeItem, DexFile};
22use jdc_core::ir::build::{has_side_effects, BlockResult, SwitchTargets, Term};
23use jdc_core::ir::expr::{AssignOp, BinOp, ConcatPart, ConstVal, Expr, TypeRef, UnOp};
24use jdc_core::ir::stmt::Stmt;
25use jdc_core::types::{JavaType, MethodDescriptor};
26use jdc_core::var::{VarInfo, VarTable};
27
28use crate::DexPool;
29
30pub type BResult<T> = Result<T, String>;
31
32/// Cheap per-method feature flags accumulated by the lifter: which
33/// post-lift passes could possibly match anything. Most methods use none of
34/// these — gating their passes skips full-tree analysis walks.
35#[derive(Clone, Copy, Default, PartialEq)]
36pub struct MethodFlags(u8);
37
38impl MethodFlags {
39    const SB: u8 = 1;
40    const MONITOR: u8 = 2;
41    const CMP: u8 = 4;
42    pub fn none() -> MethodFlags {
43        MethodFlags(0)
44    }
45    pub fn with_sb(self) -> MethodFlags {
46        MethodFlags(self.0 | Self::SB)
47    }
48    pub fn with_monitor(self) -> MethodFlags {
49        MethodFlags(self.0 | Self::MONITOR)
50    }
51    pub fn with_cmp(self) -> MethodFlags {
52        MethodFlags(self.0 | Self::CMP)
53    }
54    pub fn has_sb(self) -> bool {
55        self.0 & Self::SB != 0
56    }
57    pub fn has_monitor(self) -> bool {
58        self.0 & Self::MONITOR != 0
59    }
60    pub fn has_cmp(self) -> bool {
61        self.0 & Self::CMP != 0
62    }
63    pub fn merge(self, o: MethodFlags) -> MethodFlags {
64        MethodFlags(self.0 | o.0)
65    }
66}
67
68/// Everything one method body needs from its DEX image.
69pub struct MethodEnv<'a> {
70    pub pool: &'a DexPool,
71    /// Image index of `dex` in the pool (keys the pool's ref caches).
72    pub di: usize,
73    pub dex: &'a DexFile,
74    pub code: &'a CodeItem,
75    pub class_name: String,
76    pub method_name: std::sync::Arc<str>,
77    pub desc: MethodDescriptor,
78    /// Debug-info local table (empty for release/stripped builds):
79    /// real source names per (register, pc-range).
80    pub debug_locals: Vec<ddc_dex::DebugLocal>,
81    pub is_static: bool,
82    /// Total code units — the CFG owns the insns stream, so the lifter reads
83    /// the extent from here instead of `code.insns` (hollowed after the
84    /// CFG build moved it).
85    pub code_units: u32,
86}
87
88impl<'a> MethodEnv<'a> {
89    pub fn type_name(&self, idx: u32) -> String {
90        self.dex.type_name(idx).to_string()
91    }
92    pub fn java_type(&self, idx: u32) -> JavaType {
93        // Shared per-image parse (was desc-reparse per instruction).
94        (*self.pool.type_java(self.di, idx)).clone()
95    }
96    pub fn type_name_arc(&self, idx: u32) -> std::sync::Arc<str> {
97        self.pool.type_name_arc(self.di, idx)
98    }
99    pub fn field_ref(
100        &self,
101        idx: u32,
102    ) -> (
103        std::sync::Arc<str>,
104        std::sync::Arc<str>,
105        JavaType,
106    ) {
107        let f = self.dex.field(idx);
108        let owner = std::sync::Arc::from(self.dex.class_name(f.class_idx).as_str());
109        let raw = self.dex.string(f.name_idx);
110        // Colliding obfuscated field names (synthetic outer ref renamed
111        // to a one-char name beside a real field) resolve differently per
112        // site in source — run the shared rename registry so every
113        // reference prints the same display name as the declaration.
114        let name = if jdc_core::rename::owner_has_renames(&owner) {
115            jdc_core::rename::field_display(&owner, raw, self.dex.type_name(f.type_idx))
116                .map(std::sync::Arc::from)
117                .unwrap_or_else(|| std::sync::Arc::from(raw))
118        } else {
119            std::sync::Arc::from(raw)
120        };
121        (owner, name, self.java_type(f.type_idx))
122    }
123    pub fn method_ref(
124        &self,
125        idx: u32,
126    ) -> (
127        std::sync::Arc<str>,
128        std::sync::Arc<str>,
129        std::sync::Arc<MethodDescriptor>,
130    ) {
131        // Was: N param Strings + format! + descriptor RE-PARSE per invoke
132        // (6-10 allocations per call instruction). All three components
133        // are shared table entries now — refcount bumps.
134        let m = self.dex.method(idx);
135        let owner = std::sync::Arc::from(self.dex.class_name(m.class_idx).as_str());
136        let desc = self.pool.proto_desc_parsed(self.di, m.proto_idx);
137        // Sanitizer-collapsed method names (whole `ERROR_中文` families on
138        // weibo render one `ERROR________`) need the shared member-rename
139        // registry so declarations and call sites agree. Cold path only:
140        // clean corpora never rebuild the descriptor string.
141        let name = if jdc_core::rename::owner_has_renames(&owner) {
142            let raw = self.dex.string(m.name_idx);
143            jdc_core::rename::field_display(&owner, raw, &desc.to_string())
144                .map(std::sync::Arc::from)
145                .unwrap_or_else(|| std::sync::Arc::from(raw))
146        } else {
147            std::sync::Arc::from(self.dex.string(m.name_idx))
148        };
149        (owner, name, desc)
150    }
151}
152
153/// Per-register value view (see module docs).
154#[derive(Debug, Clone, PartialEq)]
155pub enum Reg {
156    Undef,
157    Live(u32),
158    Pending(Expr),
159    PendingCall(Expr),
160    WideHi,
161}
162
163/// Block-exit register state (machine-neutral view plus origin tracking).
164#[derive(Debug, Clone)]
165pub struct OutState {
166    pub regs: Vec<Reg>,
167    /// Per register: the pc of the instruction that produced its value
168    /// (orders merge materializations by original instruction order).
169    pub write_pc: Vec<u32>,
170}
171
172/// The registers an instruction READS as operands (receivers, arguments,
173/// move sources — destination registers excluded). Drives the block
174/// lookahead that decides when an allocation view must materialize.
175pub(crate) fn src_regs(kind: &InsnKind) -> Vec<u16> {
176    let mut out: Vec<u16> = Vec::with_capacity(4);
177    let mut push = |r: u16| out.push(r);
178    match kind {
179        InsnKind::Nop
180        | InsnKind::ReturnVoid
181        | InsnKind::Unknown
182        | InsnKind::NewInstance { .. }
183        | InsnKind::SGet { .. } => {}
184        InsnKind::Move { src, .. }
185        | InsnKind::Un { src, .. }
186        | InsnKind::Return { src }
187        | InsnKind::ArrayLength { src, .. } => push(*src),
188        InsnKind::MonitorEnter { reg }
189        | InsnKind::MonitorExit { reg }
190        | InsnKind::Throw { reg }
191        | InsnKind::CheckCast { reg, .. }
192        | InsnKind::FillArrayData { reg, .. }
193        | InsnKind::PackedSwitch { reg, .. }
194        | InsnKind::SparseSwitch { reg, .. } => push(*reg),
195        InsnKind::MoveResult { .. } | InsnKind::MoveException { .. } => {}
196        InsnKind::Const { .. }
197        | InsnKind::ConstClass { .. }
198        | InsnKind::ConstString { .. }
199        | InsnKind::ConstMethodHandle { .. }
200        | InsnKind::ConstMethodType { .. } => {}
201        InsnKind::InstanceOf { src, .. } => {
202            push(*src);
203        }
204        InsnKind::NewArray { size, .. } => {
205            push(*size);
206        }
207        InsnKind::FilledNewArray { regs, .. } | InsnKind::InvokeCustom { regs, .. } => {
208            for r in regs {
209                push(*r);
210            }
211        }
212        InsnKind::Goto { .. } => {}
213        InsnKind::Cmp { a, b, .. } | InsnKind::Bin { a, b, .. } => {
214            push(*a);
215            push(*b);
216        }
217        InsnKind::BinLit { a, .. } => push(*a),
218        InsnKind::If { a, b, z, .. } => {
219            push(*a);
220            if !*z {
221                push(*b);
222            }
223        }
224        InsnKind::AGet { array, index, .. } => {
225            push(*array);
226            push(*index);
227        }
228        InsnKind::APut {
229            value,
230            array,
231            index,
232            ..
233        } => {
234            push(*value);
235            push(*array);
236            push(*index);
237        }
238        InsnKind::IGet { obj, .. } => {
239            push(*obj);
240        }
241        InsnKind::IPut { value, obj, .. } => {
242            push(*value);
243            push(*obj);
244        }
245        InsnKind::SPut { value, .. } => {
246            push(*value);
247        }
248        InsnKind::Invoke { regs, .. } => {
249            for r in regs {
250                push(*r);
251            }
252        }
253    }
254    out
255}
256
257/// Registers an instruction READS as operands.
258/// Registers an instruction DEFINES (new value generations).
259fn dst_regs(kind: &InsnKind) -> Vec<u16> {
260    let mut out: Vec<u16> = Vec::with_capacity(2);
261    let mut push = |r: u16| out.push(r);
262    match kind {
263        InsnKind::Move { dst, .. }
264        | InsnKind::MoveResult { dst }
265        | InsnKind::MoveException { dst }
266        | InsnKind::Const { dst, .. }
267        | InsnKind::ConstClass { dst, .. }
268        | InsnKind::ConstString { dst, .. }
269        | InsnKind::ConstMethodHandle { dst, .. }
270        | InsnKind::ConstMethodType { dst, .. }
271        | InsnKind::NewInstance { dst, .. }
272        | InsnKind::NewArray { dst, .. }
273        | InsnKind::InstanceOf { dst, .. }
274        | InsnKind::ArrayLength { dst, .. }
275        | InsnKind::Cmp { dst, .. }
276        | InsnKind::AGet { dst, .. }
277        | InsnKind::IGet { dst, .. }
278        | InsnKind::SGet { dst, .. }
279        | InsnKind::Un { dst, .. }
280        | InsnKind::Bin { dst, .. }
281        | InsnKind::BinLit { dst, .. } => push(*dst),
282        _ => {}
283    }
284    out
285}
286
287/// The (pc, reg) reads that are the final read of the register's current
288/// generation: walking the register's events in pc order, a read is
289/// final when no other read of the same register precedes its next WRITE
290/// (a write starts a new generation — the old value is dead there).
291pub(crate) fn compute_final_reads(
292    cfg: &crate::cfg::DexCfg,
293) -> std::sync::Arc<jdc_core::FxHashSet<(u32, u16)>> {
294    use jdc_core::FxHashMap as HashMap;
295    // reg → (pc, is_read) events in EXECUTION order, not pc order.
296    //
297    // pc order interleaves exception handlers into the normal flow:
298    // their move-exception definitions sat BETWEEN a protected block's
299    // read and the reads of the blocks that actually follow it, and the
300    // "next event is a write ⇒ final read" rule consumed allocation
301    // views at reads that still had live uses (weixin a03/j.get: the
302    // handler's `move-exception v0` at 0024 shadowed the iput read at
303    // 001c; the New view got consumed there, the create path's phi
304    // value became Undef and the method returned the stale pre-write
305    // read). Reverse postorder over NORMAL edges matches execution;
306    // exception-only and dead blocks are appended after — their own
307    // events still participate, they just no longer interrupt.
308    let order = cfg.rpo_order();
309    let mut events: HashMap<u16, Vec<(u32, bool)>> = HashMap::default();
310    for &bid in &order {
311        for i in cfg.block_ins(&cfg.blocks[bid]) {
312            for r in src_regs(&i.kind) {
313                events.entry(r).or_default().push((i.pc, true));
314            }
315            for r in dst_regs(&i.kind) {
316                events.entry(r).or_default().push((i.pc, false));
317            }
318        }
319    }
320    let mut out = jdc_core::FxHashSet::default();
321    for (reg, evs) in events {
322        let mut dedup: Vec<(u32, bool)> = Vec::with_capacity(evs.len());
323        for (pc, is_read) in evs {
324            if dedup.last() != Some(&(pc, is_read)) {
325                dedup.push((pc, is_read));
326            }
327        }
328        for (i, &(pc, is_read)) in dedup.iter().enumerate() {
329            if !is_read {
330                continue;
331            }
332            // Final read of the current generation: no other read before
333            // the register's next write.
334            let mut final_ = true;
335            if let Some(&(_, is_read2)) = dedup.get(i + 1) {
336                if is_read2 {
337                    final_ = false;
338                }
339            }
340            if final_ {
341                out.insert((pc, reg));
342            }
343        }
344    }
345    std::sync::Arc::new(out)
346}
347
348pub struct Lifter<'a> {
349    pub env: &'a MethodEnv<'a>,
350    pub vt: &'a mut VarTable,
351    /// Owning block id (stable-var registry key).
352    block_id: usize,
353    /// (block, register) → var id, shared across REBUILDS: a re-lift of
354    /// the same block with the same input reuses the same materialized
355    /// variables, so the output state is id-stable. Without this, every
356    /// rebuild allocated fresh ids, the out-state compared unequal, and
357    /// the worklist cascaded re-queuing until the visit cap (~60
358    /// rebuilds per block, 30M lifts on weibo).
359    stable: &'a mut jdc_core::FxHashMap<(usize, u16, u64), u32>,
360    regs: Vec<Reg>,
361    write_pc: Vec<u32>,
362    stmts: Vec<Stmt>,
363    pending_call: Option<Expr>,
364    code_units: u32,
365    /// Current instruction's pc (drives the allocation materialization
366    /// lookahead below).
367    cur_pc: u32,
368    /// (pc, register) reads that are the FINAL read of the register's
369    /// current value generation: no other read before the register's
370    /// next write. An allocation view (post-fold `New` / `NewArray`)
371    /// may only be inlined at a final read; a register reused for a
372    /// call result and read again (greet → move-result v0 → println(v0))
373    /// is a new generation, not another use of the allocation.
374    final_read: std::sync::Arc<jdc_core::FxHashSet<(u32, u16)>>,
375    /// Method-level feature flags, merged in place as features are seen
376    /// (build_block consumes the lifter, so the flags must escape via a
377    /// shared reference rather than a field read afterwards).
378    mflags: &'a mut MethodFlags,
379    /// pcs of field reads whose value view MUST materialize at its
380    /// defining instruction: a later same-field write in this block would
381    /// otherwise kill the view while it is still pending, and the use-site
382    /// re-render would read the field AFTER the write (y5/e$b.a:
383    /// `iget v6,this.h.f; iput this.h=null; invoke …(v6)` rendered
384    /// `this.h = null; j((String) this.h.f, …)` — runtime NPE).
385    force_mat: jdc_core::FxHashSet<u32>,
386}
387
388impl<'a> Lifter<'a> {
389    pub fn new(
390        env: &'a MethodEnv<'a>,
391        vt: &'a mut VarTable,
392        in_regs: Vec<Reg>,
393        block_id: usize,
394        stable: &'a mut jdc_core::FxHashMap<(usize, u16, u64), u32>,
395        mflags: &'a mut MethodFlags,
396    ) -> Lifter<'a> {
397        let n = env.code.registers_size as usize;
398        let n = n.max(in_regs.len()).max(1);
399        let mut regs = in_regs;
400        regs.resize(n, Reg::Undef);
401        Lifter {
402            env,
403            vt,
404            regs,
405            write_pc: vec![0; n],
406            stmts: Vec::new(),
407            block_id,
408            stable,
409            pending_call: None,
410            code_units: env.code_units,
411            cur_pc: 0,
412            final_read: std::sync::Arc::new(jdc_core::FxHashSet::default()),
413            mflags,
414            force_mat: jdc_core::FxHashSet::default(),
415        }
416    }
417
418    // -- variable creation ---------------------------------------------------
419
420    /// Cheap 64-bit type fingerprint for the stable-var key: hashing the
421    /// JavaType itself (string bytes + tuple) ran on EVERY register
422    /// materialization; the fingerprint is one pass with no clones
423    /// (`erased()` deep-clones Array chains). G is unreachable in DEX
424    /// lift (no generic signatures) — a coarse tag suffices.
425    fn ty_key(ty: &TypeRef) -> u64 {
426        use std::hash::{Hash, Hasher};
427        fn jt_key(j: &JavaType, h: &mut jdc_core::fx::FxHasher) {
428            match j {
429                JavaType::Void => 1u64.hash(h),
430                JavaType::Boolean => 2u64.hash(h),
431                JavaType::Byte => 3u64.hash(h),
432                JavaType::Char => 4u64.hash(h),
433                JavaType::Short => 5u64.hash(h),
434                JavaType::Int => 6u64.hash(h),
435                JavaType::Float => 7u64.hash(h),
436                JavaType::Long => 8u64.hash(h),
437                JavaType::Double => 9u64.hash(h),
438                JavaType::Object(n) => {
439                    10u64.hash(h);
440                    n.hash(h);
441                }
442                JavaType::Array(inner) => {
443                    11u64.hash(h);
444                    jt_key(inner, h);
445                }
446            }
447        }
448        let mut h = jdc_core::fx::FxHasher::default();
449        match ty {
450            TypeRef::J(j) => jt_key(j, &mut h),
451            TypeRef::G(_) => 99u64.hash(&mut h),
452        }
453        h.finish()
454    }
455
456    fn fresh_var(&mut self, slot: u16, ty: TypeRef) -> u32 {
457        // Keyed by TYPE as well as (block, slot): d8 reuses one register
458        // for values of different types (`v1: byte[]` then `v1: String`
459        // in the same method — the lab package's Main.run). A Java local
460        // cannot change type — the old (block, slot) key reused one var
461        // and then RETYPED it in place, emitting `byte[] v1 = …; v1 =
462        // Obf.s(v1, 90);` (String into byte[]) and kin all over the
463        // corpus. Distinct types mint distinct vars; rebuild stability
464        // (the worklist-convergence fix) holds because the key is a pure
465        // function of the instruction stream.
466        let key = (self.block_id, slot, Self::ty_key(&ty));
467        if let Some(&id) = self.stable.get(&key) {
468            return id;
469        }
470        // Real source name from the debug-info local table when a range
471        // covers (pc, slot); otherwise the synthetic `vN`. apply_local_
472        // names never renames non-synthetic vars and de-duplicates the
473        // (legal-in-source, illegal-in-flat-Java) same-name scopes.
474        if let Some(name) = self.debug_name(slot, &ty) {
475            // Unify same register+type+name vars across blocks: the debug
476            // table says they are ONE source local, and per-block copies
477            // degrade to `out`/`out2`/`out3` suffix soup after de-dup.
478            // Dalvik registers are method-scoped, so one Java local shared
479            // by every write site is exactly register semantics.
480            let tkey = Self::ty_key(&ty);
481            if let Some(id) = self
482                .vt
483                .vars
484                .iter()
485                .find(|v| {
486                    v.slot == slot
487                        && !v.synthetic_name
488                        && v.name == name
489                        && Self::ty_key(&v.ty) == tkey
490                })
491                .map(|v| v.id)
492            {
493                self.stable.insert(key, id);
494                return id;
495            }
496            let id = self.vt.vars.len() as u32;
497            self.stable.insert(key, id);
498            self.push_var(id, slot, name, ty, false);
499            return id;
500        }
501        let id = self.vt.vars.len() as u32;
502        let name = format!("v{}", id);
503        self.stable.insert(key, id);
504        self.push_var(id, slot, name, ty, true);
505        id
506    }
507
508    fn push_var(&mut self, id: u32, slot: u16, name: String, ty: TypeRef, synthetic: bool) {
509        self.vt.vars.push(VarInfo {
510            id,
511            slot,
512            name,
513            ty,
514            is_param: false,
515            range_start: 0,
516            range_end: u16::MAX,
517            synthetic_name: synthetic,
518        });
519        while self.vt.by_slot.len() <= slot as usize {
520            self.vt.by_slot.push(Vec::new());
521        }
522        self.vt.by_slot[slot as usize].push((0, u16::MAX, id));
523    }
524
525    /// The source-level name covering `(cur_pc, slot)` in the debug
526    /// local table, sanitized into a legal Java identifier. Linear scan:
527    /// the table is empty in release APKs (early-out) and small in
528    /// debug builds.
529    fn debug_name(&self, slot: u16, ty: &TypeRef) -> Option<String> {
530        let locals = &self.env.debug_locals;
531        if locals.is_empty() {
532            return None;
533        }
534        let want = ty.erased();
535        let covers = |pc: u32| {
536            locals.iter().find(|l| {
537                l.reg == slot
538                    && pc >= l.start
539                    && pc < l.end
540                    // Type gate: register reuse mints vars of a DIFFERENT
541                    // type inside the same range (`new-array v0, v0` reads
542                    // the old v0 as an int size inside the new v0's [C
543                    // range) — naming that junk var stole the real name.
544                    && l.ty.as_deref().map(|d| crate::desc_type(d) == want).unwrap_or(true)
545            })
546        };
547        // Materializations at block merges run BEFORE any instruction of
548        // the block (cur_pc is stale/zero) — fall back to the pc where
549        // the register's current value was BORN (write_pc), which is
550        // what the debug range actually describes.
551        let hit = covers(self.cur_pc).or_else(|| {
552            let wpc = *self.write_pc.get(slot as usize)?;
553            if wpc == 0 {
554                None
555            } else {
556                covers(wpc)
557            }
558        })?;
559        let name = crate::classdec::java_ident(&hit.name);
560        if name.is_empty() {
561            return None;
562        }
563        Some(name.into_owned())
564    }
565
566    fn local_expr(&self, v: u32) -> Expr {
567        Expr::Local {
568            var: v,
569            ty: self.vt.var(v).ty.clone(),
570        }
571    }
572
573    /// Registers still holding a move-object alias of the folded
574    /// construction in `r`. The ctor fold writes one `new C(args)` view
575    /// into every copy of the allocation (keyed by the origin write_pc
576    /// the Move handler propagates from the new-instance site): one
577    /// copy being consumed or materialized must retire the whole set —
578    /// a surviving copy would re-emit the construction.
579    fn folded_new_aliases(&self, r: u16) -> Vec<usize> {
580        let Some(w) = self.write_pc.get(r as usize).copied() else {
581            return Vec::new();
582        };
583        (0..self.regs.len())
584            .filter(|&rr| {
585                rr != r as usize
586                    && self.write_pc.get(rr).copied() == Some(w)
587                    && matches!(self.regs[rr], Reg::Pending(Expr::New { raw: false, .. }))
588            })
589            .collect()
590    }
591
592    // -- register access -----------------------------------------------------
593
594    /// Read for nesting into another expression (clones the value).
595    #[track_caller]
596    fn read_nest(&mut self, r: u16) -> Expr {
597        match self.regs.get(r as usize).cloned().unwrap_or(Reg::Undef) {
598            Reg::Undef | Reg::WideHi => {
599                // Reading a register never written on this path (or beyond
600                // the frame): a fresh (never-assigned) local keeps the
601                // output compilable.
602                let ty = TypeRef::J(JavaType::Int);
603                let v = self.fresh_var(r, ty);
604                if (r as usize) < self.regs.len() {
605                    self.regs[r as usize] = Reg::Live(v);
606                }
607                self.local_expr(v)
608            }
609            Reg::Live(v) => self.local_expr(v),
610            Reg::Pending(e) => {
611                // An allocation view (`NewArray`, or a post-fold `New`)
612                // may be emitted inline only when THIS read is the
613                // register's final one: any later read would re-emission
614                // a FRESH allocation (`sput v0, sparse; aput v2, v0, v1`
615                // printed three distinct `new byte[4]`; a discarded-
616                // result StringBuilder chain printed one `new
617                // StringBuilder()` per append). Materialize when a later
618                // read exists; inline the last use, keeping
619                // `foo(new Bar())` nesting. `New { raw: true }` is
620                // always exempt: the constructor fold consumes the raw
621                // view at the invoke-direct site.
622                //
623                // A folded construction ALIASED into a second register
624                // (move-object) counts as a later read too — one `new
625                // C(args)` cannot be inlined at one alias and survive as
626                // a pending copy on the other. Materialize so both
627                // aliases share the single construction var.
628                //
629                // RAW pendings are no longer exempt from materialization
630                // either (only from the fold path, which reads the
631                // register state directly): when the ctor invoke never
632                // arrives (init lost to a degraded region), every read
633                // used to clone a FRESH `new Q.d()` — multi-allocation
634                // semantic corruption rendered as `new Q.d().d2 = v35;`
635                // temp-write shapes (uuyc ctor-mismatch ×1.4k).
636                let alias_needs_share = matches!(&e, Expr::New { raw: false, .. })
637                    && self.folded_new_aliases(r).iter().any(|&rr| {
638                        self.final_read
639                            .iter()
640                            .any(|&(pc2, r2)| r2 == rr as u16 && pc2 >= self.cur_pc)
641                    });
642                let alloc = alias_needs_share
643                    || matches!(&e, Expr::NewArray { .. })
644                    || (matches!(&e, Expr::New { .. })
645                        && !self.final_read.contains(&(self.cur_pc, r)));
646                if alloc {
647                    let v = self.materialize(r);
648                    self.local_expr(v)
649                } else {
650                    // Inline AND consume: this is the register's final
651                    // read in the block, yet leaving the Pending view in
652                    // place made the block-exit materialization emit the
653                    // SAME allocation a second time (`this.last = new
654                    // Report(..); new Report(..);` — a duplicated
655                    // observable side effect; lab package Guard.report).
656                    // `New { raw: true }` is EXEMPT: the ctor fold reads
657                    // the raw view at the invoke-direct site and must
658                    // still find it (consuming it there broke every
659                    // constructor fold).
660                    if matches!(&e, Expr::NewArray { .. })
661                        || matches!(&e, Expr::New { raw: false, .. })
662                    {
663                        let aliases = if matches!(&e, Expr::New { raw: false, .. }) {
664                            self.folded_new_aliases(r)
665                        } else {
666                            Vec::new()
667                        };
668                        if let Some(slot) = self.regs.get_mut(r as usize) {
669                            *slot = Reg::Undef;
670                        }
671                        // Consuming the last use retires the aliased
672                        // copies too (none of them is read again — the
673                        // share branch above materialized otherwise).
674                        for rr in aliases {
675                            self.regs[rr] = Reg::Undef;
676                        }
677                    }
678                    e
679                }
680            }
681            Reg::PendingCall(_) => {
682                let v = self.materialize(r);
683                self.local_expr(v)
684            }
685        }
686    }
687
688    /// Read for a context that evaluates exactly once (term payloads): the
689    /// value is consumed, not cloned — otherwise the block-exit
690    /// materialization would emit it a second time.
691    fn read_term(&mut self, r: u16) -> Expr {
692        match self.regs.get(r as usize).cloned().unwrap_or(Reg::Undef) {
693            Reg::PendingCall(e) | Reg::Pending(e) => {
694                // Terms terminate the block: no later read can follow, so a
695                // folded construction consumed here retires its aliased
696                // copies as well.
697                let aliases = if matches!(&e, Expr::New { raw: false, .. }) {
698                    self.folded_new_aliases(r)
699                } else {
700                    Vec::new()
701                };
702                if (r as usize) < self.regs.len() {
703                    self.regs[r as usize] = Reg::Undef;
704                }
705                for rr in aliases {
706                    self.regs[rr] = Reg::Undef;
707                }
708                self.pending_call = None;
709                e
710            }
711            _ => self.read_nest(r),
712        }
713    }
714
715    fn write(&mut self, r: u16, e: Expr, pc: u32, wide: bool) {
716        if r as usize >= self.regs.len() {
717            return;
718        }
719        self.write_pc[r as usize] = pc;
720        // A view that a later same-field write in this block kills must
721        // materialize HERE (at the bytecode read position), or the use
722        // re-renders the field read past the write.
723        if self.force_mat.contains(&pc) {
724            let e = value_of_cmp(&e);
725            let v = self.materialize_value(r, e);
726            self.regs[r as usize] = Reg::Live(v);
727            if wide {
728                self.mark_wide_hi(r + 1, pc);
729            }
730            return;
731        }
732        // Loop-carried Pending trees grow multiplicatively across fixpoint
733        // rounds (merge clones the whole tree per side per visit); past
734        // this size the register materializes, which caps every state at
735        // a bounded expression over stable locals.
736        if expr_size(&e) > 32 {
737            self.write_pc[r as usize] = pc;
738            let v = self.materialize_value(r, e);
739            if (r as usize) < self.regs.len() {
740                self.regs[r as usize] = Reg::Live(v);
741            }
742            if wide {
743                self.mark_wide_hi(r + 1, pc);
744            }
745            return;
746        }
747        self.regs[r as usize] = Reg::Pending(e);
748        if wide {
749            self.mark_wide_hi(r + 1, pc);
750        }
751    }
752
753    /// Materialize WITHOUT consuming the register slot's caller contract:
754    /// emit `v = e` and return the var id (used by the size cap).
755    fn materialize_value(&mut self, r: u16, e: Expr) -> u32 {
756        let e = value_of_cmp(&e);
757        let ty = e.type_ref();
758        // fresh_var is type-keyed: a retyped register gets a FRESH var,
759        // so no in-place retyping here (each var keeps its mint type and
760        // its LocalDef label always matches its init).
761        let v = self.fresh_var(r, ty);
762        self.stmts.push(Stmt::LocalDef {
763            var: v,
764            init: Some(e),
765            is_final: false,
766            force_type: true,
767        });
768        v
769    }
770
771    /// Write a value whose evaluation already happened (invoke /
772    /// filled-new-array results): consumed exactly once.
773    fn write_call_result(&mut self, r: u16, e: Expr, pc: u32) {
774        if r as usize >= self.regs.len() {
775            return;
776        }
777        self.write_pc[r as usize] = pc;
778        self.regs[r as usize] = Reg::PendingCall(e);
779    }
780
781    fn mark_wide_hi(&mut self, r: u16, pc: u32) {
782        if (r as usize) < self.regs.len() {
783            self.regs[r as usize] = Reg::WideHi;
784            self.write_pc[r as usize] = pc;
785        }
786    }
787
788    /// Materialize every pending view whose expression tree reads the
789    /// given field anywhere (top-level, receiver chain, or nested call
790    /// arguments): the write about to land invalidates the view, and a
791    /// later use would re-render the read at its own position — past the
792    /// write. Materializing HERE puts the temp before the write, strictly
793    /// closer to the bytecode's read position than any use site.
794    fn materialize_views_mentioning(
795        &mut self,
796        cls: &std::sync::Arc<str>,
797        name: &std::sync::Arc<str>,
798        is_static: bool,
799    ) {
800        let mut stale: Vec<u16> = Vec::new();
801        for (i, r) in self.regs.iter().enumerate() {
802            match r {
803                Reg::Pending(e) | Reg::PendingCall(e)
804                    if expr_mentions_field(e, cls, name, is_static) =>
805                {
806                    stale.push(i as u16);
807                }
808                _ => {}
809            }
810        }
811        for r in stale {
812            self.materialize(r);
813        }
814    }
815
816    /// Emit `v = expr` and mark the register `Live(v)`.
817    fn materialize(&mut self, r: u16) -> u32 {
818        // TAKE the slot's expression (single consumer: this materialization
819        // replaces the register with Live(v)) — the old `.cloned()` deep-
820        // cloned the whole pending Expr and then dropped the original.
821        let cur = std::mem::replace(
822            self.regs.get_mut(r as usize).unwrap_or(&mut Reg::Undef),
823            Reg::Undef,
824        );
825        let e = match cur {
826            Reg::Pending(e) | Reg::PendingCall(e) => e,
827            Reg::Live(v) => {
828                if (r as usize) < self.regs.len() {
829                    self.regs[r as usize] = Reg::Live(v);
830                }
831                return v;
832            }
833            // Undef (a value flowing in from an unanalyzed path): a fresh
834            // local, NOT Const 0 — `0.new a()` was not even valid Java.
835            _ => {
836                let v = self.fresh_var(r, TypeRef::J(JavaType::Object("java/lang/Object".into())));
837                self.stmts.push(Stmt::LocalDef {
838                    var: v,
839                    init: None,
840                    is_final: false,
841                    force_type: false,
842                });
843                if (r as usize) < self.regs.len() {
844                    self.regs[r as usize] = Reg::Live(v);
845                }
846                return v;
847            }
848        };
849        // cmp sentinels stored as values become library compare calls.
850        let e = value_of_cmp_owned(e);
851        let ty = e.type_ref();
852        // fresh_var is type-keyed: a retyped register gets a FRESH var,
853        // so no in-place retyping here (each var keeps its mint type and
854        // its LocalDef label always matches its init).
855        let v = self.fresh_var(r, ty);
856        self.stmts.push(Stmt::LocalDef {
857            var: v,
858            init: Some(e),
859            is_final: false,
860            force_type: true,
861        });
862        if (r as usize) < self.regs.len() {
863            self.regs[r as usize] = Reg::Live(v);
864        }
865        // A folded construction materialized from ONE alias binds every
866        // alias to the same var — the copies would otherwise re-emit the
867        // construction at their own read (or orphan at block exit).
868        for rr in self.folded_new_aliases(r) {
869            self.regs[rr] = Reg::Live(v);
870        }
871        v
872    }
873
874    /// Discard an unconsumed call result (invoke without move-result).
875    fn drop_pending_call(&mut self) {
876        if let Some(e) = self.pending_call.take() {
877            let e = value_of_cmp(&e);
878            if has_side_effects(&e) {
879                self.stmts.push(Stmt::ExprStmt(e));
880            }
881        }
882    }
883
884    // -- helpers -------------------------------------------------------------
885
886    fn owner_expr(&self, obj: Expr, cls: &str) -> Option<Box<Expr>> {
887        if cls == self.env.class_name && matches!(obj, Expr::This) {
888            return None;
889        }
890        Some(Box::new(obj))
891    }
892
893    fn arith_op(op: ArithOp) -> BinOp {
894        match op {
895            ArithOp::Add => BinOp::Add,
896            ArithOp::Sub => BinOp::Sub,
897            ArithOp::Mul => BinOp::Mul,
898            ArithOp::Div => BinOp::Div,
899            ArithOp::Rem => BinOp::Rem,
900            ArithOp::And => BinOp::And,
901            ArithOp::Or => BinOp::Or,
902            ArithOp::Xor => BinOp::Xor,
903            ArithOp::Shl => BinOp::Shl,
904            ArithOp::Shr => BinOp::Shr,
905            ArithOp::Ushr => BinOp::Ushr,
906        }
907    }
908
909    fn cmp_op(op: CmpOp) -> BinOp {
910        match op {
911            CmpOp::Eq => BinOp::Eq,
912            CmpOp::Ne => BinOp::Ne,
913            CmpOp::Lt => BinOp::Lt,
914            CmpOp::Ge => BinOp::Ge,
915            CmpOp::Gt => BinOp::Gt,
916            CmpOp::Le => BinOp::Le,
917        }
918    }
919
920    fn cmp_sentinel(kind: CmpKind, l: Expr, r: Expr) -> Expr {
921        let name = match kind {
922            CmpKind::CmplF => "\0cmpl-float",
923            CmpKind::CmpgF => "\0cmpg-float",
924            CmpKind::CmplD => "\0cmpl-double",
925            CmpKind::CmpgD => "\0cmpg-double",
926            CmpKind::CmpJ => "\0cmp-long",
927        };
928        Expr::Invokedynamic {
929            name: name.into(),
930            desc: MethodDescriptor {
931                args: vec![],
932                ret: JavaType::Int,
933            },
934            args: vec![l, r],
935            bsm_text: String::new(),
936            bsm_static_args: vec![],
937        }
938    }
939
940    // -- the main loop ---------------------------------------------------------
941
942    /// Lift one block. `handler_types` gives catch types for ranges this
943    /// block handles (for move-exception typing).
944    pub fn build_block(
945        mut self,
946        ins: &[Insn],
947        handler_types: &[Option<std::sync::Arc<str>>],
948        final_read: std::sync::Arc<jdc_core::FxHashSet<(u32, u16)>>,
949    ) -> BResult<(BlockResult, OutState)> {
950        let payloads = &self.env.code.payloads;
951        // METHOD-wide final-read set (computed once by the caller over
952        // the full instruction stream). The block-scoped computation
953        // this replaced marked a block's LAST read of a register as
954        // final even when LATER BLOCKS read the same generation: the
955        // alloc inlined-and-consumed at the block tail, and the
956        // downstream reads minted fresh never-assigned int locals
957        // (`new c(..).e = new HashMap();` + `v28.e` with `int v28` —
958        // weixin tf5/e, the dominant 无法取消引用int shape once the
959        // obscuring cascades stopped suppressing it).
960        self.final_read = final_read;
961        // Field views a later same-field write in this block kills: the
962        // register holds the READ expression as a pending view; the write
963        // only replaces the written register, so the stale view re-renders
964        // at its use (past the write). Materialize such views at their
965        // DEFINING pc — the temp local is exactly the source's temporary.
966        {
967            let mut field_writes: jdc_core::FxHashMap<
968                (std::sync::Arc<str>, std::sync::Arc<str>, bool),
969                Vec<u32>,
970            > = jdc_core::FxHashMap::default();
971            for ins in ins {
972                match &ins.kind {
973                    InsnKind::IPut { field_idx, .. } | InsnKind::SPut { field_idx, .. } => {
974                        let (cls, name, _) = self.env.field_ref(*field_idx);
975                        let is_static = matches!(ins.kind, InsnKind::SPut { .. });
976                        field_writes
977                            .entry((cls, name, is_static))
978                            .or_default()
979                            .push(ins.pc);
980                    }
981                    _ => {}
982                }
983            }
984            if !field_writes.is_empty() {
985                for ins in ins {
986                    let (_dst, field_idx, is_static) = match &ins.kind {
987                        InsnKind::IGet { dst, field_idx, .. } => (*dst, *field_idx, false),
988                        InsnKind::SGet { dst, field_idx } => (*dst, *field_idx, true),
989                        _ => continue,
990                    };
991                    let (cls, name, _) = self.env.field_ref(field_idx);
992                    let killed = field_writes
993                        .get(&(cls, name, is_static))
994                        .is_some_and(|ws| ws.iter().any(|w| *w > ins.pc));
995                    if killed {
996                        self.force_mat.insert(ins.pc);
997                    }
998                }
999            }
1000        }
1001        for ins in ins {
1002            self.cur_pc = ins.pc;
1003            match &ins.kind {
1004                InsnKind::Nop => {}
1005                InsnKind::Unknown => {
1006                    return Err(format!(
1007                        "unsupported opcode {:#04x} ({}) at pc {}",
1008                        ins.op,
1009                        ddc_dex::insn::op_name(ins.op),
1010                        ins.pc
1011                    ));
1012                }
1013                InsnKind::Move { dst, src } => {
1014                    // Impure / call values are consumed once: materialize the
1015                    // source so both registers share one evaluation. A raw
1016                    // `new-instance` view is exempt (as at block exit): the
1017                    // ctor fold consumes it at the invoke-direct site, and
1018                    // the invoke-range receiver shuffle (`new-instance v0;
1019                    // move-object v5, v0; invoke/range {v5..}`) must keep it
1020                    // pending — materializing here emitted an empty `new C()`
1021                    // while the real construction orphaned at the receiver
1022                    // (Kotlin default-arg ctors: every enum constant got an
1023                    // empty shell).
1024                    let src_state = self.regs.get(*src as usize).cloned().unwrap_or(Reg::Undef);
1025                    if matches!(src_state, Reg::PendingCall(_))
1026                        || matches!(&src_state, Reg::Pending(e)
1027                            if has_side_effects(e) && !matches!(e, Expr::New { raw: true, .. }))
1028                    {
1029                        self.materialize(*src);
1030                    }
1031                    let st = self.regs.get(*src as usize).cloned().unwrap_or(Reg::Undef);
1032                    let wide = reg_is_wide(&st, self.vt);
1033                    if (*dst as usize) < self.regs.len() {
1034                        self.regs[*dst as usize] = st;
1035                        self.write_pc[*dst as usize] =
1036                            self.write_pc.get(*src as usize).copied().unwrap_or(0);
1037                    }
1038                    if wide {
1039                        self.mark_wide_hi(*dst + 1, ins.pc);
1040                    }
1041                }
1042                InsnKind::MoveResult { dst } => {
1043                    if let Some(e) = self.pending_call.take() {
1044                        let wide =
1045                            matches!(e.type_ref().erased(), JavaType::Long | JavaType::Double);
1046                        self.write_call_result(*dst, e, ins.pc);
1047                        if wide {
1048                            self.mark_wide_hi(*dst + 1, ins.pc);
1049                        }
1050                    } else {
1051                        // move-result without a pending call: fresh var.
1052                        let v = self.fresh_var(*dst, TypeRef::J(JavaType::Int));
1053                        if (*dst as usize) < self.regs.len() {
1054                            self.regs[*dst as usize] = Reg::Live(v);
1055                        }
1056                    }
1057                }
1058                InsnKind::MoveException { dst } => {
1059                    let ty = handler_types
1060                        .iter()
1061                        .flatten()
1062                        .next()
1063                        .map(|t| TypeRef::J(JavaType::Object(t.clone())))
1064                        .unwrap_or_else(|| {
1065                            TypeRef::J(JavaType::Object("java/lang/Throwable".into()))
1066                        });
1067                    let v = self.fresh_var(*dst, ty);
1068                    self.stmts.push(Stmt::LocalDef {
1069                        var: v,
1070                        init: None,
1071                        is_final: false,
1072                        force_type: true,
1073                    });
1074                    if (*dst as usize) < self.regs.len() {
1075                        self.regs[*dst as usize] = Reg::Live(v);
1076                    }
1077                }
1078                InsnKind::ReturnVoid
1079                | InsnKind::Return { .. }
1080                | InsnKind::Throw { .. }
1081                | InsnKind::Goto { .. }
1082                | InsnKind::PackedSwitch { .. }
1083                | InsnKind::SparseSwitch { .. }
1084                | InsnKind::If { .. } => {}
1085                InsnKind::Const { dst, val, wide } => {
1086                    let e = if *wide {
1087                        Expr::Const(ConstVal::Long(*val))
1088                    } else {
1089                        Expr::Const(ConstVal::Int(*val as i32))
1090                    };
1091                    self.write(*dst, e, ins.pc, *wide);
1092                }
1093                InsnKind::ConstString { dst, str_idx } => {
1094                    let s = std::sync::Arc::from(self.env.dex.string(*str_idx));
1095                    self.write(*dst, Expr::Const(ConstVal::Str(s)), ins.pc, false);
1096                }
1097                InsnKind::ConstClass { dst, type_idx } => {
1098                    let ty = TypeRef::J(self.env.java_type(*type_idx));
1099                    self.write(*dst, Expr::Const(ConstVal::ClassLit(ty)), ins.pc, false);
1100                }
1101                InsnKind::MonitorEnter { reg } => {
1102                    let e = self.read_nest(*reg);
1103                    *self.mflags = self.mflags.with_monitor();
1104                    self.stmts.push(Stmt::MonitorEnter(e));
1105                }
1106                InsnKind::MonitorExit { reg } => {
1107                    let e = self.read_nest(*reg);
1108                    *self.mflags = self.mflags.with_monitor();
1109                    self.stmts.push(Stmt::MonitorExit(e));
1110                }
1111                InsnKind::CheckCast { reg, type_idx } => {
1112                    let e = self.read_nest(*reg);
1113                    let ty = TypeRef::J(self.env.java_type(*type_idx));
1114                    self.write(*reg, Expr::Cast { ty, e: Box::new(e) }, ins.pc, false);
1115                }
1116                InsnKind::InstanceOf { dst, src, type_idx } => {
1117                    let e = self.read_nest(*src);
1118                    let ty = TypeRef::J(self.env.java_type(*type_idx));
1119                    self.write(*dst, Expr::InstanceOf { e: Box::new(e), ty }, ins.pc, false);
1120                }
1121                InsnKind::ArrayLength { dst, src } => {
1122                    let e = self.read_nest(*src);
1123                    let length = Expr::Field {
1124                        owner: Some(Box::new(e)),
1125                        cls: std::sync::Arc::from(""),
1126                        name: "length".into(),
1127                        ty: TypeRef::J(JavaType::Int),
1128                        is_static: false,
1129                    };
1130                    self.write(*dst, length, ins.pc, false);
1131                }
1132                InsnKind::NewInstance { dst, type_idx } => {
1133                    let cls = self.env.type_name_arc(*type_idx);
1134                    let ty = TypeRef::J(JavaType::Object(cls.clone()));
1135                    self.write(
1136                        *dst,
1137                        Expr::New {
1138                            cls,
1139                            ty,
1140                            args: vec![],
1141                            arg_tys: Vec::new(),
1142                            raw: true,
1143                        },
1144                        ins.pc,
1145                        false,
1146                    );
1147                }
1148                InsnKind::NewArray {
1149                    dst,
1150                    size,
1151                    type_idx,
1152                } => {
1153                    let elem_desc = self.env.type_name(*type_idx);
1154                    // The type id names the ARRAY type: strip one `[`.
1155                    let elem = crate::desc_type(elem_desc.trim_start_matches('['));
1156                    // `new-array vB, vC, T@` allocates ONE dimension (vC) of
1157                    // elements whose type is T@ with a single `[` stripped.
1158                    // When T@ is itself an array (`[[I` = int[][]), the extra
1159                    // `[` levels are trailing empty dims: `new int[size][]`.
1160                    // Counting only the first `[` as the sized dim and the
1161                    // rest as trailing_dims (elem keeps the fully-stripped
1162                    // base) — the old trim_start_matches('[') dropped every
1163                    // level, rendering int[][] as `new int[size]` and the
1164                    // element stores as `a[i] = (int[]) x` ("int[]无法转换
1165                    // 为int", 267 on reqable).
1166                    let n_brackets = elem_desc.chars().take_while(|&c| c == '[').count();
1167                    let trailing_dims = n_brackets.saturating_sub(1).min(u8::MAX as usize) as u8;
1168                    let dims = self.read_nest(*size);
1169                    self.write(
1170                        *dst,
1171                        Expr::NewArray {
1172                            elem: TypeRef::J(elem),
1173                            dims: vec![dims],
1174                            trailing_dims,
1175                            init: None,
1176                        },
1177                        ins.pc,
1178                        false,
1179                    );
1180                }
1181                InsnKind::FilledNewArray { regs, type_idx } => {
1182                    self.drop_pending_call();
1183                    let elem_desc = self.env.type_name(*type_idx);
1184                    let base = elem_desc.trim_start_matches('[');
1185                    // `trim_start_matches` strips EVERY leading `[` — the
1186                    // total depth must be counted back: a filled-new-array
1187                    // may create a MULTIdimensional array in one insn
1188                    // (`filled-new-array/range {40 rows}, [[I` — autoclaw
1189                    // so/P's static table; R8 emits this for 2-D literal
1190                    // rows). Losing the extra depth rendered `new int[]
1191                    // {…rows…}` (int[]无法转换为int ×787 on autoclaw) and
1192                    // the init form needs trailing_dims = depth − 1 (the
1193                    // printer emits trailing+1 bracket pairs).
1194                    let depth = elem_desc.len() - base.len();
1195                    let elem = crate::desc_type(base);
1196                    // A wide element (long/double) occupies TWO
1197                    // register slots per element — walking the cursor by
1198                    // the element width groups them correctly (reading
1199                    // every register minted fresh locals from the WideHi
1200                    // slots). Multi-dim elements are ARRAY REFERENCES
1201                    // (`[[J` fills `[J` refs) — never wide.
1202                    let wide = depth == 1 && elem.is_wide();
1203                    let mut args: Vec<Expr> = Vec::with_capacity(regs.len());
1204                    let mut it = regs.iter();
1205                    while let Some(&r) = it.next() {
1206                        args.push(self.read_nest(r));
1207                        if wide {
1208                            it.next();
1209                        }
1210                    }
1211                    self.pending_call = Some(Expr::NewArray {
1212                        elem: TypeRef::J(elem),
1213                        dims: vec![],
1214                        trailing_dims: depth.saturating_sub(1).min(255) as u8,
1215                        init: Some(args),
1216                    });
1217                }
1218                InsnKind::FillArrayData { reg, payload_pc } => {
1219                    if let Some(Payload::ArrayData {
1220                        elem_width,
1221                        size,
1222                        data,
1223                    }) = payloads.get(payload_pc)
1224                    {
1225                        let mut elems = payload_consts(*elem_width, *size, data);
1226                        let cur = self.regs.get(*reg as usize).cloned().unwrap_or(Reg::Undef);
1227                        if let Reg::Pending(Expr::NewArray { elem, init, .. }) = cur {
1228                            if init.is_none() {
1229                                // fill-array-data 的字面量也是位模式:按数组元素
1230                                // 类型重解释(`new float[] {1050253722…}` 应为
1231                                // `{0.3f, 0.6f, 1.0f}`)
1232                                match elem.erased() {
1233                                    jdc_core::types::JavaType::Float => {
1234                                        for e in elems.iter_mut() {
1235                                            *e = reinterpret_float(e.clone(), false);
1236                                        }
1237                                    }
1238                                    jdc_core::types::JavaType::Double => {
1239                                        for e in elems.iter_mut() {
1240                                            *e = reinterpret_float(e.clone(), true);
1241                                        }
1242                                    }
1243                                    _ => {}
1244                                }
1245                                self.write(
1246                                    *reg,
1247                                    Expr::NewArray {
1248                                        elem,
1249                                        dims: vec![Expr::Const(ConstVal::Int(*size as i32))],
1250                                        trailing_dims: 0,
1251                                        init: Some(elems),
1252                                    },
1253                                    ins.pc,
1254                                    false,
1255                                );
1256                                continue;
1257                            }
1258                        }
1259                        // Not the new-array+fill pattern: element stores.
1260                        let arr = self.read_nest(*reg);
1261                        for (i, e) in elems.into_iter().enumerate() {
1262                            let idx = Expr::Const(ConstVal::Int(i as i32));
1263                            let target = Expr::ArrayIndex {
1264                                array: Box::new(arr.clone()),
1265                                index: Box::new(idx),
1266                            };
1267                            self.stmts.push(Stmt::ExprStmt(Expr::Assign {
1268                                target: Box::new(target),
1269                                op: AssignOp::Plain,
1270                                value: Box::new(e),
1271                            }));
1272                        }
1273                    }
1274                }
1275                InsnKind::Cmp { dst, a, b, kind } => {
1276                    let l = self.read_nest(*a);
1277                    let r = self.read_nest(*b);
1278                    // float/double 比较的操作数常量同族位重解释
1279                    let (l, r) = match kind {
1280                        CmpKind::CmplF | CmpKind::CmpgF => {
1281                            (reinterpret_float(l, false), reinterpret_float(r, false))
1282                        }
1283                        CmpKind::CmplD | CmpKind::CmpgD => {
1284                            (reinterpret_float(l, true), reinterpret_float(r, true))
1285                        }
1286                        _ => (l, r),
1287                    };
1288                    *self.mflags = self.mflags.with_cmp();
1289                    self.write(*dst, Self::cmp_sentinel(*kind, l, r), ins.pc, false);
1290                }
1291                InsnKind::AGet {
1292                    dst, array, index, ..
1293                } => {
1294                    let a = self.read_nest(*array);
1295                    let i = self.read_nest(*index);
1296                    self.write(
1297                        *dst,
1298                        Expr::ArrayIndex {
1299                            array: Box::new(a),
1300                            index: Box::new(i),
1301                        },
1302                        ins.pc,
1303                        false,
1304                    );
1305                }
1306                InsnKind::APut {
1307                    value,
1308                    array,
1309                    index,
1310                    ..
1311                } => {
1312                    self.drop_pending_call();
1313                    let v = self.read_nest(*value);
1314                    let a = self.read_nest(*array);
1315                    let i = self.read_nest(*index);
1316                    let target = Expr::ArrayIndex {
1317                        array: Box::new(a),
1318                        index: Box::new(i),
1319                    };
1320                    self.stmts.push(Stmt::ExprStmt(Expr::Assign {
1321                        target: Box::new(target),
1322                        op: AssignOp::Plain,
1323                        value: Box::new(v),
1324                    }));
1325                }
1326                InsnKind::IGet {
1327                    dst,
1328                    obj,
1329                    field_idx,
1330                } => {
1331                    self.drop_pending_call();
1332                    let (cls, name, ty) = self.env.field_ref(*field_idx);
1333                    let owner = self.read_nest(*obj);
1334                    let owner_opt = self.owner_expr(owner, &cls);
1335                    self.write(
1336                        *dst,
1337                        Expr::Field {
1338                            owner: owner_opt,
1339                            cls,
1340                            name,
1341                            ty: TypeRef::J(ty),
1342                            is_static: false,
1343                        },
1344                        ins.pc,
1345                        false,
1346                    );
1347                }
1348                InsnKind::IPut {
1349                    value,
1350                    obj,
1351                    field_idx,
1352                } => {
1353                    self.drop_pending_call();
1354                    let (cls, name, ty) = self.env.field_ref(*field_idx);
1355                    // Views that mention the written field ANYWHERE (the
1356                    // receiver chain counts: `this.h.e` is killed by
1357                    // `this.h = null`) go stale the moment the write lands;
1358                    // materialize them ahead of it so their use re-renders
1359                    // the pre-write value.
1360                    self.materialize_views_mentioning(&cls, &name, false);
1361                    let v = null_in_obj_ctx(self.read_nest(*value), &ty);
1362                    let owner = self.read_nest(*obj);
1363                    let owner_opt = self.owner_expr(owner, &cls);
1364                    let target = Expr::Field {
1365                        owner: owner_opt,
1366                        cls,
1367                        name,
1368                        ty: TypeRef::J(ty),
1369                        is_static: false,
1370                    };
1371                    self.stmts.push(Stmt::ExprStmt(Expr::Assign {
1372                        target: Box::new(target),
1373                        op: AssignOp::Plain,
1374                        value: Box::new(v),
1375                    }));
1376                }
1377                InsnKind::SGet { dst, field_idx } => {
1378                    self.drop_pending_call();
1379                    let (cls, name, ty) = self.env.field_ref(*field_idx);
1380                    self.write(
1381                        *dst,
1382                        Expr::Field {
1383                            owner: None,
1384                            cls,
1385                            name,
1386                            ty: TypeRef::J(ty),
1387                            is_static: true,
1388                        },
1389                        ins.pc,
1390                        false,
1391                    );
1392                }
1393                InsnKind::SPut { value, field_idx } => {
1394                    self.drop_pending_call();
1395                    let (cls, name, ty) = self.env.field_ref(*field_idx);
1396                    self.materialize_views_mentioning(&cls, &name, true);
1397                    let v = null_in_obj_ctx(self.read_nest(*value), &ty);
1398                    let target = Expr::Field {
1399                        owner: None,
1400                        cls,
1401                        name,
1402                        ty: TypeRef::J(ty),
1403                        is_static: true,
1404                    };
1405                    self.stmts.push(Stmt::ExprStmt(Expr::Assign {
1406                        target: Box::new(target),
1407                        op: AssignOp::Plain,
1408                        value: Box::new(v),
1409                    }));
1410                }
1411                InsnKind::Invoke {
1412                    kind,
1413                    regs,
1414                    method_idx,
1415                } => {
1416                    self.drop_pending_call();
1417                    self.do_invoke(*kind, regs, *method_idx, ins.pc)?;
1418                }
1419                InsnKind::InvokeCustom {
1420                    call_site_idx,
1421                    regs,
1422                } => {
1423                    self.drop_pending_call();
1424                    let e = self.build_invoke_custom(*call_site_idx, regs, ins.pc);
1425                    self.pending_call = Some(e);
1426                }
1427                InsnKind::Un {
1428                    dst,
1429                    src,
1430                    op,
1431                    from: _,
1432                    to,
1433                } => {
1434                    let e = self.read_nest(*src);
1435                    let ne = match op {
1436                        ddc_dex::insn::UnArith::Neg => Expr::Un {
1437                            op: UnOp::Neg,
1438                            e: Box::new(e),
1439                        },
1440                        ddc_dex::insn::UnArith::Not => Expr::Un {
1441                            op: UnOp::BitNot,
1442                            e: Box::new(e),
1443                        },
1444                        ddc_dex::insn::UnArith::Conv => Expr::Cast {
1445                            ty: TypeRef::J(primitive_type(*to)),
1446                            e: Box::new(e),
1447                        },
1448                    };
1449                    let wide = matches!(*to, 'J' | 'D');
1450                    self.write(*dst, ne, ins.pc, wide);
1451                }
1452                InsnKind::Bin { op, dst, a, b, ty } => {
1453                    let l = self.read_nest(*a);
1454                    let r = self.read_nest(*b);
1455                    // 浮点运算的操作数常量按位重解释(见 reinterpret_float)
1456                    let (l, r) = match *ty {
1457                        'F' => (reinterpret_float(l, false), reinterpret_float(r, false)),
1458                        'D' => (reinterpret_float(l, true), reinterpret_float(r, true)),
1459                        _ => (l, r),
1460                    };
1461                    let jt = primitive_type(*ty);
1462                    let e = Expr::Bin {
1463                        op: Self::arith_op(*op),
1464                        l: Box::new(l),
1465                        r: Box::new(r),
1466                        ty: Some(TypeRef::J(jt)),
1467                    };
1468                    let wide = matches!(*ty, 'J' | 'D');
1469                    self.write(*dst, e, ins.pc, wide);
1470                }
1471                InsnKind::BinLit {
1472                    op,
1473                    dst,
1474                    a,
1475                    lit,
1476                    rsub,
1477                } => {
1478                    let av = self.read_nest(*a);
1479                    let e = if *rsub {
1480                        Expr::Bin {
1481                            op: BinOp::Sub,
1482                            l: Box::new(Expr::Const(ConstVal::Int(*lit))),
1483                            r: Box::new(av),
1484                            ty: Some(TypeRef::J(JavaType::Int)),
1485                        }
1486                    } else {
1487                        Expr::Bin {
1488                            op: Self::arith_op(*op),
1489                            l: Box::new(av),
1490                            r: Box::new(Expr::Const(ConstVal::Int(*lit))),
1491                            ty: Some(TypeRef::J(JavaType::Int)),
1492                        }
1493                    };
1494                    self.write(*dst, e, ins.pc, false);
1495                }
1496                InsnKind::ConstMethodHandle { dst, handle_idx } => {
1497                    self.drop_pending_call();
1498                    let e = self.method_handle_expr(*handle_idx);
1499                    self.write(*dst, e, ins.pc, false);
1500                }
1501                InsnKind::ConstMethodType { dst, proto_idx } => {
1502                    self.drop_pending_call();
1503                    let proto = self.env.dex.proto(*proto_idx);
1504                    let desc = format!(
1505                        "({}){}",
1506                        self.env
1507                            .dex
1508                            .proto_params(*proto_idx)
1509                            .iter()
1510                            .map(|&t| self.env.dex.type_name(t))
1511                            .collect::<Vec<_>>()
1512                            .join(""),
1513                        self.env.dex.type_name(proto.return_type_idx)
1514                    );
1515                    self.write(*dst, Expr::Raw(desc), ins.pc, false);
1516                }
1517            }
1518        }
1519
1520        // Terminator first: terms may consume PendingCall values directly.
1521        let term = self.block_term(ins);
1522
1523        // Exit cleanup: unconsumed call → statement; impure pendings → vars.
1524        // Open `new`/`new-array` views stay pending (their consumer may live
1525        // in a later block).
1526        self.drop_pending_call();
1527        self.materialize_impure_at_exit();
1528
1529        // Move (not clone): the lifter is consumed after build_block, and
1530        // these two clones — regs deep-copies boxed Exprs for any Pending
1531        // states — ran 3.7M times on weibo.
1532        let out = OutState {
1533            regs: std::mem::take(&mut self.regs),
1534            write_pc: std::mem::take(&mut self.write_pc),
1535        };
1536        Ok((
1537            BlockResult {
1538                stmts: std::mem::take(&mut self.stmts),
1539                out_stack: Vec::new(),
1540                term,
1541            },
1542            out,
1543        ))
1544    }
1545
1546    fn do_invoke(
1547        &mut self,
1548        kind: InvokeKind,
1549        regs: &[u16],
1550        method_idx: u32,
1551        pc: u32,
1552    ) -> BResult<()> {
1553        let (cls, name, md) = self.env.method_ref(method_idx);
1554        if cls.as_ref() == "java/lang/StringBuilder" || cls.as_ref() == "java/lang/StringBuffer" {
1555            *self.mflags = self.mflags.with_sb();
1556        }
1557        let is_static = matches!(kind, InvokeKind::Static);
1558        let receiver_reg = if is_static {
1559            None
1560        } else {
1561            regs.first().copied()
1562        };
1563        let arg_regs: Vec<u16> = if is_static {
1564            regs.to_vec()
1565        } else {
1566            regs.iter().skip(1).copied().collect()
1567        };
1568        let mut args: Vec<Expr> = Vec::with_capacity(md.args.len());
1569        // A WIDE parameter (long/double) occupies TWO register slots in
1570        // the instruction's register list — indexing by PARAMETER
1571        // position shifted every argument after a wide one (weixin
1572        // yieldTransaction(JZ L) read the boolean from the long's HIGH
1573        // register and the signal from the boolean's — the root of the
1574        // call-argument int↔bool family and ~11k dangling `int vN`
1575        // reads). Advance the register cursor by the arg's width.
1576        let mut ri = 0usize;
1577        for at in md.args.iter() {
1578            let r = arg_regs.get(ri).copied().unwrap_or(0);
1579            args.push(null_in_obj_ctx(self.read_nest(r), at));
1580            ri += if at.is_wide() { 2 } else { 1 };
1581        }
1582        // Constructor call: fold `new C` receivers; this/super otherwise.
1583        // recv_expr is computed LAZILY (after the fold's early return):
1584        // reading the receiver through read_nest before the fold would
1585        // materialize a raw pending New into a local and the fold's
1586        // Pending-pattern check would never fire.
1587        if name.as_ref() == "<init>" && matches!(kind, InvokeKind::Direct) {
1588            let recv_reg = receiver_reg.unwrap_or(0);
1589            let recv_state = self
1590                .regs
1591                .get(recv_reg as usize)
1592                .cloned()
1593                .unwrap_or(Reg::Undef);
1594            if let Reg::Pending(Expr::New {
1595                raw: true, cls: nc, ..
1596            }) = &recv_state
1597            {
1598                // R8 constructor outlining / vertical class merging:
1599                // `new-instance Child` + `invoke-direct Parent-><init>`
1600                // — the child's trivial ctor was stripped and the verifier
1601                // allows the super-ctor call on the uninitialized
1602                // instance. The CONSTRUCTED class is the new-instance
1603                // type; folding on the invoke owner rendered
1604                // `new Hd.c(p2x)` against the abstract parent (uuyc
1605                // 抽象的无法实例化 ×2.2k). The inherited-ctor bridge in
1606                // classdec gives the child a matching `super(..)`
1607                // delegation, so `new Child(args)` compiles.
1608                if nc == &cls || self.env.pool.is_subtype(nc.as_ref(), cls.as_ref()) {
1609                    let folded = Expr::New {
1610                        cls: nc.clone(),
1611                        ty: TypeRef::J(JavaType::Object(nc.clone())),
1612                        arg_tys: md.args.clone(),
1613                        args,
1614                        raw: false,
1615                    };
1616                    // The raw pending may live in SEVERAL registers:
1617                    // `move-object` copies it for the invoke-range
1618                    // receiver shuffle. Writing only the receiver left
1619                    // the original register's raw view behind, so the
1620                    // following `sput v0` inlined an EMPTY `new C()`
1621                    // while the real construction orphaned at the
1622                    // receiver. Carry the fold to every alias, keyed by
1623                    // the origin write_pc the Move handler propagates.
1624                    let origin = self.write_pc.get(recv_reg as usize).copied();
1625                    let aliases: Vec<usize> = origin
1626                        .map(|w| {
1627                            (0..self.regs.len())
1628                                .filter(|&r| {
1629                                    r != recv_reg as usize
1630                                        && self.write_pc.get(r).copied() == Some(w)
1631                                        && matches!(
1632                                            &self.regs[r],
1633                                            Reg::Pending(Expr::New { raw: true, cls: c, .. })
1634                                                if c == nc
1635                                        )
1636                                })
1637                                .collect()
1638                        })
1639                        .unwrap_or_default();
1640                    self.write(recv_reg, folded.clone(), pc, false);
1641                    // write()'s size cap may have materialized the
1642                    // construction into a local — share that var with the
1643                    // aliases instead of pending copies of the same
1644                    // allocation.
1645                    match self.regs.get(recv_reg as usize) {
1646                        Some(Reg::Live(v)) => {
1647                            let v = *v;
1648                            for r in aliases {
1649                                self.regs[r] = Reg::Live(v);
1650                            }
1651                        }
1652                        _ => {
1653                            for r in aliases {
1654                                self.regs[r] = Reg::Pending(folded.clone());
1655                                self.write_pc[r] = pc;
1656                            }
1657                        }
1658                    }
1659                    return Ok(());
1660                }
1661            }
1662            let recv_expr = receiver_reg.map(|r| self.read_nest(r));
1663            let owner_expr_v = recv_expr.unwrap_or(Expr::This);
1664            // Normalize the receiver: in an instance method the `this`
1665            // parameter is ALWAYS var 0 (entry_regs pushes it first).
1666            // read_nest hands back Local{var:0}, which the emitter's
1667            // is_this/lost_alloc checks do not recognize — a plain
1668            // `super()` arrived there as a non-this receiver and printed
1669            // `new Object();` (every default ctor in every app).
1670            let owner_expr_v = if !self.env.is_static
1671                && matches!(&owner_expr_v, Expr::Local { var: 0, .. })
1672            {
1673                Expr::This
1674            } else {
1675                owner_expr_v
1676            };
1677            let is_super = cls.as_ref() != self.env.class_name.as_str();
1678            let owner = if matches!(owner_expr_v, Expr::This) && is_super {
1679                None
1680            } else {
1681                Some(Box::new(owner_expr_v))
1682            };
1683            let call = Expr::Method {
1684                owner,
1685                cls,
1686                name: "<init>".into(),
1687                desc: md,
1688                args,
1689                is_static: false,
1690                is_interface: false,
1691                is_special: true,
1692                is_super,
1693                is_dynamic: false,
1694                type_args: vec![],
1695            };
1696            self.stmts.push(Stmt::ExprStmt(call));
1697            return Ok(());
1698        }
1699
1700        let recv_expr = receiver_reg.map(|r| self.read_nest(r));
1701        let is_interface = matches!(kind, InvokeKind::Interface);
1702        // `super.` only when the receiver is THIS. R8/d8 desugar an
1703        // inner-class/lambda `outer.super.m()` into a STATIC synthetic
1704        // accessor `w(Outer p) { invoke-special p, Super.m() }` — a
1705        // nonvirtual call on a PARAMETER. The `super` keyword is illegal
1706        // there ("无法从静态上下文中引用非静态 变量 super", weixin 318 +
1707        // lark 176), and Java cannot express special dispatch on an
1708        // arbitrary instance, so a plain call on the receiver is the
1709        // only legal render.
1710        // The receiver may reach here as Expr::This, the Raw("\u{3}")
1711        // placeholder, or — most often — the `this` LOCAL (read_nest of
1712        // register v0; the VarInfo is named "this" and emit prints the
1713        // keyword). All three are the this-receiver; anything else (a
1714        // real parameter/local) is not.
1715        let recv_is_this = match &recv_expr {
1716            Some(Expr::This) => true,
1717            Some(Expr::Raw(t)) => t == "\u{3}" || t == "this",
1718            Some(Expr::Local { var, .. }) => {
1719                (*var as usize) < self.vt.vars.len() && self.vt.vars[*var as usize].name == "this"
1720            }
1721            _ => false,
1722        };
1723        let is_super = match kind {
1724            InvokeKind::Super | InvokeKind::Direct => {
1725                recv_is_this && cls.as_ref() != self.env.class_name.as_str()
1726            }
1727            _ => false,
1728        };
1729        let owner: Option<Box<Expr>> = match recv_expr {
1730            None => None,
1731            Some(o) => {
1732                if is_super && matches!(o, Expr::This) {
1733                    None
1734                } else {
1735                    self.owner_expr(o, &cls)
1736                }
1737            }
1738        };
1739        let is_dynamic = matches!(kind, InvokeKind::Polymorphic);
1740        let call = Expr::Method {
1741            owner,
1742            cls,
1743            name,
1744            desc: md,
1745            args,
1746            is_static,
1747            is_interface,
1748            is_special: matches!(kind, InvokeKind::Direct),
1749            is_super,
1750            is_dynamic,
1751            type_args: vec![],
1752        };
1753        self.pending_call = Some(call);
1754        Ok(())
1755    }
1756
1757    /// Resolve a method-handle constant into a readable expression
1758    /// (`Cls::name` for method kinds).
1759    fn method_handle_expr(&mut self, handle_idx: u32) -> Expr {
1760        let Some(h) = self.env.dex.method_handle(handle_idx) else {
1761            return Expr::Raw(format!("methodHandle@{}", handle_idx));
1762        };
1763        if h.is_field {
1764            let f = self.env.dex.field(h.target_id);
1765            let cls = self.env.dex.class_name(f.class_idx);
1766            let name = self.env.dex.string(f.name_idx).to_string();
1767            return Expr::Raw(format!("{}::{}", cls.replace('/', "."), name));
1768        }
1769        let m = self.env.dex.method(h.target_id);
1770        let cls = self.env.dex.class_name(m.class_idx);
1771        let name = self.env.dex.string(m.name_idx).to_string();
1772        Expr::Raw(format!("{}::{}", cls.replace('/', "."), name))
1773    }
1774
1775    /// Build the expression for an invoke-custom site.
1776    fn build_invoke_custom(&mut self, cs_idx: u32, regs: &[u16], pc: u32) -> Expr {
1777        let Some(cs) = self.env.dex.call_site(cs_idx).cloned() else {
1778            return Expr::Invokedynamic {
1779                name: format!("call-site#{}", cs_idx),
1780                desc: jdc_core::types::MethodDescriptor {
1781                    args: vec![],
1782                    ret: JavaType::Object("java/lang/Object".into()),
1783                },
1784                args: vec![],
1785                bsm_text: "unresolved".into(),
1786                bsm_static_args: vec![],
1787            };
1788        };
1789        let site_name = self.env.dex.string(cs.name_idx).to_string();
1790        let params: Vec<JavaType> = self
1791            .env
1792            .dex
1793            .proto_params(cs.proto_idx)
1794            .iter()
1795            .map(|&t| self.env.java_type(t))
1796            .collect();
1797        let ret = self
1798            .env
1799            .java_type(self.env.dex.proto(cs.proto_idx).return_type_idx);
1800
1801        let (bs_cls, bs_name) = match self.env.dex.method_handle(cs.bootstrap_handle) {
1802            Some(h) if !h.is_field => {
1803                let m = self.env.dex.method(h.target_id);
1804                (
1805                    self.env.dex.class_name(m.class_idx),
1806                    self.env.dex.string(m.name_idx).to_string(),
1807                )
1808            }
1809            _ => (String::new(), String::new()),
1810        };
1811
1812        // Dynamic arguments (the SAM parameters for lambdas). A wide
1813        // parameter occupies TWO register slots — index a walking cursor
1814        // by the param's width, not the param position (same bug class
1815        // as the invoke arg lift).
1816        let mut args: Vec<Expr> = Vec::with_capacity(params.len().min(regs.len()));
1817        let mut ri = 0usize;
1818        for at in params.iter() {
1819            if let Some(&r) = regs.get(ri) {
1820                args.push(self.read_nest(r));
1821            }
1822            ri += if at.is_wide() { 2 } else { 1 };
1823        }
1824
1825        // StringConcatFactory: fold the recipe into `+`.
1826        if bs_name == "makeConcatWithConstants" {
1827            let recipe = cs
1828                .linker_args
1829                .iter()
1830                .find_map(|v| match v {
1831                    ddc_dex::annotations::EncodedValue::String(s) => {
1832                        Some(self.env.dex.string(*s).to_string())
1833                    }
1834                    _ => None,
1835                })
1836                .unwrap_or_default();
1837            return concat_from_recipe(&recipe, args);
1838        }
1839
1840        // LambdaMetafactory: linker args are [samType (erased),
1841        // implMethod (handle), instantiatedMethodType]. The instantiated
1842        // type carries the source-level SAM parameters.
1843        let mut impl_m = None;
1844        let mut instantiated_proto: Option<u32> = None;
1845        if bs_name == "metafactory" || bs_name == "altMetafactory" {
1846            let mut seen_handle = false;
1847            for lv in &cs.linker_args {
1848                match lv {
1849                    ddc_dex::annotations::EncodedValue::MethodHandle(hi) => {
1850                        if !seen_handle {
1851                            seen_handle = true;
1852                            if let Some(h) = self.env.dex.method_handle(*hi) {
1853                                if !h.is_field {
1854                                    let m = self.env.dex.method(h.target_id);
1855                                    impl_m = Some((
1856                                        self.env.dex.class_name(m.class_idx).replace('/', "."),
1857                                        self.env.dex.string(m.name_idx).to_string(),
1858                                    ));
1859                                }
1860                            }
1861                        }
1862                    }
1863                    ddc_dex::annotations::EncodedValue::MethodType(p) if seen_handle => {
1864                        instantiated_proto = Some(*p);
1865                    }
1866                    _ => {}
1867                }
1868            }
1869        }
1870        let _ = pc;
1871
1872        // Remaining registers are the captured values.
1873        let mut captures = Vec::new();
1874        for &r in regs.iter().skip(params.len().min(regs.len())) {
1875            captures.push(self.read_nest(r));
1876        }
1877
1878        if let Some((impl_cls, impl_name)) = impl_m {
1879            if captures.is_empty() {
1880                // Non-capturing: render real Java syntax. A synthetic
1881                // `lambda$` impl is a lambda; anything else is a method
1882                // reference (`Cls::name`).
1883                if impl_name.starts_with("lambda$") {
1884                    // Source-level parameter count comes from the
1885                    // instantiated SAM type, not the site proto (which is
1886                    // the functional-interface factory view: `()Function`).
1887                    let n_params = match instantiated_proto {
1888                        Some(p) => self.env.dex.proto_params(p).len(),
1889                        None => params.len(),
1890                    };
1891                    let pnames: Vec<String> = (0..n_params).map(|i| format!("a{}", i)).collect();
1892                    return Expr::Raw(format!(
1893                        "({}) -> {}.{}({})",
1894                        pnames.join(", "),
1895                        impl_cls,
1896                        impl_name,
1897                        pnames.join(", ")
1898                    ));
1899                }
1900                return Expr::Raw(format!("{}::{}", impl_cls, impl_name));
1901            }
1902            // Capturing: the captures are real expressions; emit the
1903            // desugared call shape (impl applies SAM params then captures).
1904            let mut call_args = args;
1905            call_args.extend(captures);
1906            return Expr::Invokedynamic {
1907                name: format!("{} -> {}.{}", site_name, impl_cls, impl_name),
1908                desc: jdc_core::types::MethodDescriptor { args: params, ret },
1909                args: call_args,
1910                bsm_text: format!("{}::{}", bs_cls, bs_name),
1911                bsm_static_args: vec![],
1912            };
1913        }
1914
1915        Expr::Invokedynamic {
1916            name: site_name,
1917            desc: jdc_core::types::MethodDescriptor { args: params, ret },
1918            args: captures,
1919            bsm_text: format!("{}::{}", bs_cls, bs_name),
1920            bsm_static_args: vec![],
1921        }
1922    }
1923
1924    fn materialize_impure_at_exit(&mut self) {
1925        let n = self.regs.len();
1926        for r in 0..n {
1927            let st = self.regs[r].clone();
1928            let impure = match &st {
1929                Reg::PendingCall(_) => true,
1930                Reg::Pending(e) => {
1931                    has_side_effects(e)
1932                        && !matches!(e, Expr::New { raw: true, .. })
1933                        && !matches!(e, Expr::NewArray { init: None, .. })
1934                }
1935                _ => false,
1936            };
1937            if impure {
1938                self.materialize(r as u16);
1939            }
1940        }
1941    }
1942
1943    /// Terminator for the block, from its last instruction.
1944    fn block_term(&mut self, ins: &[Insn]) -> Term {
1945        let Some(last) = ins.last() else {
1946            return Term::Goto;
1947        };
1948        match &last.kind {
1949            InsnKind::Goto { .. } => Term::Goto,
1950            InsnKind::ReturnVoid => Term::Return(None),
1951            InsnKind::Return { src } => {
1952                let v = null_in_obj_ctx(self.read_term(*src), &self.env.desc.ret);
1953                Term::Return(Some(v))
1954            }
1955            InsnKind::Throw { reg } => {
1956                // throw is always an object context.
1957                let mut v = self.read_term(*reg);
1958                if let Expr::Const(ConstVal::Int(0)) = v {
1959                    v = Expr::Const(ConstVal::Null);
1960                }
1961                Term::Throw(v)
1962            }
1963            InsnKind::If { op, a, b, z, .. } => {
1964                let cond = if *z {
1965                    let v = self.read_nest(*a);
1966                    let (l, r, realop) = jdc_core::ir::build::unfold_cmp(v, Self::cmp_op(*op));
1967                    Expr::Bin {
1968                        op: realop,
1969                        l: Box::new(l),
1970                        r: Box::new(r),
1971                        ty: Some(TypeRef::J(JavaType::Boolean)),
1972                    }
1973                } else {
1974                    let l = self.read_nest(*a);
1975                    let r = self.read_nest(*b);
1976                    Expr::Bin {
1977                        op: Self::cmp_op(*op),
1978                        l: Box::new(l),
1979                        r: Box::new(r),
1980                        ty: Some(TypeRef::J(JavaType::Boolean)),
1981                    }
1982                };
1983                Term::Cond { cond }
1984            }
1985            InsnKind::PackedSwitch { reg, payload_pc }
1986            | InsnKind::SparseSwitch { reg, payload_pc } => {
1987                let selector = self.read_term(*reg);
1988                let default = Some(last.pc + last.size);
1989                let targets = match self.env.code.payloads.get(payload_pc) {
1990                    Some(Payload::Packed { first_key, targets }) => SwitchTargets::Table {
1991                        low: *first_key,
1992                        targets: targets
1993                            .iter()
1994                            .map(|t| (last.pc as i64 + *t as i64) as u32)
1995                            .collect(),
1996                    },
1997                    Some(Payload::Sparse { pairs }) => SwitchTargets::Lookup {
1998                        pairs: pairs
1999                            .iter()
2000                            .map(|(k, t)| (*k, (last.pc as i64 + *t as i64) as u32))
2001                            .collect(),
2002                    },
2003                    _ => SwitchTargets::Lookup { pairs: vec![] },
2004                };
2005                Term::Switch {
2006                    selector,
2007                    targets,
2008                    default,
2009                }
2010            }
2011            // Fell off the analysis end (payload region after): goto.
2012            _ => {
2013                let next = last.pc + last.size;
2014                if next < self.code_units {
2015                    Term::Fallthrough
2016                } else {
2017                    Term::Goto
2018                }
2019            }
2020        }
2021    }
2022}
2023
2024/// StringConcatFactory recipe → `+` parts: `\u{1}` slots consume the next
2025/// argument, `\u{2}` <len> <chars> emits literal text (rare), other bytes
2026/// are literal.
2027fn concat_from_recipe(recipe: &str, args: Vec<Expr>) -> Expr {
2028    use jdc_core::ir::expr::ConcatPart;
2029    let mut parts: Vec<ConcatPart> = Vec::new();
2030    let mut arg_i = 0usize;
2031    let mut const_buf = String::new();
2032    for c in recipe.chars() {
2033        match c {
2034            '\u{1}' => {
2035                if !const_buf.is_empty() {
2036                    parts.push(ConcatPart::Const(std::mem::take(&mut const_buf)));
2037                }
2038                if arg_i < args.len() {
2039                    parts.push(ConcatPart::Str(args[arg_i].clone()));
2040                    arg_i += 1;
2041                }
2042            }
2043            '\u{2}' => {
2044                // Length-prefixed literal: skip the length char and copy the
2045                // following chars until the next marker.
2046            }
2047            c => const_buf.push(c),
2048        }
2049    }
2050    if !const_buf.is_empty() {
2051        parts.push(ConcatPart::Const(const_buf));
2052    }
2053    Expr::StringConcat(parts)
2054}
2055
2056/// Node count of an expression tree (caps loop-carried growth: a Pending
2057/// tree that grows across fixpoint rounds would otherwise reach GBs — each
2058/// merge clones the whole tree per side per visit).
2059fn expr_size(e: &Expr) -> usize {
2060    let mut n = 0usize;
2061    count_nodes(e, &mut n);
2062    n
2063}
2064
2065/// Does the expression read the given field anywhere (receiver chains and
2066/// nested arguments included)?
2067fn expr_mentions_field(
2068    e: &Expr,
2069    cls: &std::sync::Arc<str>,
2070    name: &std::sync::Arc<str>,
2071    is_static: bool,
2072) -> bool {
2073    match e {
2074        Expr::Field {
2075            owner,
2076            cls: c,
2077            name: n,
2078            is_static: s,
2079            ..
2080        } => {
2081            (c == cls && n == name && *s == is_static)
2082                || owner
2083                    .as_deref()
2084                    .is_some_and(|o| expr_mentions_field(o, cls, name, is_static))
2085        }
2086        Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => {
2087            expr_mentions_field(e, cls, name, is_static)
2088        }
2089        Expr::Bin { l, r, .. } => {
2090            expr_mentions_field(l, cls, name, is_static)
2091                || expr_mentions_field(r, cls, name, is_static)
2092        }
2093        Expr::Cond { c, t, f } => {
2094            expr_mentions_field(c, cls, name, is_static)
2095                || expr_mentions_field(t, cls, name, is_static)
2096                || expr_mentions_field(f, cls, name, is_static)
2097        }
2098        Expr::Assign { target, value, .. } => {
2099            expr_mentions_field(target, cls, name, is_static)
2100                || expr_mentions_field(value, cls, name, is_static)
2101        }
2102        Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => {
2103            expr_mentions_field(e, cls, name, is_static)
2104        }
2105        Expr::Method {
2106            owner, args, ..
2107        } => {
2108            owner
2109                .as_deref()
2110                .is_some_and(|o| expr_mentions_field(o, cls, name, is_static))
2111                || args
2112                    .iter()
2113                    .any(|a| expr_mentions_field(a, cls, name, is_static))
2114        }
2115        Expr::ArrayIndex { array, index } => {
2116            expr_mentions_field(array, cls, name, is_static)
2117                || expr_mentions_field(index, cls, name, is_static)
2118        }
2119        Expr::New { args, .. } => args
2120            .iter()
2121            .any(|a| expr_mentions_field(a, cls, name, is_static)),
2122        Expr::NewArray { dims, init, .. } => {
2123            dims.iter().any(|d| expr_mentions_field(d, cls, name, is_static))
2124                || init
2125                    .as_ref()
2126                    .is_some_and(|v| v.iter().any(|x| expr_mentions_field(x, cls, name, is_static)))
2127        }
2128        Expr::NewMultiArray { dims, .. } => {
2129            dims.iter().any(|d| expr_mentions_field(d, cls, name, is_static))
2130        }
2131        Expr::StringConcat(parts) => parts.iter().any(|p| match p {
2132            ConcatPart::Str(e) => expr_mentions_field(e, cls, name, is_static),
2133            ConcatPart::Const(_) => false,
2134        }),
2135        _ => false,
2136    }
2137}
2138
2139fn count_nodes(e: &Expr, n: &mut usize) {
2140    *n += 1;
2141    if *n > 512 {
2142        return; // cap the walk itself
2143    }
2144    match e {
2145        Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => count_nodes(e, n),
2146        Expr::Bin { l, r, .. } => {
2147            count_nodes(l, n);
2148            count_nodes(r, n);
2149        }
2150        Expr::Cond { c, t, f } => {
2151            count_nodes(c, n);
2152            count_nodes(t, n);
2153            count_nodes(f, n);
2154        }
2155        Expr::Assign { target, value, .. } => {
2156            count_nodes(target, n);
2157            count_nodes(value, n);
2158        }
2159        Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => count_nodes(e, n),
2160        Expr::Field { owner: Some(o), .. } => count_nodes(o, n),
2161        Expr::Method {
2162            owner: Some(o),
2163            args,
2164            ..
2165        } => {
2166            count_nodes(o, n);
2167            for a in args {
2168                count_nodes(a, n);
2169            }
2170        }
2171        Expr::ArrayIndex { array, index } => {
2172            count_nodes(array, n);
2173            count_nodes(index, n);
2174        }
2175        Expr::New { args, .. } => {
2176            for a in args {
2177                count_nodes(a, n);
2178            }
2179        }
2180        Expr::NewArray { dims, init, .. } => {
2181            for d in dims {
2182                count_nodes(d, n);
2183            }
2184            if let Some(v) = init {
2185                for x in v {
2186                    count_nodes(x, n);
2187                }
2188            }
2189        }
2190        Expr::NewMultiArray { dims, .. } => {
2191            for d in dims {
2192                count_nodes(d, n);
2193            }
2194        }
2195        Expr::StringConcat(parts) => {
2196            for p in parts {
2197                if let ConcatPart::Str(x) = p {
2198                    count_nodes(x, n);
2199                }
2200            }
2201        }
2202        _ => {}
2203    }
2204}
2205
2206fn reg_is_wide(st: &Reg, vt: &VarTable) -> bool {
2207    match st {
2208        Reg::Pending(e) | Reg::PendingCall(e) => e.type_ref().erased().is_wide(),
2209        Reg::Live(v) => vt.var(*v).ty.erased().is_wide(),
2210        Reg::WideHi => true,
2211        Reg::Undef => false,
2212    }
2213}
2214
2215/// d8 encodes `null` as `const/4 vN, 0`; the instruction kind does not
2216/// distinguish object contexts (return/return-object share one variant),
2217/// so the surrounding DESCRIPTOR type decides: a zero constant flowing
2218/// into a reference context is `null`, not `0` (rendering `return 0;`
2219/// from a String method — lab-package feedback).
2220fn null_in_obj_ctx(mut v: Expr, ctx_ty: &JavaType) -> Expr {
2221    if ctx_ty.is_reference() {
2222        if let Expr::Const(ConstVal::Int(0)) = v {
2223            v = Expr::Const(ConstVal::Null);
2224        }
2225    }
2226    v
2227}
2228
2229/// dex 的 `const`/`const-wide` 字面量是**无类型位模式**:浮点语境
2230/// (float/double 运算与比较、float[]/double[] 填充)的操作数寄存器里
2231/// 的 Int/Long 常量就是浮点位——编译器把浮点字面量按位发射,必须按
2232/// 位重解释。数值转换(as f32)会把 1073741824 读成 1.07e9f 而非
2233/// 2.0f,重编译后数值全错且 javac 门不可见(telegram sf/f0:
2234/// `measuredWidth / 1073741824` 应为 `/ 2.0f`,全语料 2.7 万行)。
2235fn reinterpret_float(e: Expr, wide: bool) -> Expr {
2236    match (&e, wide) {
2237        (Expr::Const(ConstVal::Int(i)), false) => {
2238            Expr::Const(ConstVal::Float(f32::from_bits(*i as u32)))
2239        }
2240        (Expr::Const(ConstVal::Long(l)), true) => {
2241            Expr::Const(ConstVal::Double(f64::from_bits(*l as u64)))
2242        }
2243        (Expr::Const(ConstVal::Int(i)), true) => {
2244            Expr::Const(ConstVal::Double(f64::from_bits(*i as u64)))
2245        }
2246        _ => e,
2247    }
2248}
2249
2250fn value_of_cmp(e: &Expr) -> Expr {
2251    value_of_cmp_owned(e.clone())
2252}
2253
2254/// By-value form: single-consumer call sites (materialize) pass the owned
2255/// expression and skip the clone the shared `&Expr` form pays on the
2256/// non-sentinel fast path.
2257fn value_of_cmp_owned(e: Expr) -> Expr {
2258    // Borrow first; only rebuild when the sentinel shape matches (the
2259    // non-sentinel fast path returns the original with zero clones).
2260    if let Expr::Invokedynamic { name, args, .. } = &e {
2261        if name.starts_with('\0') && args.len() == 2 {
2262            let (cls, ty) = match name.as_str() {
2263                "\0cmp-long" => ("java/lang/Long", JavaType::Long),
2264                "\0cmpl-float" | "\0cmpg-float" => ("java/lang/Float", JavaType::Float),
2265                _ => ("java/lang/Double", JavaType::Double),
2266            };
2267            let desc = MethodDescriptor {
2268                args: vec![ty.clone(), ty],
2269                ret: JavaType::Int,
2270            };
2271            return Expr::Method {
2272                owner: None,
2273                cls: cls.into(),
2274                name: "compare".into(),
2275                desc: std::sync::Arc::new(desc),
2276                args: args.clone(),
2277                is_static: true,
2278                is_interface: false,
2279                is_special: false,
2280                is_super: false,
2281                is_dynamic: false,
2282                type_args: vec![],
2283            };
2284        }
2285    }
2286    e
2287}
2288
2289pub fn primitive_type(c: char) -> JavaType {
2290    match c {
2291        'Z' => JavaType::Boolean,
2292        'B' => JavaType::Byte,
2293        'C' => JavaType::Char,
2294        'S' => JavaType::Short,
2295        'F' => JavaType::Float,
2296        'J' => JavaType::Long,
2297        'D' => JavaType::Double,
2298        _ => JavaType::Int,
2299    }
2300}
2301
2302/// Constant elements from a fill-array-data payload, typed by width (the
2303/// consuming new-array's element type refines them later).
2304fn payload_consts(elem_width: u16, size: u32, data: &[u8]) -> Vec<Expr> {
2305    let n = (size as usize).min(data.len() / elem_width.max(1) as usize);
2306    let mut out = Vec::with_capacity(n);
2307    for i in 0..n {
2308        let off = i * elem_width as usize;
2309        let e = match elem_width {
2310            1 => Expr::Const(ConstVal::Int(data[off] as i8 as i32)),
2311            2 => {
2312                let v = i16::from_le_bytes([data[off], data[off + 1]]);
2313                Expr::Const(ConstVal::Int(v as i32))
2314            }
2315            4 => {
2316                let mut b = [0u8; 4];
2317                b.copy_from_slice(&data[off..off + 4]);
2318                Expr::Const(ConstVal::Int(i32::from_le_bytes(b)))
2319            }
2320            8 => {
2321                let mut b = [0u8; 8];
2322                b.copy_from_slice(&data[off..off + 8]);
2323                Expr::Const(ConstVal::Long(i64::from_le_bytes(b)))
2324            }
2325            _ => Expr::Const(ConstVal::Int(0)),
2326        };
2327        out.push(e);
2328    }
2329    out
2330}
2331
2332/// Entry register state from parameters. Returns the register state.
2333pub fn entry_regs(vt: &mut VarTable, env: &MethodEnv, param_names: &[Option<String>]) -> Vec<Reg> {
2334    let code = env.code;
2335    let n = code.registers_size as usize;
2336    let mut regs = vec![Reg::Undef; n.max(1)];
2337    let ins = code.ins_size.min(code.registers_size) as usize;
2338    let mut reg = n.saturating_sub(ins);
2339    let mut pidx = 0;
2340    if !env.is_static && reg < n {
2341        // `this` occupies the first incoming register.
2342        let id = vt.vars.len() as u32;
2343        vt.vars.push(VarInfo {
2344            id,
2345            slot: reg as u16,
2346            name: "this".into(),
2347            ty: TypeRef::J(JavaType::Object(env.class_name.as_str().into())),
2348            is_param: true,
2349            range_start: 0,
2350            range_end: u16::MAX,
2351            synthetic_name: false,
2352        });
2353        while vt.by_slot.len() <= reg {
2354            vt.by_slot.push(Vec::new());
2355        }
2356        vt.by_slot[reg].push((0, u16::MAX, id));
2357        regs[reg] = Reg::Live(id);
2358        reg += 1;
2359        pidx += 1;
2360    }
2361    for (i, arg) in env.desc.args.iter().enumerate() {
2362        if reg >= n {
2363            break;
2364        }
2365        let dbg = param_names.get(i).and_then(|o| o.clone());
2366        let named = dbg.is_some();
2367        // Debug-info names are ARBITRARY DEX strings — obfuscated apps
2368        // ship params named `25` (reqable a4/e: rendered `25 = 25;`, a
2369        // javac parse error). Run them through the same deterministic
2370        // sanitizer as every other declared identifier.
2371        let name = dbg
2372            .map(|d| crate::classdec::java_ident(&d).into_owned())
2373            .unwrap_or_else(|| format!("p{}", pidx));
2374        let id = vt.vars.len() as u32;
2375        vt.vars.push(VarInfo {
2376            id,
2377            slot: reg as u16,
2378            name,
2379            ty: TypeRef::J(arg.clone()),
2380            is_param: true,
2381            range_start: 0,
2382            range_end: u16::MAX,
2383            synthetic_name: !named,
2384        });
2385        while vt.by_slot.len() <= reg {
2386            vt.by_slot.push(Vec::new());
2387        }
2388        vt.by_slot[reg].push((0, u16::MAX, id));
2389        regs[reg] = Reg::Live(id);
2390        reg += if arg.is_wide() { 2 } else { 1 };
2391        pidx += 1;
2392    }
2393    regs
2394}