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,427 @@
|
|
|
1
|
+
import { decodeCode } from '../abc/code.js';
|
|
2
|
+
import { MethodFlags, MultinameKind, NamespaceKind } from '../abc/constants.js';
|
|
3
|
+
import { disassembleMethod } from '../abc/pcode/disassembler.js';
|
|
4
|
+
import { childLists, exprEquals, not, Printer, stripCoerce, walkStmts } from './ast.js';
|
|
5
|
+
import { buildCfg } from './cfg.js';
|
|
6
|
+
import { Structurer } from './structure.js';
|
|
7
|
+
/** Short type name for a multiname; records an import when needed. */
|
|
8
|
+
export function typeNameOf(env, mn) {
|
|
9
|
+
const { abc } = env;
|
|
10
|
+
if (mn === 0)
|
|
11
|
+
return '*';
|
|
12
|
+
const m = abc.multinames[mn];
|
|
13
|
+
if (!m)
|
|
14
|
+
return '*';
|
|
15
|
+
if (m.kind === MultinameKind.TypeName) {
|
|
16
|
+
return `${typeNameOf(env, m.base)}.<${m.params.map((p) => typeNameOf(env, p)).join(',')}>`;
|
|
17
|
+
}
|
|
18
|
+
useNameOf(env, mn);
|
|
19
|
+
return abc.multinameName(mn);
|
|
20
|
+
}
|
|
21
|
+
export function useNameOf(env, mn) {
|
|
22
|
+
const { abc } = env;
|
|
23
|
+
const m = abc.multinames[mn];
|
|
24
|
+
if (!m || (m.kind !== MultinameKind.QName && m.kind !== MultinameKind.QNameA))
|
|
25
|
+
return;
|
|
26
|
+
const ns = abc.namespaces[m.ns];
|
|
27
|
+
if (!ns || ns.kind !== NamespaceKind.PackageNamespace)
|
|
28
|
+
return;
|
|
29
|
+
const pkg = abc.strings[ns.name] ?? '';
|
|
30
|
+
if (!pkg || pkg === env.currentPackage || pkg === '__AS3__.vec')
|
|
31
|
+
return;
|
|
32
|
+
env.imports?.add(`${pkg}.${abc.strings[m.name] ?? ''}`);
|
|
33
|
+
}
|
|
34
|
+
export function paramNames(abc, method) {
|
|
35
|
+
return method.paramTypes.map((_, i) => {
|
|
36
|
+
const n = method.paramNames[i];
|
|
37
|
+
const s = n ? abc.strings[n] : undefined;
|
|
38
|
+
return s && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s) ? s : `param${i + 1}`;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Parameter list text: `a:int, b:String = "x", ...rest`. */
|
|
42
|
+
export function formatParams(env, methodIndex, printer) {
|
|
43
|
+
const { abc } = env;
|
|
44
|
+
const m = abc.methods[methodIndex];
|
|
45
|
+
const names = paramNames(abc, m);
|
|
46
|
+
const firstOptional = m.paramTypes.length - m.optional.length;
|
|
47
|
+
const parts = m.paramTypes.map((t, i) => {
|
|
48
|
+
let s = `${names[i]}:${typeNameOf(env, t)}`;
|
|
49
|
+
if (i >= firstOptional) {
|
|
50
|
+
const o = m.optional[i - firstOptional];
|
|
51
|
+
s += ` = ${formatDefault(abc, o.kind, o.value)}`;
|
|
52
|
+
}
|
|
53
|
+
return s;
|
|
54
|
+
});
|
|
55
|
+
if (m.flags & MethodFlags.NEED_REST)
|
|
56
|
+
parts.push('...rest');
|
|
57
|
+
void printer;
|
|
58
|
+
return parts.join(', ');
|
|
59
|
+
}
|
|
60
|
+
/** Default value literal for optional parameters and slots. */
|
|
61
|
+
export function formatDefault(abc, kind, index) {
|
|
62
|
+
switch (kind) {
|
|
63
|
+
case 0x03:
|
|
64
|
+
return String(abc.ints[index] ?? 0);
|
|
65
|
+
case 0x04:
|
|
66
|
+
return String(abc.uints[index] ?? 0);
|
|
67
|
+
case 0x06: {
|
|
68
|
+
const d = abc.doubles[index] ?? NaN;
|
|
69
|
+
return Number.isNaN(d) ? 'NaN' : String(d);
|
|
70
|
+
}
|
|
71
|
+
case 0x01:
|
|
72
|
+
return JSON.stringify(abc.strings[index] ?? '');
|
|
73
|
+
case 0x0b:
|
|
74
|
+
return 'true';
|
|
75
|
+
case 0x0a:
|
|
76
|
+
return 'false';
|
|
77
|
+
case 0x0c:
|
|
78
|
+
return 'null';
|
|
79
|
+
case 0x00:
|
|
80
|
+
return 'undefined';
|
|
81
|
+
default:
|
|
82
|
+
return JSON.stringify(abc.strings[abc.namespaces[index]?.name ?? 0] ?? '');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Decompiles a method body into lines of AS3. */
|
|
86
|
+
export function decompileMethodBody(env, methodIndex, indent) {
|
|
87
|
+
const r = decompileMethodStatements(env, methodIndex);
|
|
88
|
+
if ('error' in r) {
|
|
89
|
+
const unit = env.indentUnit ?? ' ';
|
|
90
|
+
const pcode = disassembleMethod(env.abc, methodIndex).trimEnd().split('\n');
|
|
91
|
+
return {
|
|
92
|
+
error: r.error,
|
|
93
|
+
lines: [`${indent + unit}// Decompilation failed: ${r.error}`, `${indent + unit}// P-code:`, ...pcode.map((l) => `${indent + unit}// ${l}`)],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (!r.stmts.length)
|
|
97
|
+
return { lines: [] };
|
|
98
|
+
return { lines: r.printer.stmts(r.stmts, indent + (env.indentUnit ?? ' ')) };
|
|
99
|
+
}
|
|
100
|
+
/** Decompiles a method body to statements plus a printer bound to its local names. */
|
|
101
|
+
export function decompileMethodStatements(env, methodIndex) {
|
|
102
|
+
const { abc } = env;
|
|
103
|
+
const unit = env.indentUnit ?? ' ';
|
|
104
|
+
const method = abc.methods[methodIndex];
|
|
105
|
+
const body = abc.methodBody(methodIndex);
|
|
106
|
+
if (!method || !body)
|
|
107
|
+
return { stmts: [], printer: new Printer({ localName: (r) => `_loc${r}_`, indentUnit: unit, functionExpr: () => 'function(){}' }) };
|
|
108
|
+
const names = paramNames(abc, method);
|
|
109
|
+
const nParams = method.paramTypes.length;
|
|
110
|
+
const ctx = {
|
|
111
|
+
abc,
|
|
112
|
+
methodIndex,
|
|
113
|
+
regNames: new Map(),
|
|
114
|
+
specialRegs: new Map(),
|
|
115
|
+
activationTraits: body.traits,
|
|
116
|
+
thisTraits: env.thisTraits ?? [],
|
|
117
|
+
globalTraits: env.globalTraits ?? [],
|
|
118
|
+
catchNames: new Map(),
|
|
119
|
+
scope: [],
|
|
120
|
+
typeName: (mn) => typeNameOf(env, mn),
|
|
121
|
+
useName: (mn) => useNameOf(env, mn),
|
|
122
|
+
};
|
|
123
|
+
try {
|
|
124
|
+
const decoded = decodeCode(body.code, body.exceptions);
|
|
125
|
+
const cfg = buildCfg(abc, decoded.items, decoded.exceptions);
|
|
126
|
+
let stmts = new Structurer(ctx, cfg).run();
|
|
127
|
+
stmts = postProcess(stmts, nParams + (method.flags & (MethodFlags.NEED_REST | MethodFlags.NEED_ARGUMENTS) ? 1 : 0), (reg) => localName(reg));
|
|
128
|
+
function localName(reg) {
|
|
129
|
+
if (reg === 0)
|
|
130
|
+
return 'this';
|
|
131
|
+
if (reg <= nParams)
|
|
132
|
+
return names[reg - 1];
|
|
133
|
+
if (reg === nParams + 1 && method.flags & MethodFlags.NEED_REST)
|
|
134
|
+
return 'rest';
|
|
135
|
+
if (reg === nParams + 1 && method.flags & MethodFlags.NEED_ARGUMENTS)
|
|
136
|
+
return 'arguments';
|
|
137
|
+
const dbg = ctx.regNames.get(reg);
|
|
138
|
+
if (dbg && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(dbg))
|
|
139
|
+
return dbg;
|
|
140
|
+
return `_loc${reg}_`;
|
|
141
|
+
}
|
|
142
|
+
const printer = new Printer({
|
|
143
|
+
localName,
|
|
144
|
+
indentUnit: unit,
|
|
145
|
+
functionExpr: (mi, ind) => {
|
|
146
|
+
const inner = decompileMethodBody(env, mi, ind);
|
|
147
|
+
const m = abc.methods[mi];
|
|
148
|
+
const ret = typeNameOf(env, m.returnType);
|
|
149
|
+
return [`function(${formatParams(env, mi)}):${ret}`, `${ind}{`, ...inner.lines, `${ind}}`].join('\n');
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
// Resolve `var` declarations now that names are known.
|
|
153
|
+
removeSelfAssignments(stmts, localName);
|
|
154
|
+
declareLocals(stmts, nParams + (method.flags & (MethodFlags.NEED_REST | MethodFlags.NEED_ARGUMENTS) ? 1 : 0), localName);
|
|
155
|
+
// Variables living in the activation object (captured by closures) are declared up front.
|
|
156
|
+
if (method.flags & MethodFlags.NEED_ACTIVATION && body.traits.length) {
|
|
157
|
+
const params = new Set(names);
|
|
158
|
+
if (method.flags & MethodFlags.NEED_REST)
|
|
159
|
+
params.add('rest');
|
|
160
|
+
const decls = [];
|
|
161
|
+
for (const t of body.traits) {
|
|
162
|
+
const n = abc.multinameName(t.name);
|
|
163
|
+
if (params.has(n) || t.kind !== 0)
|
|
164
|
+
continue;
|
|
165
|
+
decls.push({ k: 'var', name: n, type: t.typeName ? typeNameOf(env, t.typeName) : undefined });
|
|
166
|
+
}
|
|
167
|
+
stmts = [...decls, ...stmts];
|
|
168
|
+
}
|
|
169
|
+
return { stmts, printer };
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
return { error: e.message };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Post-processing passes
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
function postProcess(stmts, paramRegs, localName) {
|
|
179
|
+
void paramRegs;
|
|
180
|
+
void localName;
|
|
181
|
+
stmts = stripTrailingReturn(stmts);
|
|
182
|
+
cleanLoops(stmts);
|
|
183
|
+
walkStmts(stmts, (s, list, i) => {
|
|
184
|
+
if (s.k === 'expr')
|
|
185
|
+
list[i] = { k: 'expr', e: simplifyAssign(s.e) };
|
|
186
|
+
if (s.k === 'for' && s.update)
|
|
187
|
+
s.update = simplifyComma(s.update);
|
|
188
|
+
});
|
|
189
|
+
recoverForIn(stmts);
|
|
190
|
+
whileToFor(stmts);
|
|
191
|
+
mergeForInit(stmts);
|
|
192
|
+
hoistElse(stmts);
|
|
193
|
+
liftLoopConditions(stmts);
|
|
194
|
+
return stmts;
|
|
195
|
+
}
|
|
196
|
+
/** `while(true) { if(!c) break; ... }` → `while(c) { ... }` */
|
|
197
|
+
function liftLoopConditions(stmts) {
|
|
198
|
+
walkStmts(stmts, (s) => {
|
|
199
|
+
if (s.k !== 'while' || !(s.c.k === 'lit' && s.c.v === true))
|
|
200
|
+
return;
|
|
201
|
+
const first = s.body[0];
|
|
202
|
+
if (first && first.k === 'if' && !first.else && first.then.length === 1 && first.then[0].k === 'break' && !first.then[0].label) {
|
|
203
|
+
s.c = not(first.c);
|
|
204
|
+
s.body.shift();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function stripTrailingReturn(stmts) {
|
|
209
|
+
const last = stmts[stmts.length - 1];
|
|
210
|
+
if (last && last.k === 'return' && !last.e)
|
|
211
|
+
return stmts.slice(0, -1);
|
|
212
|
+
return stmts;
|
|
213
|
+
}
|
|
214
|
+
/** Removes redundant trailing `continue` statements in loop bodies. */
|
|
215
|
+
function cleanLoops(stmts) {
|
|
216
|
+
const stripTail = (list) => {
|
|
217
|
+
for (;;) {
|
|
218
|
+
const last = list[list.length - 1];
|
|
219
|
+
if (!last)
|
|
220
|
+
return;
|
|
221
|
+
if (last.k === 'continue' && !last.label) {
|
|
222
|
+
list.pop();
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (last.k === 'if') {
|
|
226
|
+
stripTail(last.then);
|
|
227
|
+
if (last.else)
|
|
228
|
+
stripTail(last.else);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
walkStmts(stmts, (s) => {
|
|
234
|
+
if ((s.k === 'while' || s.k === 'dowhile' || s.k === 'for' || s.k === 'forin') && s.body)
|
|
235
|
+
stripTail(s.body);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function endsAbruptly(list) {
|
|
239
|
+
const last = list[list.length - 1];
|
|
240
|
+
if (!last)
|
|
241
|
+
return false;
|
|
242
|
+
if (last.k === 'return' || last.k === 'throw' || last.k === 'break' || last.k === 'continue')
|
|
243
|
+
return true;
|
|
244
|
+
if (last.k === 'if' && last.else)
|
|
245
|
+
return endsAbruptly(last.then) && endsAbruptly(last.else);
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
/** `if(c){...return;} else {B}` → `if(c){...return;} B` */
|
|
249
|
+
function hoistElse(stmts) {
|
|
250
|
+
const fix = (list) => {
|
|
251
|
+
for (let i = 0; i < list.length; i++) {
|
|
252
|
+
const s = list[i];
|
|
253
|
+
for (const child of childLists(s))
|
|
254
|
+
fix(child);
|
|
255
|
+
if (s.k === 'if' && s.else && s.else.length && endsAbruptly(s.then) && !(s.else.length === 1 && s.else[0].k === 'if')) {
|
|
256
|
+
const rest = s.else;
|
|
257
|
+
delete s.else;
|
|
258
|
+
list.splice(i + 1, 0, ...rest);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
fix(stmts);
|
|
263
|
+
}
|
|
264
|
+
function simplifyComma(e) {
|
|
265
|
+
if (e.k === 'binary' && e.op === ',')
|
|
266
|
+
return { ...e, a: simplifyComma(e.a), b: simplifyComma(e.b) };
|
|
267
|
+
return simplifyAssign(e);
|
|
268
|
+
}
|
|
269
|
+
/** `while(i < n) { ...; i++; }` (no continue) → `for(; i < n; i++) { ... }` */
|
|
270
|
+
function whileToFor(stmts) {
|
|
271
|
+
const hasOwnContinue = (list) => {
|
|
272
|
+
let found = false;
|
|
273
|
+
const visit = (l) => {
|
|
274
|
+
for (const s of l) {
|
|
275
|
+
if (s.k === 'continue' && !s.label)
|
|
276
|
+
found = true;
|
|
277
|
+
if (s.k === 'while' || s.k === 'dowhile' || s.k === 'for' || s.k === 'forin')
|
|
278
|
+
continue;
|
|
279
|
+
for (const c of childLists(s))
|
|
280
|
+
visit(c);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
visit(list);
|
|
284
|
+
return found;
|
|
285
|
+
};
|
|
286
|
+
const fix = (list) => {
|
|
287
|
+
for (let i = 0; i < list.length; i++) {
|
|
288
|
+
const s = list[i];
|
|
289
|
+
for (const child of childLists(s))
|
|
290
|
+
fix(child);
|
|
291
|
+
if (s.k !== 'while' || s.body.length < 2)
|
|
292
|
+
continue;
|
|
293
|
+
const last = s.body[s.body.length - 1];
|
|
294
|
+
if (last.k !== 'expr')
|
|
295
|
+
continue;
|
|
296
|
+
const e = last.e;
|
|
297
|
+
const target = e.k === 'postfix' ? e.e : e.k === 'assign' && e.op ? e.target : undefined;
|
|
298
|
+
if (!target || target.k !== 'local' || !readsLocalAny(s.c, target.reg) || hasOwnContinue(s.body))
|
|
299
|
+
continue;
|
|
300
|
+
list[i] = { k: 'for', c: s.c, update: e, body: s.body.slice(0, -1), label: s.label };
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
fix(stmts);
|
|
304
|
+
}
|
|
305
|
+
/** `i = 0; for(; i < n; i++)` → `for(i = 0; i < n; i++)` */
|
|
306
|
+
function mergeForInit(stmts) {
|
|
307
|
+
const fix = (list) => {
|
|
308
|
+
for (let i = 0; i < list.length; i++) {
|
|
309
|
+
const s = list[i];
|
|
310
|
+
for (const child of childLists(s))
|
|
311
|
+
fix(child);
|
|
312
|
+
if (s.k !== 'for' || s.init || i === 0)
|
|
313
|
+
continue;
|
|
314
|
+
const prev = list[i - 1];
|
|
315
|
+
if (prev.k !== 'expr' || prev.e.k !== 'assign' || prev.e.op || prev.e.target.k !== 'local')
|
|
316
|
+
continue;
|
|
317
|
+
const reg = prev.e.target.reg;
|
|
318
|
+
if (!s.c || !readsLocalAny(s.c, reg))
|
|
319
|
+
continue;
|
|
320
|
+
s.init = prev;
|
|
321
|
+
list.splice(i - 1, 1);
|
|
322
|
+
i--;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
fix(stmts);
|
|
326
|
+
}
|
|
327
|
+
function readsLocalAny(e, reg) {
|
|
328
|
+
return JSON.stringify(e).includes(`"k":"local","reg":${reg}}`);
|
|
329
|
+
}
|
|
330
|
+
function simplifyAssign(e) {
|
|
331
|
+
if (e.k !== 'assign' || e.op)
|
|
332
|
+
return e;
|
|
333
|
+
const v = stripCoerce(e.v);
|
|
334
|
+
if (v.k === 'binary' && exprEquals(v.a, e.target)) {
|
|
335
|
+
if ((v.op === '+' || v.op === '-') && v.b.k === 'lit' && v.b.v === 1)
|
|
336
|
+
return { k: 'postfix', op: v.op === '+' ? '++' : '--', e: e.target };
|
|
337
|
+
if (['+', '-', '*', '/', '%', '<<', '>>', '>>>', '&', '|', '^'].includes(v.op))
|
|
338
|
+
return { k: 'assign', target: e.target, v: v.b, op: v.op };
|
|
339
|
+
}
|
|
340
|
+
return e;
|
|
341
|
+
}
|
|
342
|
+
/** while(hasnext2(o,i)) { x = nextname(o,i); ... } → for(x in obj) { ... } */
|
|
343
|
+
function recoverForIn(stmts) {
|
|
344
|
+
const fix = (list) => {
|
|
345
|
+
for (let i = 0; i < list.length; i++) {
|
|
346
|
+
const s = list[i];
|
|
347
|
+
for (const child of childLists(s))
|
|
348
|
+
fix(child);
|
|
349
|
+
if (s.k !== 'while' || s.c.k !== 'hasnext2')
|
|
350
|
+
continue;
|
|
351
|
+
const { objReg, idxReg } = s.c;
|
|
352
|
+
const first = s.body[0];
|
|
353
|
+
if (!first || first.k !== 'expr' || first.e.k !== 'assign')
|
|
354
|
+
continue;
|
|
355
|
+
const v = stripCoerce(first.e.v);
|
|
356
|
+
if (v.k !== 'next' || v.obj.k !== 'local' || v.obj.reg !== objReg)
|
|
357
|
+
continue;
|
|
358
|
+
// Find the initialisers of the object and index registers.
|
|
359
|
+
let obj;
|
|
360
|
+
for (let j = i - 1; j >= Math.max(0, i - 4); j--) {
|
|
361
|
+
const p = list[j];
|
|
362
|
+
if (p.k !== 'expr' || p.e.k !== 'assign' || p.e.target.k !== 'local')
|
|
363
|
+
continue;
|
|
364
|
+
if (p.e.target.reg === objReg && !obj) {
|
|
365
|
+
obj = stripCoerce(p.e.v);
|
|
366
|
+
list.splice(j, 1);
|
|
367
|
+
i--;
|
|
368
|
+
}
|
|
369
|
+
else if (p.e.target.reg === idxReg) {
|
|
370
|
+
list.splice(j, 1);
|
|
371
|
+
i--;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!obj)
|
|
375
|
+
obj = { k: 'local', reg: objReg };
|
|
376
|
+
list[i] = { k: 'forin', each: v.value, target: first.e.target, obj, body: s.body.slice(1), label: s.label };
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
fix(stmts);
|
|
380
|
+
}
|
|
381
|
+
/** Drops `x = x` produced by activation objects copying parameters into slots. */
|
|
382
|
+
function removeSelfAssignments(stmts, localName) {
|
|
383
|
+
const fix = (list) => {
|
|
384
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
385
|
+
const s = list[i];
|
|
386
|
+
for (const child of childLists(s))
|
|
387
|
+
fix(child);
|
|
388
|
+
if (s.k === 'expr' && s.e.k === 'assign' && !s.e.op && s.e.target.k === 'name') {
|
|
389
|
+
const v = stripCoerce(s.e.v);
|
|
390
|
+
if (v.k === 'local' && localName(v.reg) === s.e.target.name)
|
|
391
|
+
list.splice(i, 1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
fix(stmts);
|
|
396
|
+
}
|
|
397
|
+
/** Turns the first assignment of each local register into a `var` declaration. */
|
|
398
|
+
function declareLocals(stmts, paramRegs, localName) {
|
|
399
|
+
const declared = new Set();
|
|
400
|
+
const declare = (s) => {
|
|
401
|
+
if (s.k === 'expr' && s.e.k === 'assign' && !s.e.op && s.e.target.k === 'local' && s.e.target.reg > paramRegs && !declared.has(s.e.target.reg)) {
|
|
402
|
+
declared.add(s.e.target.reg);
|
|
403
|
+
const v = s.e.v;
|
|
404
|
+
const litType = v.k === 'lit' ? (typeof v.v === 'boolean' ? 'Boolean' : typeof v.v === 'string' ? 'String' : undefined) : undefined;
|
|
405
|
+
return { k: 'var', name: localName(s.e.target.reg), type: v.k === 'coerce' ? v.type : litType, init: v.k === 'coerce' ? v.e : v };
|
|
406
|
+
}
|
|
407
|
+
return s;
|
|
408
|
+
};
|
|
409
|
+
const visit = (list) => {
|
|
410
|
+
for (let i = 0; i < list.length; i++) {
|
|
411
|
+
const s = list[i];
|
|
412
|
+
if (s.k === 'expr') {
|
|
413
|
+
list[i] = declare(s);
|
|
414
|
+
}
|
|
415
|
+
else if (s.k === 'for' && s.init) {
|
|
416
|
+
s.init = declare(s.init);
|
|
417
|
+
}
|
|
418
|
+
else if (s.k === 'forin' && s.target.k === 'local' && s.target.reg > paramRegs && !declared.has(s.target.reg)) {
|
|
419
|
+
declared.add(s.target.reg);
|
|
420
|
+
s.declare = { name: localName(s.target.reg) };
|
|
421
|
+
}
|
|
422
|
+
for (const child of childLists(s))
|
|
423
|
+
visit(child);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
visit(stmts);
|
|
427
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbolic execution of a basic block: turns AVM2 stack code into
|
|
3
|
+
* expressions and statements.
|
|
4
|
+
*/
|
|
5
|
+
import type { AbcFile, Trait } from '../abc/abc-file.js';
|
|
6
|
+
import type { Instruction } from '../abc/code.js';
|
|
7
|
+
import { type Expr, type Stmt } from './ast.js';
|
|
8
|
+
export type Terminator = {
|
|
9
|
+
k: 'fall';
|
|
10
|
+
} | {
|
|
11
|
+
k: 'jump';
|
|
12
|
+
} | {
|
|
13
|
+
k: 'cond';
|
|
14
|
+
c: Expr;
|
|
15
|
+
} | {
|
|
16
|
+
k: 'switch';
|
|
17
|
+
e: Expr;
|
|
18
|
+
} | {
|
|
19
|
+
k: 'return';
|
|
20
|
+
e?: Expr;
|
|
21
|
+
} | {
|
|
22
|
+
k: 'throw';
|
|
23
|
+
e: Expr;
|
|
24
|
+
} | {
|
|
25
|
+
k: 'end';
|
|
26
|
+
};
|
|
27
|
+
export interface MethodContext {
|
|
28
|
+
abc: AbcFile;
|
|
29
|
+
methodIndex: number;
|
|
30
|
+
/** Register names discovered from debug info. */
|
|
31
|
+
regNames: Map<number, string>;
|
|
32
|
+
/** Registers holding activation / catch scope objects. */
|
|
33
|
+
specialRegs: Map<number, Expr>;
|
|
34
|
+
/** Traits of the activation object (method body traits). */
|
|
35
|
+
activationTraits: Trait[];
|
|
36
|
+
/** Traits used to resolve getslot on `this`. */
|
|
37
|
+
thisTraits: Trait[];
|
|
38
|
+
/** Script-level traits for getglobalslot. */
|
|
39
|
+
globalTraits: Trait[];
|
|
40
|
+
/** Catch variable names by exception index. */
|
|
41
|
+
catchNames: Map<number, string>;
|
|
42
|
+
/** Symbolic scope stack, shared across blocks. */
|
|
43
|
+
scope: Expr[];
|
|
44
|
+
/** Type name formatter (records imports). */
|
|
45
|
+
typeName(mn: number): string;
|
|
46
|
+
/** Record a fully qualified name as needing an import. */
|
|
47
|
+
useName(mn: number): void;
|
|
48
|
+
}
|
|
49
|
+
export declare class SimulationError extends Error {
|
|
50
|
+
}
|
|
51
|
+
/** Simulates `insns` with the given entry stack, appending statements to `out`. */
|
|
52
|
+
export declare function simulate(ctx: MethodContext, insns: readonly Instruction[], entryStack: Expr[], out: Stmt[]): {
|
|
53
|
+
stack: Expr[];
|
|
54
|
+
term: Terminator;
|
|
55
|
+
};
|