swf-toolkit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -2
- package/README.tr.md +126 -0
- package/dist/abc/abc-file.d.ts +168 -0
- package/dist/abc/abc-file.js +656 -0
- package/dist/abc/analysis.d.ts +36 -0
- package/dist/abc/analysis.js +213 -0
- package/dist/abc/code.d.ts +59 -0
- package/dist/abc/code.js +379 -0
- package/dist/abc/constants.d.ts +80 -0
- package/dist/abc/constants.js +93 -0
- package/dist/abc/interner.d.ts +46 -0
- package/dist/abc/interner.js +168 -0
- package/dist/abc/model.d.ts +40 -0
- package/dist/abc/model.js +146 -0
- package/dist/abc/opcodes.d.ts +39 -0
- package/dist/abc/opcodes.js +196 -0
- package/dist/abc/pcode/assembler.d.ts +42 -0
- package/dist/abc/pcode/assembler.js +635 -0
- package/dist/abc/pcode/disassembler.d.ts +17 -0
- package/dist/abc/pcode/disassembler.js +147 -0
- package/dist/abc/pcode/format.d.ts +23 -0
- package/dist/abc/pcode/format.js +131 -0
- package/dist/abc/pcode/lexer.d.ts +34 -0
- package/dist/abc/pcode/lexer.js +183 -0
- package/dist/assets/assets.d.ts +26 -0
- package/dist/assets/assets.js +151 -0
- package/dist/assets/images.d.ts +31 -0
- package/dist/assets/images.js +238 -0
- package/dist/assets/png.d.ts +12 -0
- package/dist/assets/png.js +196 -0
- package/dist/assets/sounds.d.ts +37 -0
- package/dist/assets/sounds.js +236 -0
- package/dist/avm1/avm1.d.ts +55 -0
- package/dist/avm1/avm1.js +642 -0
- package/dist/avm1/project.d.ts +16 -0
- package/dist/avm1/project.js +81 -0
- package/dist/cjs/abc/abc-file.d.ts +168 -0
- package/dist/cjs/abc/abc-file.js +663 -0
- package/dist/cjs/abc/analysis.d.ts +36 -0
- package/dist/cjs/abc/analysis.js +221 -0
- package/dist/cjs/abc/code.d.ts +59 -0
- package/dist/cjs/abc/code.js +387 -0
- package/dist/cjs/abc/constants.d.ts +80 -0
- package/dist/cjs/abc/constants.js +97 -0
- package/dist/cjs/abc/interner.d.ts +46 -0
- package/dist/cjs/abc/interner.js +172 -0
- package/dist/cjs/abc/model.d.ts +40 -0
- package/dist/cjs/abc/model.js +155 -0
- package/dist/cjs/abc/opcodes.d.ts +39 -0
- package/dist/cjs/abc/opcodes.js +201 -0
- package/dist/cjs/abc/pcode/assembler.d.ts +42 -0
- package/dist/cjs/abc/pcode/assembler.js +640 -0
- package/dist/cjs/abc/pcode/disassembler.d.ts +17 -0
- package/dist/cjs/abc/pcode/disassembler.js +152 -0
- package/dist/cjs/abc/pcode/format.d.ts +23 -0
- package/dist/cjs/abc/pcode/format.js +141 -0
- package/dist/cjs/abc/pcode/lexer.d.ts +34 -0
- package/dist/cjs/abc/pcode/lexer.js +188 -0
- package/dist/cjs/assets/assets.d.ts +26 -0
- package/dist/cjs/assets/assets.js +159 -0
- package/dist/cjs/assets/images.d.ts +31 -0
- package/dist/cjs/assets/images.js +246 -0
- package/dist/cjs/assets/png.d.ts +12 -0
- package/dist/cjs/assets/png.js +202 -0
- package/dist/cjs/assets/sounds.d.ts +37 -0
- package/dist/cjs/assets/sounds.js +246 -0
- package/dist/cjs/avm1/avm1.d.ts +55 -0
- package/dist/cjs/avm1/avm1.js +649 -0
- package/dist/cjs/avm1/project.d.ts +16 -0
- package/dist/cjs/avm1/project.js +88 -0
- package/dist/cjs/compiler/ast.d.ts +285 -0
- package/dist/cjs/compiler/ast.js +3 -0
- package/dist/cjs/compiler/class-compiler.d.ts +34 -0
- package/dist/cjs/compiler/class-compiler.js +440 -0
- package/dist/cjs/compiler/codegen.d.ts +144 -0
- package/dist/cjs/compiler/codegen.js +1589 -0
- package/dist/cjs/compiler/lexer.d.ts +27 -0
- package/dist/cjs/compiler/lexer.js +254 -0
- package/dist/cjs/compiler/parser.d.ts +58 -0
- package/dist/cjs/compiler/parser.js +989 -0
- package/dist/cjs/compiler/program.d.ts +29 -0
- package/dist/cjs/compiler/program.js +36 -0
- package/dist/cjs/compiler/scope.d.ts +22 -0
- package/dist/cjs/compiler/scope.js +242 -0
- package/dist/cjs/compression/lzma.d.ts +37 -0
- package/dist/cjs/compression/lzma.js +575 -0
- package/dist/cjs/compression/swf-container.d.ts +19 -0
- package/dist/cjs/compression/swf-container.js +92 -0
- package/dist/cjs/decompiler/ast.d.ts +228 -0
- package/dist/cjs/decompiler/ast.js +455 -0
- package/dist/cjs/decompiler/cfg.d.ts +40 -0
- package/dist/cjs/decompiler/cfg.js +236 -0
- package/dist/cjs/decompiler/class-printer.d.ts +9 -0
- package/dist/cjs/decompiler/class-printer.js +173 -0
- package/dist/cjs/decompiler/method.d.ts +41 -0
- package/dist/cjs/decompiler/method.js +436 -0
- package/dist/cjs/decompiler/simulate.d.ts +55 -0
- package/dist/cjs/decompiler/simulate.js +591 -0
- package/dist/cjs/decompiler/structure.d.ts +48 -0
- package/dist/cjs/decompiler/structure.js +764 -0
- package/dist/cjs/errors.d.ts +11 -0
- package/dist/cjs/errors.js +25 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/index.js +82 -0
- package/dist/cjs/io/reader.d.ts +48 -0
- package/dist/cjs/io/reader.js +180 -0
- package/dist/cjs/io/writer.d.ts +37 -0
- package/dist/cjs/io/writer.js +175 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/project/pcode-project.d.ts +32 -0
- package/dist/cjs/project/pcode-project.js +210 -0
- package/dist/cjs/project/source-export.d.ts +12 -0
- package/dist/cjs/project/source-export.js +51 -0
- package/dist/cjs/project/source-import.d.ts +13 -0
- package/dist/cjs/project/source-import.js +73 -0
- package/dist/cjs/swf/swf.d.ts +63 -0
- package/dist/cjs/swf/swf.js +147 -0
- package/dist/cjs/swf/tag-codes.d.ts +77 -0
- package/dist/cjs/swf/tag-codes.js +109 -0
- package/dist/cjs/swf/tags.d.ts +118 -0
- package/dist/cjs/swf/tags.js +330 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +296 -0
- package/dist/compiler/ast.d.ts +285 -0
- package/dist/compiler/ast.js +2 -0
- package/dist/compiler/class-compiler.d.ts +34 -0
- package/dist/compiler/class-compiler.js +437 -0
- package/dist/compiler/codegen.d.ts +144 -0
- package/dist/compiler/codegen.js +1585 -0
- package/dist/compiler/lexer.d.ts +27 -0
- package/dist/compiler/lexer.js +249 -0
- package/dist/compiler/parser.d.ts +58 -0
- package/dist/compiler/parser.js +984 -0
- package/dist/compiler/program.d.ts +29 -0
- package/dist/compiler/program.js +32 -0
- package/dist/compiler/scope.d.ts +22 -0
- package/dist/compiler/scope.js +238 -0
- package/dist/compression/lzma.d.ts +37 -0
- package/dist/compression/lzma.js +569 -0
- package/dist/compression/swf-container.d.ts +19 -0
- package/dist/compression/swf-container.js +87 -0
- package/dist/decompiler/ast.d.ts +228 -0
- package/dist/decompiler/ast.js +441 -0
- package/dist/decompiler/cfg.d.ts +40 -0
- package/dist/decompiler/cfg.js +231 -0
- package/dist/decompiler/class-printer.d.ts +9 -0
- package/dist/decompiler/class-printer.js +170 -0
- package/dist/decompiler/method.d.ts +41 -0
- package/dist/decompiler/method.js +427 -0
- package/dist/decompiler/simulate.d.ts +55 -0
- package/dist/decompiler/simulate.js +586 -0
- package/dist/decompiler/structure.d.ts +48 -0
- package/dist/decompiler/structure.js +760 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +49 -0
- package/dist/io/reader.d.ts +48 -0
- package/dist/io/reader.js +176 -0
- package/dist/io/writer.d.ts +37 -0
- package/dist/io/writer.js +169 -0
- package/dist/project/pcode-project.d.ts +32 -0
- package/dist/project/pcode-project.js +203 -0
- package/dist/project/source-export.d.ts +12 -0
- package/dist/project/source-export.js +47 -0
- package/dist/project/source-import.d.ts +13 -0
- package/dist/project/source-import.js +70 -0
- package/dist/swf/swf.d.ts +63 -0
- package/dist/swf/swf.js +143 -0
- package/dist/swf/tag-codes.d.ts +77 -0
- package/dist/swf/tag-codes.js +105 -0
- package/dist/swf/tags.d.ts +118 -0
- package/dist/swf/tags.js +316 -0
- package/package.json +71 -4
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import { MultinameKind, NamespaceKind, TraitKind } from '../abc/constants.js';
|
|
2
|
+
import { Op, opInfo } from '../abc/opcodes.js';
|
|
3
|
+
import { formatNamespace } from '../abc/pcode/format.js';
|
|
4
|
+
import { exprEquals, hasSideEffects, lit, not, raw, readsLocal } from './ast.js';
|
|
5
|
+
export class SimulationError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/** Expressions that were duplicated with `dup` (shared across blocks of a method). */
|
|
8
|
+
const duplicated = new WeakSet();
|
|
9
|
+
const BINOPS = {
|
|
10
|
+
[Op.add]: '+',
|
|
11
|
+
[Op.add_i]: '+',
|
|
12
|
+
[Op.subtract]: '-',
|
|
13
|
+
[Op.subtract_i]: '-',
|
|
14
|
+
[Op.multiply]: '*',
|
|
15
|
+
[Op.multiply_i]: '*',
|
|
16
|
+
[Op.divide]: '/',
|
|
17
|
+
[Op.modulo]: '%',
|
|
18
|
+
[Op.lshift]: '<<',
|
|
19
|
+
[Op.rshift]: '>>',
|
|
20
|
+
[Op.urshift]: '>>>',
|
|
21
|
+
[Op.bitand]: '&',
|
|
22
|
+
[Op.bitor]: '|',
|
|
23
|
+
[Op.bitxor]: '^',
|
|
24
|
+
[Op.equals]: '==',
|
|
25
|
+
[Op.strictequals]: '===',
|
|
26
|
+
[Op.lessthan]: '<',
|
|
27
|
+
[Op.lessequals]: '<=',
|
|
28
|
+
[Op.greaterthan]: '>',
|
|
29
|
+
[Op.greaterequals]: '>=',
|
|
30
|
+
[Op.instanceof]: 'instanceof',
|
|
31
|
+
[Op.istypelate]: 'is',
|
|
32
|
+
[Op.astypelate]: 'as',
|
|
33
|
+
[Op.in]: 'in',
|
|
34
|
+
};
|
|
35
|
+
const CONDOPS = {
|
|
36
|
+
[Op.ifeq]: ['==', false],
|
|
37
|
+
[Op.ifne]: ['!=', false],
|
|
38
|
+
[Op.iflt]: ['<', false],
|
|
39
|
+
[Op.ifle]: ['<=', false],
|
|
40
|
+
[Op.ifgt]: ['>', false],
|
|
41
|
+
[Op.ifge]: ['>=', false],
|
|
42
|
+
[Op.ifstricteq]: ['===', false],
|
|
43
|
+
[Op.ifstrictne]: ['!==', false],
|
|
44
|
+
[Op.ifnlt]: ['<', true],
|
|
45
|
+
[Op.ifnle]: ['<=', true],
|
|
46
|
+
[Op.ifngt]: ['>', true],
|
|
47
|
+
[Op.ifnge]: ['>=', true],
|
|
48
|
+
};
|
|
49
|
+
const COERCE_TYPES = {
|
|
50
|
+
[Op.coerce_a]: '*',
|
|
51
|
+
[Op.coerce_s]: 'String',
|
|
52
|
+
[Op.coerce_b]: 'Boolean',
|
|
53
|
+
[Op.coerce_i]: 'int',
|
|
54
|
+
[Op.coerce_u]: 'uint',
|
|
55
|
+
[Op.coerce_d]: 'Number',
|
|
56
|
+
[Op.coerce_o]: 'Object',
|
|
57
|
+
[Op.convert_s]: 'String',
|
|
58
|
+
[Op.convert_b]: 'Boolean',
|
|
59
|
+
[Op.convert_i]: 'int',
|
|
60
|
+
[Op.convert_u]: 'uint',
|
|
61
|
+
[Op.convert_d]: 'Number',
|
|
62
|
+
[Op.convert_o]: 'Object',
|
|
63
|
+
};
|
|
64
|
+
function slotName(traits, abc, slot) {
|
|
65
|
+
const t = traits.find((x) => (x.kind === TraitKind.Slot || x.kind === TraitKind.Const || x.kind === TraitKind.Class || x.kind === TraitKind.Function) && x.id === slot);
|
|
66
|
+
return t ? abc.multinameName(t.name) : undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Simulates `insns` with the given entry stack, appending statements to `out`. */
|
|
69
|
+
export function simulate(ctx, insns, entryStack, out) {
|
|
70
|
+
const { abc, scope } = ctx;
|
|
71
|
+
const stack = [...entryStack];
|
|
72
|
+
const pop = () => {
|
|
73
|
+
const v = stack.pop();
|
|
74
|
+
if (!v)
|
|
75
|
+
throw new SimulationError('Stack underflow');
|
|
76
|
+
return v;
|
|
77
|
+
};
|
|
78
|
+
const popN = (n) => {
|
|
79
|
+
const xs = [];
|
|
80
|
+
for (let i = 0; i < n; i++)
|
|
81
|
+
xs.push(pop());
|
|
82
|
+
return xs.reverse();
|
|
83
|
+
};
|
|
84
|
+
const emit = (s) => {
|
|
85
|
+
out.push(s);
|
|
86
|
+
};
|
|
87
|
+
/** Builds the member expression for a multiname operand, popping runtime parts. */
|
|
88
|
+
const member = (obj, mnIndex, popObj) => {
|
|
89
|
+
const m = abc.multinames[mnIndex];
|
|
90
|
+
let nameExpr;
|
|
91
|
+
let nsExpr;
|
|
92
|
+
if (m) {
|
|
93
|
+
switch (m.kind) {
|
|
94
|
+
case MultinameKind.RTQNameL:
|
|
95
|
+
case MultinameKind.RTQNameLA:
|
|
96
|
+
nameExpr = pop();
|
|
97
|
+
nsExpr = pop();
|
|
98
|
+
break;
|
|
99
|
+
case MultinameKind.MultinameL:
|
|
100
|
+
case MultinameKind.MultinameLA:
|
|
101
|
+
nameExpr = pop();
|
|
102
|
+
break;
|
|
103
|
+
case MultinameKind.RTQName:
|
|
104
|
+
case MultinameKind.RTQNameA:
|
|
105
|
+
nsExpr = pop();
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const target = popObj ? pop() : (obj ?? { k: 'global' });
|
|
110
|
+
const attr = !!m && [MultinameKind.QNameA, MultinameKind.RTQNameA, MultinameKind.RTQNameLA, MultinameKind.MultinameA, MultinameKind.MultinameLA].includes(m.kind);
|
|
111
|
+
if (nameExpr)
|
|
112
|
+
return { k: 'index', obj: target, index: nameExpr, attr };
|
|
113
|
+
if (m && (m.kind === MultinameKind.QName || m.kind === MultinameKind.QNameA)) {
|
|
114
|
+
const ns = abc.namespaces[m.ns];
|
|
115
|
+
if (ns && ns.kind === NamespaceKind.Namespace) {
|
|
116
|
+
const uri = abc.strings[ns.name] ?? '';
|
|
117
|
+
// Hide AS3 builtin and interface ("pkg:Interface") namespaces.
|
|
118
|
+
if (uri !== 'http://adobe.com/AS3/2006/builtin' && !/^[\w.$]*:[\w$]+$/.test(uri)) {
|
|
119
|
+
return { k: 'member', obj: target, name: abc.multinameName(mnIndex), attr, ns: nsLabel(uri) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (nsExpr) {
|
|
124
|
+
const ns = nsExpr.k === 'name' ? nsExpr.name : nsExpr.k === 'member' && nsExpr.obj.k !== 'findprop' ? undefined : nsExpr.k === 'member' ? nsExpr.name : undefined;
|
|
125
|
+
return { k: 'member', obj: target, name: abc.multinameName(mnIndex), attr, ns: ns ?? '*' };
|
|
126
|
+
}
|
|
127
|
+
return { k: 'member', obj: target, name: abc.multinameName(mnIndex), attr };
|
|
128
|
+
};
|
|
129
|
+
/** Simplify `findprop(x).x` → `x`. */
|
|
130
|
+
const access = (e) => {
|
|
131
|
+
if (e.k === 'member' && (e.obj.k === 'findprop' || e.obj.k === 'global') && !e.ns)
|
|
132
|
+
return { k: 'name', name: e.name };
|
|
133
|
+
return e;
|
|
134
|
+
};
|
|
135
|
+
/** Assignment with dup / postfix handling. */
|
|
136
|
+
const assign = (target, value, isLocalReg) => {
|
|
137
|
+
// Value duplicated on the stack: the remaining copies now equal the target.
|
|
138
|
+
let dup = false;
|
|
139
|
+
const bare = value.k === 'coerce' ? stripCoerceLocal(value) : value;
|
|
140
|
+
for (let i = 0; i < stack.length; i++) {
|
|
141
|
+
if (stack[i] === value || stack[i] === bare) {
|
|
142
|
+
stack[i] = target;
|
|
143
|
+
dup = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Postfix: old value still on the stack, new value = old ± 1.
|
|
147
|
+
const v = value.k === 'coerce' ? value.e : value;
|
|
148
|
+
if (!dup && v.k === 'binary' && (v.op === '+' || v.op === '-') && v.b.k === 'lit' && v.b.v === 1 && exprEquals(v.a, target)) {
|
|
149
|
+
const idx = stack.findIndex((s) => exprEquals(s, target));
|
|
150
|
+
if (idx >= 0) {
|
|
151
|
+
stack[idx] = { k: 'postfix', op: v.op === '+' ? '++' : '--', e: target };
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (isLocalReg !== undefined) {
|
|
156
|
+
// Old value of the register still referenced on the stack: freeze it.
|
|
157
|
+
for (let i = 0; i < stack.length; i++) {
|
|
158
|
+
if (readsLocal(stack[i], isLocalReg) && stack[i] !== target) {
|
|
159
|
+
// Cannot express safely; keep textual order (rare in compiler output).
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
emit({ k: 'expr', e: { k: 'assign', target, v: value } });
|
|
164
|
+
};
|
|
165
|
+
const getLocal = (reg) => {
|
|
166
|
+
if (reg === 0)
|
|
167
|
+
return { k: 'this' };
|
|
168
|
+
return ctx.specialRegs.get(reg) ?? { k: 'local', reg };
|
|
169
|
+
};
|
|
170
|
+
const setLocal = (reg, value) => {
|
|
171
|
+
if (value.k === 'activation' || value.k === 'catchscope' || value.k === 'findprop' || value.k === 'global') {
|
|
172
|
+
ctx.specialRegs.set(reg, value);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
ctx.specialRegs.delete(reg);
|
|
176
|
+
assign({ k: 'local', reg }, value, reg);
|
|
177
|
+
};
|
|
178
|
+
const resolveSlot = (obj, slot) => {
|
|
179
|
+
switch (obj.k) {
|
|
180
|
+
case 'activation': {
|
|
181
|
+
const n = slotName(ctx.activationTraits, abc, slot);
|
|
182
|
+
return n ? { k: 'name', name: n } : { k: 'slot', obj, slot };
|
|
183
|
+
}
|
|
184
|
+
case 'catchscope':
|
|
185
|
+
return { k: 'name', name: ctx.catchNames.get(obj.index) ?? `e${obj.index}` };
|
|
186
|
+
case 'global': {
|
|
187
|
+
const n = slotName(ctx.globalTraits, abc, slot);
|
|
188
|
+
return n ? { k: 'name', name: n } : { k: 'slot', obj, slot };
|
|
189
|
+
}
|
|
190
|
+
case 'this': {
|
|
191
|
+
const n = slotName(ctx.thisTraits, abc, slot);
|
|
192
|
+
return n ? { k: 'member', obj, name: n } : { k: 'slot', obj, slot };
|
|
193
|
+
}
|
|
194
|
+
default:
|
|
195
|
+
return { k: 'slot', obj, slot };
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
for (let pc = 0; pc < insns.length; pc++) {
|
|
199
|
+
const ins = insns[pc];
|
|
200
|
+
const a = ins.args;
|
|
201
|
+
const op = ins.op;
|
|
202
|
+
const bin = BINOPS[op];
|
|
203
|
+
if (bin) {
|
|
204
|
+
const b = pop();
|
|
205
|
+
const x = pop();
|
|
206
|
+
stack.push({ k: 'binary', op: bin, a: x, b });
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const coerceType = COERCE_TYPES[op];
|
|
210
|
+
if (coerceType) {
|
|
211
|
+
stack.push({ k: 'coerce', type: coerceType, e: pop() });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
switch (op) {
|
|
215
|
+
case Op.nop:
|
|
216
|
+
case Op.label:
|
|
217
|
+
case Op.bkpt:
|
|
218
|
+
case Op.debugline:
|
|
219
|
+
case Op.debugfile:
|
|
220
|
+
case Op.bkptline:
|
|
221
|
+
case Op.timestamp:
|
|
222
|
+
case Op.kill:
|
|
223
|
+
break;
|
|
224
|
+
case Op.debug:
|
|
225
|
+
if (a[0] === 1 && a[1])
|
|
226
|
+
ctx.regNames.set((a[2] ?? 0) + 1, abc.strings[a[1]] ?? '');
|
|
227
|
+
break;
|
|
228
|
+
case Op.throw:
|
|
229
|
+
return { stack, term: { k: 'throw', e: pop() } };
|
|
230
|
+
case Op.returnvoid:
|
|
231
|
+
return { stack, term: { k: 'return' } };
|
|
232
|
+
case Op.returnvalue:
|
|
233
|
+
return { stack, term: { k: 'return', e: pop() } };
|
|
234
|
+
case Op.jump:
|
|
235
|
+
return { stack, term: { k: 'jump' } };
|
|
236
|
+
case Op.iftrue:
|
|
237
|
+
return { stack, term: { k: 'cond', c: pop() } };
|
|
238
|
+
case Op.iffalse:
|
|
239
|
+
return { stack, term: { k: 'cond', c: not(pop()) } };
|
|
240
|
+
case Op.lookupswitch:
|
|
241
|
+
return { stack, term: { k: 'switch', e: pop() } };
|
|
242
|
+
case Op.getsuper: {
|
|
243
|
+
const m = member(null, a[0], true);
|
|
244
|
+
stack.push(m.k === 'member' ? { ...m, obj: { k: 'super' } } : m);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case Op.setsuper: {
|
|
248
|
+
const v = pop();
|
|
249
|
+
const m = member(null, a[0], true);
|
|
250
|
+
assign(m.k === 'member' ? { ...m, obj: { k: 'super' } } : m, v);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case Op.dxns:
|
|
254
|
+
emit({ k: 'comment', text: `default xml namespace = ${JSON.stringify(abc.strings[a[0]] ?? '')}` });
|
|
255
|
+
break;
|
|
256
|
+
case Op.dxnslate:
|
|
257
|
+
emit({ k: 'comment', text: 'default xml namespace = <runtime>' });
|
|
258
|
+
pop();
|
|
259
|
+
break;
|
|
260
|
+
case Op.pushwith:
|
|
261
|
+
scope.push(pop());
|
|
262
|
+
emit({ k: 'comment', text: 'with(...) scope begins' });
|
|
263
|
+
break;
|
|
264
|
+
case Op.pushscope:
|
|
265
|
+
scope.push(pop());
|
|
266
|
+
break;
|
|
267
|
+
case Op.popscope:
|
|
268
|
+
scope.pop();
|
|
269
|
+
break;
|
|
270
|
+
case Op.nextname:
|
|
271
|
+
case Op.nextvalue: {
|
|
272
|
+
const idx = pop();
|
|
273
|
+
const obj = pop();
|
|
274
|
+
stack.push({ k: 'next', value: op === Op.nextvalue, obj, idx });
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case Op.hasnext: {
|
|
278
|
+
const idx = pop();
|
|
279
|
+
const obj = pop();
|
|
280
|
+
stack.push({ k: 'hasnext', obj, idx });
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case Op.hasnext2:
|
|
284
|
+
stack.push({ k: 'hasnext2', objReg: a[0], idxReg: a[1] });
|
|
285
|
+
break;
|
|
286
|
+
case Op.pushnull:
|
|
287
|
+
stack.push(lit(null));
|
|
288
|
+
break;
|
|
289
|
+
case Op.pushundefined:
|
|
290
|
+
stack.push(lit(undefined));
|
|
291
|
+
break;
|
|
292
|
+
case Op.pushtrue:
|
|
293
|
+
stack.push(lit(true));
|
|
294
|
+
break;
|
|
295
|
+
case Op.pushfalse:
|
|
296
|
+
stack.push(lit(false));
|
|
297
|
+
break;
|
|
298
|
+
case Op.pushnan:
|
|
299
|
+
stack.push(lit(NaN));
|
|
300
|
+
break;
|
|
301
|
+
case Op.pushbyte:
|
|
302
|
+
case Op.pushshort:
|
|
303
|
+
stack.push(lit(a[0]));
|
|
304
|
+
break;
|
|
305
|
+
case Op.pushstring:
|
|
306
|
+
stack.push(lit(abc.strings[a[0]] ?? ''));
|
|
307
|
+
break;
|
|
308
|
+
case Op.pushint:
|
|
309
|
+
stack.push(lit(abc.ints[a[0]] ?? 0));
|
|
310
|
+
break;
|
|
311
|
+
case Op.pushuint:
|
|
312
|
+
stack.push(lit(abc.uints[a[0]] ?? 0));
|
|
313
|
+
break;
|
|
314
|
+
case Op.pushdouble:
|
|
315
|
+
stack.push(lit(abc.doubles[a[0]] ?? NaN));
|
|
316
|
+
break;
|
|
317
|
+
case Op.pushnamespace:
|
|
318
|
+
stack.push(raw(formatNamespace(abc, a[0])));
|
|
319
|
+
break;
|
|
320
|
+
case Op.pop: {
|
|
321
|
+
const v = pop();
|
|
322
|
+
// A duplicated value has already been consumed elsewhere (e.g. by a branch).
|
|
323
|
+
if (hasSideEffects(v) && !duplicated.has(v))
|
|
324
|
+
emit({ k: 'expr', e: v });
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case Op.dup: {
|
|
328
|
+
const v = pop();
|
|
329
|
+
duplicated.add(v);
|
|
330
|
+
stack.push(v, v);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
case Op.swap: {
|
|
334
|
+
const x = pop();
|
|
335
|
+
const y = pop();
|
|
336
|
+
stack.push(x, y);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case Op.li8:
|
|
340
|
+
case Op.li16:
|
|
341
|
+
case Op.li32:
|
|
342
|
+
case Op.lf32:
|
|
343
|
+
case Op.lf64:
|
|
344
|
+
case Op.sxi1:
|
|
345
|
+
case Op.sxi8:
|
|
346
|
+
case Op.sxi16:
|
|
347
|
+
stack.push({ k: 'call', fn: raw(opInfo(op).name), args: [pop()] });
|
|
348
|
+
break;
|
|
349
|
+
case Op.si8:
|
|
350
|
+
case Op.si16:
|
|
351
|
+
case Op.si32:
|
|
352
|
+
case Op.sf32:
|
|
353
|
+
case Op.sf64: {
|
|
354
|
+
const addr = pop();
|
|
355
|
+
const v = pop();
|
|
356
|
+
emit({ k: 'expr', e: { k: 'call', fn: raw(opInfo(op).name), args: [v, addr] } });
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case Op.newfunction:
|
|
360
|
+
stack.push({ k: 'function', methodIndex: a[0] });
|
|
361
|
+
break;
|
|
362
|
+
case Op.call: {
|
|
363
|
+
const args = popN(a[0]);
|
|
364
|
+
pop(); // receiver
|
|
365
|
+
const fn = pop();
|
|
366
|
+
stack.push({ k: 'call', fn, args });
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case Op.construct: {
|
|
370
|
+
const args = popN(a[0]);
|
|
371
|
+
stack.push({ k: 'new', ctor: pop(), args });
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case Op.callmethod: {
|
|
375
|
+
const args = popN(a[1]);
|
|
376
|
+
const obj = pop();
|
|
377
|
+
stack.push({ k: 'call', fn: { k: 'member', obj, name: `/*method#${a[0]}*/` }, args });
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case Op.callstatic: {
|
|
381
|
+
const args = popN(a[1]);
|
|
382
|
+
pop();
|
|
383
|
+
stack.push({ k: 'call', fn: raw(`/*static method ${a[0]}*/`), args });
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
case Op.callsuper:
|
|
387
|
+
case Op.callsupervoid: {
|
|
388
|
+
const args = popN(a[1]);
|
|
389
|
+
const m = member(null, a[0], true);
|
|
390
|
+
const call = { k: 'call', fn: m.k === 'member' ? { ...m, obj: { k: 'super' } } : m, args };
|
|
391
|
+
if (op === Op.callsupervoid)
|
|
392
|
+
emit({ k: 'expr', e: call });
|
|
393
|
+
else
|
|
394
|
+
stack.push(call);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case Op.callproperty:
|
|
398
|
+
case Op.callproplex:
|
|
399
|
+
case Op.callpropvoid: {
|
|
400
|
+
const args = popN(a[1]);
|
|
401
|
+
const call = { k: 'call', fn: access(member(null, a[0], true)), args };
|
|
402
|
+
if (op === Op.callpropvoid)
|
|
403
|
+
emit({ k: 'expr', e: call });
|
|
404
|
+
else
|
|
405
|
+
stack.push(call);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
case Op.constructsuper: {
|
|
409
|
+
const args = popN(a[0]);
|
|
410
|
+
pop();
|
|
411
|
+
emit({ k: 'expr', e: { k: 'call', fn: { k: 'super' }, args } });
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
case Op.constructprop: {
|
|
415
|
+
const args = popN(a[1]);
|
|
416
|
+
stack.push({ k: 'new', ctor: access(member(null, a[0], true)), args });
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case Op.applytype: {
|
|
420
|
+
const params = popN(a[0]);
|
|
421
|
+
stack.push({ k: 'applytype', base: pop(), params });
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case Op.newobject: {
|
|
425
|
+
const flat = popN(a[0] * 2);
|
|
426
|
+
const props = [];
|
|
427
|
+
for (let i = 0; i < flat.length; i += 2)
|
|
428
|
+
props.push([flat[i], flat[i + 1]]);
|
|
429
|
+
stack.push({ k: 'object', props });
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case Op.newarray:
|
|
433
|
+
stack.push({ k: 'array', items: popN(a[0]) });
|
|
434
|
+
break;
|
|
435
|
+
case Op.newactivation:
|
|
436
|
+
stack.push({ k: 'activation' });
|
|
437
|
+
break;
|
|
438
|
+
case Op.newclass:
|
|
439
|
+
pop();
|
|
440
|
+
stack.push({ k: 'name', name: abc.multinameName(abc.instances[a[0]]?.name ?? 0) });
|
|
441
|
+
break;
|
|
442
|
+
case Op.getdescendants: {
|
|
443
|
+
const m = member(null, a[0], true);
|
|
444
|
+
stack.push(m.k === 'member' ? { k: 'descendants', obj: m.obj, name: m.name } : m);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
case Op.newcatch:
|
|
448
|
+
stack.push({ k: 'catchscope', index: a[0] });
|
|
449
|
+
break;
|
|
450
|
+
case Op.findpropstrict:
|
|
451
|
+
case Op.findproperty: {
|
|
452
|
+
const m = member({ k: 'global' }, a[0], false);
|
|
453
|
+
ctx.useName(a[0]);
|
|
454
|
+
stack.push(m.k === 'member' ? { k: 'findprop', name: m.name } : { k: 'global' });
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case Op.finddef:
|
|
458
|
+
ctx.useName(a[0]);
|
|
459
|
+
stack.push({ k: 'findprop', name: abc.multinameName(a[0]) });
|
|
460
|
+
break;
|
|
461
|
+
case Op.getlex:
|
|
462
|
+
ctx.useName(a[0]);
|
|
463
|
+
stack.push({ k: 'name', name: abc.multinameName(a[0]) });
|
|
464
|
+
break;
|
|
465
|
+
case Op.setproperty:
|
|
466
|
+
case Op.initproperty: {
|
|
467
|
+
const v = pop();
|
|
468
|
+
assign(access(member(null, a[0], true)), v);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case Op.getproperty:
|
|
472
|
+
stack.push(access(member(null, a[0], true)));
|
|
473
|
+
break;
|
|
474
|
+
case Op.deleteproperty:
|
|
475
|
+
stack.push({ k: 'unary', op: 'delete', e: access(member(null, a[0], true)) });
|
|
476
|
+
break;
|
|
477
|
+
case Op.getlocal:
|
|
478
|
+
stack.push(getLocal(a[0]));
|
|
479
|
+
break;
|
|
480
|
+
case Op.getlocal0:
|
|
481
|
+
case Op.getlocal1:
|
|
482
|
+
case Op.getlocal2:
|
|
483
|
+
case Op.getlocal3:
|
|
484
|
+
stack.push(getLocal(op - Op.getlocal0));
|
|
485
|
+
break;
|
|
486
|
+
case Op.setlocal:
|
|
487
|
+
setLocal(a[0], pop());
|
|
488
|
+
break;
|
|
489
|
+
case Op.setlocal0:
|
|
490
|
+
case Op.setlocal1:
|
|
491
|
+
case Op.setlocal2:
|
|
492
|
+
case Op.setlocal3:
|
|
493
|
+
setLocal(op - Op.setlocal0, pop());
|
|
494
|
+
break;
|
|
495
|
+
case Op.getglobalscope:
|
|
496
|
+
stack.push({ k: 'global' });
|
|
497
|
+
break;
|
|
498
|
+
case Op.getscopeobject:
|
|
499
|
+
stack.push(scope[a[0]] ?? { k: 'global' });
|
|
500
|
+
break;
|
|
501
|
+
case Op.getouterscope:
|
|
502
|
+
stack.push({ k: 'global' });
|
|
503
|
+
break;
|
|
504
|
+
case Op.getslot:
|
|
505
|
+
stack.push(resolveSlot(pop(), a[0]));
|
|
506
|
+
break;
|
|
507
|
+
case Op.setslot: {
|
|
508
|
+
const v = pop();
|
|
509
|
+
const obj = pop();
|
|
510
|
+
const target = resolveSlot(obj, a[0]);
|
|
511
|
+
if (obj.k === 'catchscope' && v.k === 'name' && target.k === 'name' && v.name === target.name)
|
|
512
|
+
break;
|
|
513
|
+
assign(target, v);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case Op.getglobalslot:
|
|
517
|
+
stack.push(resolveSlot({ k: 'global' }, a[0]));
|
|
518
|
+
break;
|
|
519
|
+
case Op.setglobalslot:
|
|
520
|
+
assign(resolveSlot({ k: 'global' }, a[0]), pop());
|
|
521
|
+
break;
|
|
522
|
+
case Op.esc_xelem:
|
|
523
|
+
case Op.esc_xattr:
|
|
524
|
+
case Op.checkfilter:
|
|
525
|
+
break; // value passes through
|
|
526
|
+
case Op.coerce:
|
|
527
|
+
stack.push({ k: 'coerce', type: ctx.typeName(a[0]), e: pop() });
|
|
528
|
+
break;
|
|
529
|
+
case Op.astype:
|
|
530
|
+
stack.push({ k: 'binary', op: 'as', a: pop(), b: { k: 'name', name: ctx.typeName(a[0]) } });
|
|
531
|
+
break;
|
|
532
|
+
case Op.istype:
|
|
533
|
+
stack.push({ k: 'binary', op: 'is', a: pop(), b: { k: 'name', name: ctx.typeName(a[0]) } });
|
|
534
|
+
break;
|
|
535
|
+
case Op.negate:
|
|
536
|
+
case Op.negate_i:
|
|
537
|
+
stack.push({ k: 'unary', op: '-', e: pop() });
|
|
538
|
+
break;
|
|
539
|
+
case Op.increment:
|
|
540
|
+
case Op.increment_i:
|
|
541
|
+
stack.push({ k: 'binary', op: '+', a: pop(), b: lit(1) });
|
|
542
|
+
break;
|
|
543
|
+
case Op.decrement:
|
|
544
|
+
case Op.decrement_i:
|
|
545
|
+
stack.push({ k: 'binary', op: '-', a: pop(), b: lit(1) });
|
|
546
|
+
break;
|
|
547
|
+
case Op.inclocal:
|
|
548
|
+
case Op.inclocal_i:
|
|
549
|
+
emit({ k: 'expr', e: { k: 'postfix', op: '++', e: getLocal(a[0]) } });
|
|
550
|
+
break;
|
|
551
|
+
case Op.declocal:
|
|
552
|
+
case Op.declocal_i:
|
|
553
|
+
emit({ k: 'expr', e: { k: 'postfix', op: '--', e: getLocal(a[0]) } });
|
|
554
|
+
break;
|
|
555
|
+
case Op.typeof:
|
|
556
|
+
stack.push({ k: 'unary', op: 'typeof', e: pop() });
|
|
557
|
+
break;
|
|
558
|
+
case Op.not:
|
|
559
|
+
stack.push(not(pop()));
|
|
560
|
+
break;
|
|
561
|
+
case Op.bitnot:
|
|
562
|
+
stack.push({ k: 'unary', op: '~', e: pop() });
|
|
563
|
+
break;
|
|
564
|
+
default: {
|
|
565
|
+
const cond = CONDOPS[op];
|
|
566
|
+
if (cond) {
|
|
567
|
+
const b = pop();
|
|
568
|
+
const x = pop();
|
|
569
|
+
const c = { k: 'binary', op: cond[0], a: x, b };
|
|
570
|
+
return { stack, term: { k: 'cond', c: cond[1] ? { k: 'unary', op: '!', e: c } : c } };
|
|
571
|
+
}
|
|
572
|
+
throw new SimulationError(`Unsupported instruction ${opInfo(op)?.name ?? op}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return { stack, term: { k: 'fall' } };
|
|
577
|
+
}
|
|
578
|
+
function stripCoerceLocal(e) {
|
|
579
|
+
while (e.k === 'coerce')
|
|
580
|
+
e = e.e;
|
|
581
|
+
return e;
|
|
582
|
+
}
|
|
583
|
+
function nsLabel(uri) {
|
|
584
|
+
const tail = uri.split(/[/:]/).filter(Boolean).pop() ?? 'ns';
|
|
585
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(tail) ? tail : 'ns';
|
|
586
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-flow structuring: rebuilds if/else, loops, switch and try/catch
|
|
3
|
+
* from the CFG, using dominator / post-dominator information.
|
|
4
|
+
*/
|
|
5
|
+
import type { Stmt } from './ast.js';
|
|
6
|
+
import { type Cfg } from './cfg.js';
|
|
7
|
+
import { type MethodContext } from './simulate.js';
|
|
8
|
+
export declare class Structurer {
|
|
9
|
+
private readonly ctx;
|
|
10
|
+
private readonly cfg;
|
|
11
|
+
private visited;
|
|
12
|
+
private loops;
|
|
13
|
+
private labelCounter;
|
|
14
|
+
private tries;
|
|
15
|
+
/** Labels of loop statements that need one, keyed by LoopCtx. */
|
|
16
|
+
private loopStmts;
|
|
17
|
+
constructor(ctx: MethodContext, cfg: Cfg);
|
|
18
|
+
run(): Stmt[];
|
|
19
|
+
/**
|
|
20
|
+
* If block `id` only evaluates a condition on top of `stack` (no statements,
|
|
21
|
+
* stack unchanged afterwards), returns that condition.
|
|
22
|
+
*/
|
|
23
|
+
private blockIsEmptyCondition;
|
|
24
|
+
/** Collapses short-circuit conditions (`a && b`, `a || b`) starting at a conditional block. */
|
|
25
|
+
private collapse;
|
|
26
|
+
private mergeOf;
|
|
27
|
+
/** Follows blocks that only contain `label` / `jump` / `nop` to their destination. */
|
|
28
|
+
private resolveTrivial;
|
|
29
|
+
/** Statement for jumping to `target` from inside the current constructs, or null. */
|
|
30
|
+
private jumpStmt;
|
|
31
|
+
private loopLabel;
|
|
32
|
+
private seq;
|
|
33
|
+
private evalCond;
|
|
34
|
+
private evalPath;
|
|
35
|
+
private emitLoop;
|
|
36
|
+
/**
|
|
37
|
+
* Natural loop body plus dead-end blocks (return/throw paths) dominated by
|
|
38
|
+
* the header: those are syntactically inside the loop even though they
|
|
39
|
+
* never branch back.
|
|
40
|
+
*/
|
|
41
|
+
private loopRegion;
|
|
42
|
+
/** Header condition for blocks that only compute a condition from scratch (e.g. hasnext2). */
|
|
43
|
+
private pureHeaderCondition;
|
|
44
|
+
private switchFollow;
|
|
45
|
+
private reaches;
|
|
46
|
+
private emitSwitch;
|
|
47
|
+
private emitTry;
|
|
48
|
+
}
|