tjs2dec\decompile/
expr_build.rs

1use anyhow::Result;
2
3use crate::model::{ConstPools, Tjs2File, Tjs2Object, Variant};
4use crate::vmcodes::vm;
5
6use super::expr::{BinOp, Expr, UnOp};
7use super::ssa::{Phi, SsaBlock, SsaInsn, SsaProgram, Var, VarId};
8
9#[derive(Debug, Clone)]
10pub enum Stmt {
11    Assign {
12        dst: VarId,
13        expr: Expr,
14    },
15    Store {
16        target: Expr,
17        value: Expr,
18    },
19    /// Read-modify-write update: target = target (op) rhs; optionally write the value into dst.
20    Update {
21        dst: Option<VarId>,
22        target: Expr,
23        op: BinOp,
24        rhs: Expr,
25    },
26    Expr(Expr),
27    Opaque {
28        op: &'static str,
29        args: Vec<Expr>,
30        defs: Vec<VarId>,
31    },
32}
33
34#[derive(Debug, Clone)]
35pub enum Terminator {
36    Jmp(usize),
37    Br {
38        cond: Expr,
39        if_true: usize,
40        if_false: usize,
41    },
42    Ret,
43    Throw(Expr),
44    Exit,
45    Fallthrough,
46}
47
48#[derive(Debug, Clone)]
49pub struct ExprBlock {
50    pub id: usize,
51    pub start_pc: usize,
52    pub pred: Vec<usize>,
53    pub succ: Vec<usize>,
54    pub phi: Vec<Phi>,
55    pub stmts: Vec<Stmt>,
56    pub term: Terminator,
57}
58
59#[derive(Debug, Clone)]
60pub struct ExprProgram {
61    pub obj_index: usize,
62    pub entry_block: usize,
63    pub blocks: Vec<ExprBlock>,
64}
65
66impl ExprProgram {
67    pub fn from_ssa(file: &Tjs2File, obj: &Tjs2Object, ssa: &SsaProgram) -> Result<Self> {
68        let mut blocks = Vec::new();
69        for b in &ssa.blocks {
70            blocks.push(lower_block(file, obj, b));
71        }
72        Ok(Self {
73            obj_index: ssa.obj_index,
74            entry_block: ssa.entry_block,
75            blocks,
76        })
77    }
78
79    pub fn dump(&self) -> String {
80        let mut out = String::new();
81        for b in &self.blocks {
82            out.push_str(&format!(
83                "-- bb{} @{} (pred={:?}, succ={:?})\n",
84                b.id, b.start_pc, b.pred, b.succ
85            ));
86            for p in &b.phi {
87                out.push_str(&format!("  {} = phi [", fmt_vid(p.result)));
88                for (i, (pred, v)) in p.args.iter().enumerate() {
89                    if i != 0 {
90                        out.push_str(", ");
91                    }
92                    out.push_str(&format!("bb{}: {}", pred, fmt_vid(*v)));
93                }
94                out.push_str("]\n");
95            }
96            for st in &b.stmts {
97                match st {
98                    Stmt::Assign { dst, expr } => {
99                        out.push_str(&format!("  {} = {};\n", fmt_vid(*dst), expr));
100                    }
101                    Stmt::Store { target, value } => {
102                        out.push_str(&format!("  {} = {};\n", target, value));
103                    }
104                    Stmt::Update {
105                        dst,
106                        target,
107                        op,
108                        rhs,
109                    } => {
110                        if let Some(d) = dst {
111                            out.push_str(&format!(
112                                "  {} = ({} {} {}); {} = {};\n",
113                                fmt_vid(*d),
114                                target,
115                                op.op_str(),
116                                rhs,
117                                target,
118                                fmt_vid(*d)
119                            ));
120                        } else {
121                            out.push_str(&format!(
122                                "  {} = ({} {} {});\n",
123                                target,
124                                target,
125                                op.op_str(),
126                                rhs
127                            ));
128                        }
129                    }
130                    Stmt::Expr(e) => out.push_str(&format!("  {};\n", e)),
131                    Stmt::Opaque { op, args, defs } => {
132                        if !defs.is_empty() {
133                            out.push_str("  ");
134                            for (i, d) in defs.iter().enumerate() {
135                                if i != 0 {
136                                    out.push_str(", ");
137                                }
138                                out.push_str(&fmt_vid(*d));
139                            }
140                            out.push_str(" = ");
141                        } else {
142                            out.push_str("  ");
143                        }
144                        out.push_str(op);
145                        if !args.is_empty() {
146                            out.push('(');
147                            for (i, a) in args.iter().enumerate() {
148                                if i != 0 {
149                                    out.push_str(", ");
150                                }
151                                out.push_str(&a.to_string());
152                            }
153                            out.push(')');
154                        }
155                        out.push('\n');
156                    }
157                }
158            }
159            out.push_str(&format!("  ;; term = {}\n\n", fmt_term(&b.term)));
160        }
161        out
162    }
163}
164
165fn lower_block(file: &Tjs2File, obj: &Tjs2Object, b: &SsaBlock) -> ExprBlock {
166    let mut stmts = Vec::new();
167
168    // Keep φ nodes verbatim for now (next stage will resolve them during structuring).
169    let phi = b.phi.clone();
170
171    // Lower instructions.
172    for insn in &b.insns {
173        lower_insn(file, obj, insn, &mut stmts);
174    }
175
176    // Derive terminator from last instruction when possible.
177    let term = derive_terminator(b, b.insns.last());
178
179    ExprBlock {
180        id: b.id,
181        start_pc: b.start_pc,
182        pred: b.pred.clone(),
183        succ: b.succ.clone(),
184        phi,
185        stmts,
186        term,
187    }
188}
189
190fn derive_terminator(b: &SsaBlock, last: Option<&SsaInsn>) -> Terminator {
191    let Some(insn) = last else {
192        return Terminator::Fallthrough;
193    };
194
195    match insn.op {
196        vm::VM_JMP => {
197            let tgt = b.succ.get(0).copied().unwrap_or(b.id);
198            Terminator::Jmp(tgt)
199        }
200        vm::VM_JF | vm::VM_JNF => {
201            let tgt = b.succ.get(0).copied().unwrap_or(b.id);
202            let fall = b.succ.get(1).copied().unwrap_or(b.id);
203            let flag = insn
204                .uses
205                .get(0)
206                .copied()
207                .map(Expr::SsaVar)
208                .unwrap_or(Expr::Flag);
209            // JF: jump if !flag ; JNF: jump if flag
210            let cond = if insn.op == vm::VM_JF {
211                Expr::Unary(UnOp::Not, Box::new(flag))
212            } else {
213                flag
214            };
215            Terminator::Br {
216                cond,
217                if_true: tgt,
218                if_false: fall,
219            }
220        }
221        vm::VM_RET => Terminator::Ret,
222        vm::VM_THROW => {
223            let e = insn
224                .uses
225                .get(0)
226                .copied()
227                .map(Expr::SsaVar)
228                .unwrap_or(Expr::Opaque("throw".into(), vec![]));
229            Terminator::Throw(e)
230        }
231        vm::VM_EXTRY => Terminator::Exit,
232        _ => Terminator::Fallthrough,
233    }
234}
235
236fn lower_insn(file: &Tjs2File, obj: &Tjs2Object, insn: &SsaInsn, out: &mut Vec<Stmt>) {
237    // Helpers
238    let use_e = |i: usize| insn.uses.get(i).copied().map(Expr::SsaVar);
239    let def0 = || insn.defs.get(0).copied();
240
241    match insn.op {
242        vm::VM_CP => {
243            if let (Some(dst), Some(src)) = (def0(), use_e(0)) {
244                out.push(Stmt::Assign { dst, expr: src });
245            } else {
246                out.push(Stmt::Opaque {
247                    op: insn.mnemonic,
248                    args: vec![],
249                    defs: insn.defs.clone(),
250                });
251            }
252        }
253
254        vm::VM_CONST => {
255            // raw_ops: [dst_reg, data_idx]
256            if let Some(dst) = def0() {
257                let data_idx = insn.raw_ops.get(1).copied().unwrap_or(0);
258                out.push(Stmt::Assign {
259                    dst,
260                    expr: data_to_expr(file, obj, data_idx),
261                });
262            }
263        }
264
265        vm::VM_CL => {
266            if let Some(dst) = def0() {
267                out.push(Stmt::Assign {
268                    dst,
269                    expr: Expr::Void,
270                });
271            }
272        }
273
274        vm::VM_CCL => {
275            // multiple defs: clear to void
276            for d in &insn.defs {
277                out.push(Stmt::Assign {
278                    dst: *d,
279                    expr: Expr::Void,
280                });
281            }
282        }
283
284        vm::VM_GLOBAL => {
285            if let Some(dst) = def0() {
286                out.push(Stmt::Assign {
287                    dst,
288                    expr: Expr::Opaque("global".into(), vec![]),
289                });
290            }
291        }
292
293        vm::VM_SETF => {
294            if let Some(dst) = def0() {
295                out.push(Stmt::Assign {
296                    dst,
297                    expr: Expr::Bool(true),
298                });
299            }
300        }
301        vm::VM_SETNF => {
302            if let Some(dst) = def0() {
303                out.push(Stmt::Assign {
304                    dst,
305                    expr: Expr::Bool(false),
306                });
307            }
308        }
309
310        // Flag producers (CEQ/CDEQ/CLT/CGT/CHKINS/TT/TF/NF already SSA-versioned)
311        vm::VM_CEQ | vm::VM_CDEQ | vm::VM_CLT | vm::VM_CGT | vm::VM_CHKINS => {
312            if let Some(dst) = def0() {
313                let a = use_e(0).unwrap_or(Expr::Opaque("a".into(), vec![]));
314                let b = use_e(1).unwrap_or(Expr::Opaque("b".into(), vec![]));
315                let op = match insn.op {
316                    vm::VM_CEQ => BinOp::Eq,
317                    vm::VM_CDEQ => BinOp::StrictEq,
318                    vm::VM_CLT => BinOp::Lt,
319                    vm::VM_CGT => BinOp::Gt,
320                    vm::VM_CHKINS => BinOp::In,
321                    _ => BinOp::Eq,
322                };
323                out.push(Stmt::Assign {
324                    dst,
325                    expr: Expr::Binary(op, Box::new(a), Box::new(b)),
326                });
327            }
328        }
329        vm::VM_TT => {
330            if let Some(dst) = def0() {
331                let a = use_e(0).unwrap_or(Expr::Opaque("a".into(), vec![]));
332                // !!a
333                let e = Expr::Unary(UnOp::Not, Box::new(Expr::Unary(UnOp::Not, Box::new(a))));
334                out.push(Stmt::Assign { dst, expr: e });
335            }
336        }
337        vm::VM_TF => {
338            if let Some(dst) = def0() {
339                let a = use_e(0).unwrap_or(Expr::Opaque("a".into(), vec![]));
340                out.push(Stmt::Assign {
341                    dst,
342                    expr: Expr::Unary(UnOp::Not, Box::new(a)),
343                });
344            }
345        }
346        vm::VM_NF => {
347            if let Some(dst) = def0() {
348                let a = use_e(0).unwrap_or(Expr::Opaque("flag".into(), vec![]));
349                out.push(Stmt::Assign {
350                    dst,
351                    expr: Expr::Unary(UnOp::Not, Box::new(a)),
352                });
353            }
354        }
355
356        // Property read
357        vm::VM_GPD | vm::VM_GPDS => {
358            if let Some(dst) = def0() {
359                let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
360                let data_idx = insn.raw_ops.get(2).copied().unwrap_or(0);
361                let key_e = data_to_expr(file, obj, data_idx);
362                let expr = prop_read(obj_e, key_e);
363                out.push(Stmt::Assign { dst, expr });
364            }
365        }
366        vm::VM_GPI | vm::VM_GPIS => {
367            if let Some(dst) = def0() {
368                let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
369                let key_e = use_e(1).unwrap_or(Expr::Opaque("key".into(), vec![]));
370                let expr = Expr::Index(Box::new(obj_e), Box::new(key_e));
371                out.push(Stmt::Assign { dst, expr });
372            }
373        }
374        vm::VM_GETP => {
375            if let Some(dst) = def0() {
376                let prop = use_e(0).unwrap_or(Expr::Opaque("prop".into(), vec![]));
377                out.push(Stmt::Assign {
378                    dst,
379                    expr: Expr::Deref(Box::new(prop)),
380                });
381            }
382        }
383
384        // Property store
385        vm::VM_SPD | vm::VM_SPDE | vm::VM_SPDEH | vm::VM_SPDS => {
386            let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
387            let val_e = use_e(1).unwrap_or(Expr::Opaque("val".into(), vec![]));
388            let data_idx = insn.raw_ops.get(1).copied().unwrap_or(0);
389            let key_e = data_to_expr(file, obj, data_idx);
390            let target = prop_lvalue(obj_e, key_e);
391            out.push(Stmt::Store {
392                target,
393                value: val_e,
394            });
395        }
396        vm::VM_SPI | vm::VM_SPIE | vm::VM_SPIS => {
397            let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
398            let key_e = use_e(1).unwrap_or(Expr::Opaque("key".into(), vec![]));
399            let val_e = use_e(2).unwrap_or(Expr::Opaque("val".into(), vec![]));
400            let target = Expr::Index(Box::new(obj_e), Box::new(key_e));
401            out.push(Stmt::Store {
402                target,
403                value: val_e,
404            });
405        }
406        vm::VM_SETP => {
407            let prop = use_e(0).unwrap_or(Expr::Opaque("prop".into(), vec![]));
408            let val = use_e(1).unwrap_or(Expr::Opaque("val".into(), vec![]));
409            out.push(Stmt::Store {
410                target: Expr::Deref(Box::new(prop)),
411                value: val,
412            });
413        }
414
415        // Calls
416        vm::VM_CALL => {
417            let dst = def0();
418            let func = use_e(0).unwrap_or(Expr::Opaque("func".into(), vec![]));
419            let args = insn
420                .uses
421                .iter()
422                .skip(1)
423                .map(|v| Expr::SsaVar(*v))
424                .collect::<Vec<_>>();
425            let call = Expr::Call(Box::new(func), args);
426            if let Some(d) = dst {
427                out.push(Stmt::Assign { dst: d, expr: call });
428            } else {
429                out.push(Stmt::Expr(call));
430            }
431        }
432        vm::VM_NEW => {
433            let dst = def0();
434            let ctor = use_e(0).unwrap_or(Expr::Opaque("ctor".into(), vec![]));
435            let args = insn
436                .uses
437                .iter()
438                .skip(1)
439                .map(|v| Expr::SsaVar(*v))
440                .collect::<Vec<_>>();
441            let e = Expr::New(Box::new(ctor), args);
442            if let Some(d) = dst {
443                out.push(Stmt::Assign { dst: d, expr: e });
444            } else {
445                out.push(Stmt::Expr(e));
446            }
447        }
448        vm::VM_CALLD => {
449            let dst = def0();
450            let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
451            let data_idx = insn.raw_ops.get(2).copied().unwrap_or(0);
452            let key_e = data_to_expr(file, obj, data_idx);
453            let args = insn
454                .uses
455                .iter()
456                .skip(1)
457                .map(|v| Expr::SsaVar(*v))
458                .collect::<Vec<_>>();
459            let call = method_call_or_index_call(obj_e, key_e, args);
460            if let Some(d) = dst {
461                out.push(Stmt::Assign { dst: d, expr: call });
462            } else {
463                out.push(Stmt::Expr(call));
464            }
465        }
466        vm::VM_CALLI => {
467            let dst = def0();
468            let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
469            let key_e = use_e(1).unwrap_or(Expr::Opaque("key".into(), vec![]));
470            let args = insn
471                .uses
472                .iter()
473                .skip(2)
474                .map(|v| Expr::SsaVar(*v))
475                .collect::<Vec<_>>();
476            let func = Expr::Index(Box::new(obj_e), Box::new(key_e));
477            let call = Expr::Call(Box::new(func), args);
478            if let Some(d) = dst {
479                out.push(Stmt::Assign { dst: d, expr: call });
480            } else {
481                out.push(Stmt::Expr(call));
482            }
483        }
484
485        // Fallback: represent as opaque op(defs, uses)
486        _ => {
487            let args = insn
488                .uses
489                .iter()
490                .map(|v| Expr::SsaVar(*v))
491                .collect::<Vec<_>>();
492            out.push(Stmt::Opaque {
493                op: insn.mnemonic,
494                args,
495                defs: insn.defs.clone(),
496            });
497        }
498    }
499}
500
501fn data_to_expr(file: &Tjs2File, obj: &Tjs2Object, data_idx: i32) -> Expr {
502    let v = obj.data.get(data_idx as usize).unwrap_or(&Variant::Unknown);
503    variant_to_expr(v, &file.const_pools)
504}
505
506fn variant_to_expr(v: &Variant, pools: &ConstPools) -> Expr {
507    match *v {
508        Variant::Void => Expr::Void,
509        Variant::NullObject => Expr::Null,
510        Variant::String(i) => Expr::Str(pools.strings.get(i as usize).cloned().unwrap_or_default()),
511        Variant::Octet(i) => Expr::Octet(pools.octets.get(i as usize).cloned().unwrap_or_default()),
512        Variant::Real(i) => Expr::Real(*pools.doubles.get(i as usize).unwrap_or(&f64::NAN)),
513        Variant::Byte(i) => Expr::Int(*pools.bytes.get(i as usize).unwrap_or(&0) as i64),
514        Variant::Short(i) => Expr::Int(*pools.shorts.get(i as usize).unwrap_or(&0) as i64),
515        Variant::Integer(i) => Expr::Int(*pools.ints.get(i as usize).unwrap_or(&0) as i64),
516        Variant::Long(i) => Expr::Int(*pools.longs.get(i as usize).unwrap_or(&0)),
517        Variant::InterObject(idx) => Expr::Opaque(format!("#InterObject({})", idx), vec![]),
518        Variant::InterGenerator(idx) => Expr::Opaque(format!("#InterGenerator({})", idx), vec![]),
519        Variant::Unknown => Expr::Opaque("#Unknown".into(), vec![]),
520    }
521}
522
523fn prop_read(obj: Expr, key: Expr) -> Expr {
524    // Prefer member when key is identifier string.
525    if let Expr::Str(s) = &key {
526        if is_ident_ascii(s) {
527            return Expr::Member(Box::new(obj), s.clone());
528        }
529    }
530    Expr::Index(Box::new(obj), Box::new(key))
531}
532
533fn prop_lvalue(obj: Expr, key: Expr) -> Expr {
534    // Same policy as read.
535    prop_read(obj, key)
536}
537
538fn method_call_or_index_call(base: Expr, key: Expr, args: Vec<Expr>) -> Expr {
539    if let Expr::Str(s) = &key {
540        if is_ident_ascii(s) {
541            return Expr::MethodCall {
542                base: Box::new(base),
543                member: s.clone(),
544                args,
545            };
546        }
547    }
548    let f = Expr::Index(Box::new(base), Box::new(key));
549    Expr::Call(Box::new(f), args)
550}
551
552pub fn is_ident_ascii(s: &str) -> bool {
553    let mut it = s.chars();
554    let Some(first) = it.next() else {
555        return false;
556    };
557    let ok_first = first == '_' || first.is_ascii_alphabetic();
558    if !ok_first {
559        return false;
560    }
561    it.all(|c| c == '_' || c.is_ascii_alphanumeric())
562}
563
564fn fmt_vid(v: VarId) -> String {
565    match v.var {
566        Var::Reg(r) => format!("r{}#{}", r, v.ver),
567        Var::Flag => format!("flag#{}", v.ver),
568        Var::Exception => format!("exc#{}", v.ver),
569    }
570}
571
572fn fmt_term(t: &Terminator) -> String {
573    match t {
574        Terminator::Jmp(b) => format!("jmp bb{}", b),
575        Terminator::Br {
576            cond,
577            if_true,
578            if_false,
579        } => format!("br ({}) ? bb{} : bb{}", cond, if_true, if_false),
580        Terminator::Ret => "ret".into(),
581        Terminator::Throw(e) => format!("throw {}", e),
582        Terminator::Exit => "exit".into(),
583        Terminator::Fallthrough => "fallthrough".into(),
584    }
585}