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,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small ActionScript 3 AST used by the decompiler, plus a precedence-aware printer.
|
|
3
|
+
*/
|
|
4
|
+
export type Expr = {
|
|
5
|
+
k: 'lit';
|
|
6
|
+
v: string | number | boolean | null | undefined;
|
|
7
|
+
} | {
|
|
8
|
+
k: 'raw';
|
|
9
|
+
text: string;
|
|
10
|
+
} | {
|
|
11
|
+
k: 'this';
|
|
12
|
+
} | {
|
|
13
|
+
k: 'super';
|
|
14
|
+
} | {
|
|
15
|
+
k: 'global';
|
|
16
|
+
} | {
|
|
17
|
+
k: 'local';
|
|
18
|
+
reg: number;
|
|
19
|
+
} | {
|
|
20
|
+
k: 'name';
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
/** Scope object returned by findprop; only meaningful as the target of a member access. */
|
|
24
|
+
| {
|
|
25
|
+
k: 'findprop';
|
|
26
|
+
name: string;
|
|
27
|
+
} | {
|
|
28
|
+
k: 'member';
|
|
29
|
+
obj: Expr;
|
|
30
|
+
name: string;
|
|
31
|
+
attr?: boolean;
|
|
32
|
+
ns?: string;
|
|
33
|
+
} | {
|
|
34
|
+
k: 'index';
|
|
35
|
+
obj: Expr;
|
|
36
|
+
index: Expr;
|
|
37
|
+
attr?: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
k: 'descendants';
|
|
40
|
+
obj: Expr;
|
|
41
|
+
name: string;
|
|
42
|
+
} | {
|
|
43
|
+
k: 'call';
|
|
44
|
+
fn: Expr;
|
|
45
|
+
args: Expr[];
|
|
46
|
+
} | {
|
|
47
|
+
k: 'new';
|
|
48
|
+
ctor: Expr;
|
|
49
|
+
args: Expr[];
|
|
50
|
+
} | {
|
|
51
|
+
k: 'unary';
|
|
52
|
+
op: string;
|
|
53
|
+
e: Expr;
|
|
54
|
+
} | {
|
|
55
|
+
k: 'postfix';
|
|
56
|
+
op: '++' | '--';
|
|
57
|
+
e: Expr;
|
|
58
|
+
} | {
|
|
59
|
+
k: 'binary';
|
|
60
|
+
op: string;
|
|
61
|
+
a: Expr;
|
|
62
|
+
b: Expr;
|
|
63
|
+
} | {
|
|
64
|
+
k: 'cond';
|
|
65
|
+
c: Expr;
|
|
66
|
+
t: Expr;
|
|
67
|
+
f: Expr;
|
|
68
|
+
} | {
|
|
69
|
+
k: 'assign';
|
|
70
|
+
target: Expr;
|
|
71
|
+
v: Expr;
|
|
72
|
+
op?: string;
|
|
73
|
+
} | {
|
|
74
|
+
k: 'array';
|
|
75
|
+
items: Expr[];
|
|
76
|
+
} | {
|
|
77
|
+
k: 'object';
|
|
78
|
+
props: Array<[Expr, Expr]>;
|
|
79
|
+
} | {
|
|
80
|
+
k: 'function';
|
|
81
|
+
methodIndex: number;
|
|
82
|
+
} | {
|
|
83
|
+
k: 'applytype';
|
|
84
|
+
base: Expr;
|
|
85
|
+
params: Expr[];
|
|
86
|
+
} | {
|
|
87
|
+
k: 'coerce';
|
|
88
|
+
type: string;
|
|
89
|
+
e: Expr;
|
|
90
|
+
} | {
|
|
91
|
+
k: 'activation';
|
|
92
|
+
} | {
|
|
93
|
+
k: 'catchscope';
|
|
94
|
+
index: number;
|
|
95
|
+
} | {
|
|
96
|
+
k: 'hasnext2';
|
|
97
|
+
objReg: number;
|
|
98
|
+
idxReg: number;
|
|
99
|
+
} | {
|
|
100
|
+
k: 'next';
|
|
101
|
+
value: boolean;
|
|
102
|
+
obj: Expr;
|
|
103
|
+
idx: Expr;
|
|
104
|
+
} | {
|
|
105
|
+
k: 'hasnext';
|
|
106
|
+
obj: Expr;
|
|
107
|
+
idx: Expr;
|
|
108
|
+
} | {
|
|
109
|
+
k: 'slot';
|
|
110
|
+
obj: Expr;
|
|
111
|
+
slot: number;
|
|
112
|
+
name?: string;
|
|
113
|
+
};
|
|
114
|
+
export interface CatchClause {
|
|
115
|
+
name: string;
|
|
116
|
+
type: string;
|
|
117
|
+
body: Stmt[];
|
|
118
|
+
}
|
|
119
|
+
export interface SwitchCase {
|
|
120
|
+
/** null = default. */
|
|
121
|
+
test: Expr | null;
|
|
122
|
+
body: Stmt[];
|
|
123
|
+
}
|
|
124
|
+
export type Stmt = {
|
|
125
|
+
k: 'expr';
|
|
126
|
+
e: Expr;
|
|
127
|
+
} | {
|
|
128
|
+
k: 'var';
|
|
129
|
+
name: string;
|
|
130
|
+
type?: string;
|
|
131
|
+
init?: Expr;
|
|
132
|
+
} | {
|
|
133
|
+
k: 'return';
|
|
134
|
+
e?: Expr;
|
|
135
|
+
} | {
|
|
136
|
+
k: 'throw';
|
|
137
|
+
e: Expr;
|
|
138
|
+
} | {
|
|
139
|
+
k: 'if';
|
|
140
|
+
c: Expr;
|
|
141
|
+
then: Stmt[];
|
|
142
|
+
else?: Stmt[];
|
|
143
|
+
} | {
|
|
144
|
+
k: 'while';
|
|
145
|
+
c: Expr;
|
|
146
|
+
body: Stmt[];
|
|
147
|
+
label?: string;
|
|
148
|
+
} | {
|
|
149
|
+
k: 'dowhile';
|
|
150
|
+
c: Expr;
|
|
151
|
+
body: Stmt[];
|
|
152
|
+
label?: string;
|
|
153
|
+
} | {
|
|
154
|
+
k: 'for';
|
|
155
|
+
init?: Stmt;
|
|
156
|
+
c?: Expr;
|
|
157
|
+
update?: Expr;
|
|
158
|
+
body: Stmt[];
|
|
159
|
+
label?: string;
|
|
160
|
+
} | {
|
|
161
|
+
k: 'forin';
|
|
162
|
+
each: boolean;
|
|
163
|
+
target: Expr;
|
|
164
|
+
declare?: {
|
|
165
|
+
name: string;
|
|
166
|
+
type?: string;
|
|
167
|
+
};
|
|
168
|
+
obj: Expr;
|
|
169
|
+
body: Stmt[];
|
|
170
|
+
label?: string;
|
|
171
|
+
} | {
|
|
172
|
+
k: 'switch';
|
|
173
|
+
disc: Expr;
|
|
174
|
+
cases: SwitchCase[];
|
|
175
|
+
label?: string;
|
|
176
|
+
} | {
|
|
177
|
+
k: 'try';
|
|
178
|
+
body: Stmt[];
|
|
179
|
+
catches: CatchClause[];
|
|
180
|
+
finally?: Stmt[];
|
|
181
|
+
} | {
|
|
182
|
+
k: 'break';
|
|
183
|
+
label?: string;
|
|
184
|
+
} | {
|
|
185
|
+
k: 'continue';
|
|
186
|
+
label?: string;
|
|
187
|
+
} | {
|
|
188
|
+
k: 'with';
|
|
189
|
+
obj: Expr;
|
|
190
|
+
body: Stmt[];
|
|
191
|
+
} | {
|
|
192
|
+
k: 'comment';
|
|
193
|
+
text: string;
|
|
194
|
+
} | {
|
|
195
|
+
k: 'block';
|
|
196
|
+
body: Stmt[];
|
|
197
|
+
};
|
|
198
|
+
export declare const lit: (v: string | number | boolean | null | undefined) => Expr;
|
|
199
|
+
export declare const raw: (text: string) => Expr;
|
|
200
|
+
/** Logical negation with simplification of comparisons and double negation. */
|
|
201
|
+
export declare function not(e: Expr): Expr;
|
|
202
|
+
export declare function stripCoerce(e: Expr): Expr;
|
|
203
|
+
/** True when evaluating `e` can have side effects (so it must not be dropped or duplicated). */
|
|
204
|
+
export declare function hasSideEffects(e: Expr): boolean;
|
|
205
|
+
/** Structural equality ignoring implicit coercions (used to detect `x = x + 1` → `x++` etc.). */
|
|
206
|
+
export declare function exprEquals(a: Expr, b: Expr): boolean;
|
|
207
|
+
export declare function readsLocal(e: Expr, reg: number): boolean;
|
|
208
|
+
export declare function walkExpr(e: Expr, f: (e: Expr) => void): void;
|
|
209
|
+
/** Visits every statement list recursively (pre-order). */
|
|
210
|
+
export declare function walkStmts(list: Stmt[], f: (s: Stmt, list: Stmt[], index: number) => void): void;
|
|
211
|
+
export declare function childLists(s: Stmt): Stmt[][];
|
|
212
|
+
export interface PrintContext {
|
|
213
|
+
localName(reg: number): string;
|
|
214
|
+
/** Renders a closure (newfunction) body. */
|
|
215
|
+
functionExpr(methodIndex: number, indent: string): string;
|
|
216
|
+
indentUnit: string;
|
|
217
|
+
}
|
|
218
|
+
export declare class Printer {
|
|
219
|
+
readonly ctx: PrintContext;
|
|
220
|
+
constructor(ctx: PrintContext);
|
|
221
|
+
expr(e: Expr, minPrec?: number, indent?: string): string;
|
|
222
|
+
private member;
|
|
223
|
+
private exprInner;
|
|
224
|
+
stmts(list: Stmt[], indent: string): string[];
|
|
225
|
+
private block;
|
|
226
|
+
private labelPrefix;
|
|
227
|
+
stmt(s: Stmt, indent: string): string[];
|
|
228
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small ActionScript 3 AST used by the decompiler, plus a precedence-aware printer.
|
|
3
|
+
*/
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Constructors / helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export const lit = (v) => ({ k: 'lit', v });
|
|
8
|
+
export const raw = (text) => ({ k: 'raw', text });
|
|
9
|
+
/** Logical negation with simplification of comparisons and double negation. */
|
|
10
|
+
export function not(e) {
|
|
11
|
+
if (e.k === 'unary' && e.op === '!')
|
|
12
|
+
return e.e;
|
|
13
|
+
if (e.k === 'binary') {
|
|
14
|
+
const inv = { '==': '!=', '!=': '==', '===': '!==', '!==': '===' };
|
|
15
|
+
const o = inv[e.op];
|
|
16
|
+
if (o)
|
|
17
|
+
return { ...e, op: o };
|
|
18
|
+
}
|
|
19
|
+
if (e.k === 'lit' && typeof e.v === 'boolean')
|
|
20
|
+
return lit(!e.v);
|
|
21
|
+
return { k: 'unary', op: '!', e };
|
|
22
|
+
}
|
|
23
|
+
export function stripCoerce(e) {
|
|
24
|
+
while (e.k === 'coerce')
|
|
25
|
+
e = e.e;
|
|
26
|
+
return e;
|
|
27
|
+
}
|
|
28
|
+
/** True when evaluating `e` can have side effects (so it must not be dropped or duplicated). */
|
|
29
|
+
export function hasSideEffects(e) {
|
|
30
|
+
switch (e.k) {
|
|
31
|
+
case 'call':
|
|
32
|
+
case 'new':
|
|
33
|
+
case 'assign':
|
|
34
|
+
case 'postfix':
|
|
35
|
+
case 'function':
|
|
36
|
+
return e.k !== 'function';
|
|
37
|
+
case 'unary':
|
|
38
|
+
return e.op === 'delete' || e.op === '++' || e.op === '--' || hasSideEffects(e.e);
|
|
39
|
+
case 'member':
|
|
40
|
+
return hasSideEffects(e.obj);
|
|
41
|
+
case 'index':
|
|
42
|
+
return hasSideEffects(e.obj) || hasSideEffects(e.index);
|
|
43
|
+
case 'binary':
|
|
44
|
+
return hasSideEffects(e.a) || hasSideEffects(e.b);
|
|
45
|
+
case 'cond':
|
|
46
|
+
return hasSideEffects(e.c) || hasSideEffects(e.t) || hasSideEffects(e.f);
|
|
47
|
+
case 'coerce':
|
|
48
|
+
return hasSideEffects(e.e);
|
|
49
|
+
case 'array':
|
|
50
|
+
return e.items.some(hasSideEffects);
|
|
51
|
+
case 'object':
|
|
52
|
+
return e.props.some(([a, b]) => hasSideEffects(a) || hasSideEffects(b));
|
|
53
|
+
default:
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Structural equality ignoring implicit coercions (used to detect `x = x + 1` → `x++` etc.). */
|
|
58
|
+
export function exprEquals(a, b) {
|
|
59
|
+
const canon = (e) => JSON.stringify(e, (_k, v) => {
|
|
60
|
+
let x = v;
|
|
61
|
+
while (x && typeof x === 'object' && x.k === 'coerce')
|
|
62
|
+
x = x.e;
|
|
63
|
+
return x;
|
|
64
|
+
});
|
|
65
|
+
return canon(a) === canon(b);
|
|
66
|
+
}
|
|
67
|
+
export function readsLocal(e, reg) {
|
|
68
|
+
let found = false;
|
|
69
|
+
walkExpr(e, (x) => {
|
|
70
|
+
if (x.k === 'local' && x.reg === reg)
|
|
71
|
+
found = true;
|
|
72
|
+
});
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
export function walkExpr(e, f) {
|
|
76
|
+
f(e);
|
|
77
|
+
switch (e.k) {
|
|
78
|
+
case 'member':
|
|
79
|
+
case 'descendants':
|
|
80
|
+
walkExpr(e.obj, f);
|
|
81
|
+
break;
|
|
82
|
+
case 'index':
|
|
83
|
+
walkExpr(e.obj, f);
|
|
84
|
+
walkExpr(e.index, f);
|
|
85
|
+
break;
|
|
86
|
+
case 'call':
|
|
87
|
+
walkExpr(e.fn, f);
|
|
88
|
+
e.args.forEach((x) => walkExpr(x, f));
|
|
89
|
+
break;
|
|
90
|
+
case 'new':
|
|
91
|
+
walkExpr(e.ctor, f);
|
|
92
|
+
e.args.forEach((x) => walkExpr(x, f));
|
|
93
|
+
break;
|
|
94
|
+
case 'unary':
|
|
95
|
+
case 'postfix':
|
|
96
|
+
case 'coerce':
|
|
97
|
+
walkExpr(e.e, f);
|
|
98
|
+
break;
|
|
99
|
+
case 'binary':
|
|
100
|
+
walkExpr(e.a, f);
|
|
101
|
+
walkExpr(e.b, f);
|
|
102
|
+
break;
|
|
103
|
+
case 'cond':
|
|
104
|
+
walkExpr(e.c, f);
|
|
105
|
+
walkExpr(e.t, f);
|
|
106
|
+
walkExpr(e.f, f);
|
|
107
|
+
break;
|
|
108
|
+
case 'assign':
|
|
109
|
+
walkExpr(e.target, f);
|
|
110
|
+
walkExpr(e.v, f);
|
|
111
|
+
break;
|
|
112
|
+
case 'array':
|
|
113
|
+
e.items.forEach((x) => walkExpr(x, f));
|
|
114
|
+
break;
|
|
115
|
+
case 'object':
|
|
116
|
+
e.props.forEach(([a, b]) => {
|
|
117
|
+
walkExpr(a, f);
|
|
118
|
+
walkExpr(b, f);
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
case 'applytype':
|
|
122
|
+
walkExpr(e.base, f);
|
|
123
|
+
e.params.forEach((x) => walkExpr(x, f));
|
|
124
|
+
break;
|
|
125
|
+
case 'next':
|
|
126
|
+
case 'hasnext':
|
|
127
|
+
walkExpr(e.obj, f);
|
|
128
|
+
walkExpr(e.idx, f);
|
|
129
|
+
break;
|
|
130
|
+
case 'slot':
|
|
131
|
+
walkExpr(e.obj, f);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Visits every statement list recursively (pre-order). */
|
|
136
|
+
export function walkStmts(list, f) {
|
|
137
|
+
for (let i = 0; i < list.length; i++) {
|
|
138
|
+
const s = list[i];
|
|
139
|
+
f(s, list, i);
|
|
140
|
+
for (const child of childLists(s))
|
|
141
|
+
walkStmts(child, f);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function childLists(s) {
|
|
145
|
+
switch (s.k) {
|
|
146
|
+
case 'if':
|
|
147
|
+
return s.else ? [s.then, s.else] : [s.then];
|
|
148
|
+
case 'while':
|
|
149
|
+
case 'dowhile':
|
|
150
|
+
case 'for':
|
|
151
|
+
case 'forin':
|
|
152
|
+
case 'with':
|
|
153
|
+
case 'block':
|
|
154
|
+
return [s.body];
|
|
155
|
+
case 'switch':
|
|
156
|
+
return s.cases.map((c) => c.body);
|
|
157
|
+
case 'try':
|
|
158
|
+
return [s.body, ...s.catches.map((c) => c.body), ...(s.finally ? [s.finally] : [])];
|
|
159
|
+
default:
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Printer
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
const BINARY_PREC = {
|
|
167
|
+
',': 0,
|
|
168
|
+
'||': 3,
|
|
169
|
+
'&&': 4,
|
|
170
|
+
'|': 5,
|
|
171
|
+
'^': 6,
|
|
172
|
+
'&': 7,
|
|
173
|
+
'==': 8,
|
|
174
|
+
'!=': 8,
|
|
175
|
+
'===': 8,
|
|
176
|
+
'!==': 8,
|
|
177
|
+
'<': 9,
|
|
178
|
+
'>': 9,
|
|
179
|
+
'<=': 9,
|
|
180
|
+
'>=': 9,
|
|
181
|
+
instanceof: 9,
|
|
182
|
+
is: 9,
|
|
183
|
+
as: 9,
|
|
184
|
+
in: 9,
|
|
185
|
+
'<<': 10,
|
|
186
|
+
'>>': 10,
|
|
187
|
+
'>>>': 10,
|
|
188
|
+
'+': 11,
|
|
189
|
+
'-': 11,
|
|
190
|
+
'*': 12,
|
|
191
|
+
'/': 12,
|
|
192
|
+
'%': 12,
|
|
193
|
+
};
|
|
194
|
+
function precOf(e) {
|
|
195
|
+
switch (e.k) {
|
|
196
|
+
case 'assign':
|
|
197
|
+
return 1;
|
|
198
|
+
case 'cond':
|
|
199
|
+
return 2;
|
|
200
|
+
case 'binary':
|
|
201
|
+
return BINARY_PREC[e.op] ?? 9;
|
|
202
|
+
case 'unary':
|
|
203
|
+
return 13;
|
|
204
|
+
case 'postfix':
|
|
205
|
+
return 14;
|
|
206
|
+
case 'new':
|
|
207
|
+
return 15;
|
|
208
|
+
case 'call':
|
|
209
|
+
case 'member':
|
|
210
|
+
case 'index':
|
|
211
|
+
case 'descendants':
|
|
212
|
+
case 'applytype':
|
|
213
|
+
return 16;
|
|
214
|
+
case 'coerce':
|
|
215
|
+
return precOf(e.e);
|
|
216
|
+
case 'function':
|
|
217
|
+
return 0;
|
|
218
|
+
default:
|
|
219
|
+
return 17;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
223
|
+
function formatNumber(v) {
|
|
224
|
+
if (Number.isNaN(v))
|
|
225
|
+
return 'NaN';
|
|
226
|
+
if (v === Infinity)
|
|
227
|
+
return 'Infinity';
|
|
228
|
+
if (v === -Infinity)
|
|
229
|
+
return '-Infinity';
|
|
230
|
+
if (Object.is(v, -0))
|
|
231
|
+
return '-0';
|
|
232
|
+
return String(v);
|
|
233
|
+
}
|
|
234
|
+
export class Printer {
|
|
235
|
+
ctx;
|
|
236
|
+
constructor(ctx) {
|
|
237
|
+
this.ctx = ctx;
|
|
238
|
+
}
|
|
239
|
+
expr(e, minPrec = 0, indent = '') {
|
|
240
|
+
const s = this.exprInner(e, indent);
|
|
241
|
+
return precOf(e) < minPrec ? `(${s})` : s;
|
|
242
|
+
}
|
|
243
|
+
member(obj, indent) {
|
|
244
|
+
if (obj.k === 'new')
|
|
245
|
+
return `(${this.expr(obj, 0, indent)})`;
|
|
246
|
+
return this.expr(obj, 16, indent);
|
|
247
|
+
}
|
|
248
|
+
exprInner(e, indent) {
|
|
249
|
+
switch (e.k) {
|
|
250
|
+
case 'lit':
|
|
251
|
+
if (typeof e.v === 'string')
|
|
252
|
+
return JSON.stringify(e.v);
|
|
253
|
+
if (typeof e.v === 'number')
|
|
254
|
+
return formatNumber(e.v);
|
|
255
|
+
if (e.v === undefined)
|
|
256
|
+
return 'undefined';
|
|
257
|
+
return String(e.v);
|
|
258
|
+
case 'raw':
|
|
259
|
+
return e.text;
|
|
260
|
+
case 'this':
|
|
261
|
+
return 'this';
|
|
262
|
+
case 'super':
|
|
263
|
+
return 'super';
|
|
264
|
+
case 'global':
|
|
265
|
+
return 'global';
|
|
266
|
+
case 'local':
|
|
267
|
+
return this.ctx.localName(e.reg);
|
|
268
|
+
case 'name':
|
|
269
|
+
return e.name;
|
|
270
|
+
case 'findprop':
|
|
271
|
+
return e.name;
|
|
272
|
+
case 'member': {
|
|
273
|
+
if (e.obj.k === 'findprop' || e.obj.k === 'global' || e.obj.k === 'activation' || e.obj.k === 'catchscope')
|
|
274
|
+
return (e.attr ? '@' : '') + e.name;
|
|
275
|
+
const nsPart = e.ns ? `${e.ns}::` : '';
|
|
276
|
+
if (!e.attr && !nsPart && !IDENT_RE.test(e.name))
|
|
277
|
+
return `${this.member(e.obj, indent)}[${JSON.stringify(e.name)}]`;
|
|
278
|
+
return `${this.member(e.obj, indent)}.${e.attr ? '@' : ''}${nsPart}${e.name}`;
|
|
279
|
+
}
|
|
280
|
+
case 'index':
|
|
281
|
+
return `${this.member(e.obj, indent)}${e.attr ? '.@' : ''}[${this.expr(e.index, 0, indent)}]`;
|
|
282
|
+
case 'descendants':
|
|
283
|
+
return `${this.member(e.obj, indent)}..${e.name}`;
|
|
284
|
+
case 'call':
|
|
285
|
+
return `${this.expr(e.fn, 16, indent)}(${e.args.map((a) => this.expr(a, 2, indent)).join(', ')})`;
|
|
286
|
+
case 'new': {
|
|
287
|
+
const ctor = e.ctor.k === 'call' ? `(${this.expr(e.ctor, 0, indent)})` : this.expr(e.ctor, 16, indent);
|
|
288
|
+
return `new ${ctor}(${e.args.map((a) => this.expr(a, 2, indent)).join(', ')})`;
|
|
289
|
+
}
|
|
290
|
+
case 'unary': {
|
|
291
|
+
const word = /^[a-z]/.test(e.op);
|
|
292
|
+
const inner = this.expr(e.e, 13, indent);
|
|
293
|
+
// Avoid "- -x" / "!-x" ambiguities.
|
|
294
|
+
return word ? `${e.op} ${inner}` : e.op === '-' && inner.startsWith('-') ? `-(${inner})` : `${e.op}${inner}`;
|
|
295
|
+
}
|
|
296
|
+
case 'postfix':
|
|
297
|
+
return `${this.expr(e.e, 14, indent)}${e.op}`;
|
|
298
|
+
case 'binary': {
|
|
299
|
+
const p = BINARY_PREC[e.op] ?? 9;
|
|
300
|
+
if (e.op === ',')
|
|
301
|
+
return `${this.expr(e.a, 1, indent)}, ${this.expr(e.b, 1, indent)}`;
|
|
302
|
+
return `${this.expr(e.a, p, indent)} ${e.op} ${this.expr(e.b, p + 1, indent)}`;
|
|
303
|
+
}
|
|
304
|
+
case 'cond':
|
|
305
|
+
return `${this.expr(e.c, 3, indent)} ? ${this.expr(e.t, 2, indent)} : ${this.expr(e.f, 2, indent)}`;
|
|
306
|
+
case 'assign':
|
|
307
|
+
return `${this.expr(e.target, 16, indent)} ${e.op ?? ''}= ${this.expr(e.v, 1, indent)}`;
|
|
308
|
+
case 'array':
|
|
309
|
+
return `[${e.items.map((a) => this.expr(a, 2, indent)).join(', ')}]`;
|
|
310
|
+
case 'object':
|
|
311
|
+
if (e.props.length === 0)
|
|
312
|
+
return '{}';
|
|
313
|
+
return `{${e.props
|
|
314
|
+
.map(([k, v]) => {
|
|
315
|
+
const key = k.k === 'lit' && typeof k.v === 'string' && IDENT_RE.test(k.v) ? k.v : this.expr(k, 17, indent);
|
|
316
|
+
return `${key}: ${this.expr(v, 2, indent)}`;
|
|
317
|
+
})
|
|
318
|
+
.join(', ')}}`;
|
|
319
|
+
case 'function':
|
|
320
|
+
return this.ctx.functionExpr(e.methodIndex, indent);
|
|
321
|
+
case 'applytype':
|
|
322
|
+
return `${this.expr(e.base, 16, indent)}.<${e.params.map((p) => this.expr(p, 0, indent)).join(', ')}>`;
|
|
323
|
+
case 'coerce':
|
|
324
|
+
return this.exprInner(e.e, indent);
|
|
325
|
+
case 'activation':
|
|
326
|
+
return '/*activation*/';
|
|
327
|
+
case 'catchscope':
|
|
328
|
+
return '/*catch*/';
|
|
329
|
+
case 'hasnext2':
|
|
330
|
+
return `hasnext2(${this.ctx.localName(e.objReg)}, ${this.ctx.localName(e.idxReg)})`;
|
|
331
|
+
case 'next':
|
|
332
|
+
return `${e.value ? 'nextvalue' : 'nextname'}(${this.expr(e.obj, 0, indent)}, ${this.expr(e.idx, 0, indent)})`;
|
|
333
|
+
case 'hasnext':
|
|
334
|
+
return `hasnext(${this.expr(e.obj, 0, indent)}, ${this.expr(e.idx, 0, indent)})`;
|
|
335
|
+
case 'slot':
|
|
336
|
+
return e.name ?? `${this.member(e.obj, indent)}./*slot ${e.slot}*/`;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
stmts(list, indent) {
|
|
340
|
+
const out = [];
|
|
341
|
+
for (const s of list)
|
|
342
|
+
out.push(...this.stmt(s, indent));
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
block(body, indent) {
|
|
346
|
+
return [`${indent}{`, ...this.stmts(body, indent + this.ctx.indentUnit), `${indent}}`];
|
|
347
|
+
}
|
|
348
|
+
labelPrefix(label, indent, out) {
|
|
349
|
+
if (label)
|
|
350
|
+
out.push(`${indent}${label}:`);
|
|
351
|
+
}
|
|
352
|
+
stmt(s, indent) {
|
|
353
|
+
const I = indent;
|
|
354
|
+
const out = [];
|
|
355
|
+
switch (s.k) {
|
|
356
|
+
case 'expr':
|
|
357
|
+
out.push(`${I}${this.expr(s.e, 0, I)};`);
|
|
358
|
+
break;
|
|
359
|
+
case 'var':
|
|
360
|
+
out.push(`${I}var ${s.name}${s.type ? ':' + s.type : ''}${s.init ? ' = ' + this.expr(s.init, 1, I) : ''};`);
|
|
361
|
+
break;
|
|
362
|
+
case 'return':
|
|
363
|
+
out.push(s.e ? `${I}return ${this.expr(s.e, 0, I)};` : `${I}return;`);
|
|
364
|
+
break;
|
|
365
|
+
case 'throw':
|
|
366
|
+
out.push(`${I}throw ${this.expr(s.e, 0, I)};`);
|
|
367
|
+
break;
|
|
368
|
+
case 'if': {
|
|
369
|
+
out.push(`${I}if(${this.expr(s.c, 0, I)})`, ...this.block(s.then, I));
|
|
370
|
+
let els = s.else;
|
|
371
|
+
while (els && els.length) {
|
|
372
|
+
const only = els.length === 1 ? els[0] : undefined;
|
|
373
|
+
if (only && only.k === 'if') {
|
|
374
|
+
out.push(`${I}else if(${this.expr(only.c, 0, I)})`, ...this.block(only.then, I));
|
|
375
|
+
els = only.else;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
out.push(`${I}else`, ...this.block(els, I));
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case 'while':
|
|
385
|
+
this.labelPrefix(s.label, I, out);
|
|
386
|
+
out.push(`${I}while(${this.expr(s.c, 0, I)})`, ...this.block(s.body, I));
|
|
387
|
+
break;
|
|
388
|
+
case 'dowhile':
|
|
389
|
+
this.labelPrefix(s.label, I, out);
|
|
390
|
+
out.push(`${I}do`, ...this.block(s.body, I), `${I}while(${this.expr(s.c, 0, I)});`);
|
|
391
|
+
break;
|
|
392
|
+
case 'for': {
|
|
393
|
+
this.labelPrefix(s.label, I, out);
|
|
394
|
+
const init = s.init ? this.stmt(s.init, '')[0].replace(/;$/, '') : '';
|
|
395
|
+
out.push(`${I}for(${init}; ${s.c ? this.expr(s.c, 0, I) : ''}; ${s.update ? this.expr(s.update, 0, I) : ''})`, ...this.block(s.body, I));
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case 'forin': {
|
|
399
|
+
this.labelPrefix(s.label, I, out);
|
|
400
|
+
const target = s.declare ? `var ${s.declare.name}${s.declare.type ? ':' + s.declare.type : ''}` : this.expr(s.target, 0, I);
|
|
401
|
+
out.push(`${I}for${s.each ? ' each' : ''}(${target} in ${this.expr(s.obj, 0, I)})`, ...this.block(s.body, I));
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case 'switch': {
|
|
405
|
+
this.labelPrefix(s.label, I, out);
|
|
406
|
+
out.push(`${I}switch(${this.expr(s.disc, 0, I)})`, `${I}{`);
|
|
407
|
+
const I2 = I + this.ctx.indentUnit;
|
|
408
|
+
for (const c of s.cases) {
|
|
409
|
+
out.push(c.test ? `${I2}case ${this.expr(c.test, 0, I2)}:` : `${I2}default:`);
|
|
410
|
+
out.push(...this.stmts(c.body, I2 + this.ctx.indentUnit));
|
|
411
|
+
}
|
|
412
|
+
out.push(`${I}}`);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case 'try':
|
|
416
|
+
out.push(`${I}try`, ...this.block(s.body, I));
|
|
417
|
+
for (const c of s.catches)
|
|
418
|
+
out.push(`${I}catch(${c.name}:${c.type})`, ...this.block(c.body, I));
|
|
419
|
+
if (s.finally)
|
|
420
|
+
out.push(`${I}finally`, ...this.block(s.finally, I));
|
|
421
|
+
break;
|
|
422
|
+
case 'break':
|
|
423
|
+
out.push(`${I}break${s.label ? ' ' + s.label : ''};`);
|
|
424
|
+
break;
|
|
425
|
+
case 'continue':
|
|
426
|
+
out.push(`${I}continue${s.label ? ' ' + s.label : ''};`);
|
|
427
|
+
break;
|
|
428
|
+
case 'with':
|
|
429
|
+
out.push(`${I}with(${this.expr(s.obj, 0, I)})`, ...this.block(s.body, I));
|
|
430
|
+
break;
|
|
431
|
+
case 'comment':
|
|
432
|
+
for (const line of s.text.split('\n'))
|
|
433
|
+
out.push(`${I}// ${line}`);
|
|
434
|
+
break;
|
|
435
|
+
case 'block':
|
|
436
|
+
out.push(...this.block(s.body, I));
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-flow graph over decoded method code, with dominator and
|
|
3
|
+
* post-dominator trees.
|
|
4
|
+
*/
|
|
5
|
+
import type { AbcFile } from '../abc/abc-file.js';
|
|
6
|
+
import type { CodeException, CodeItem, Instruction } from '../abc/code.js';
|
|
7
|
+
export type BlockKind = 'fall' | 'jump' | 'cond' | 'switch' | 'return' | 'throw' | 'raw' | 'end';
|
|
8
|
+
export interface Block {
|
|
9
|
+
id: number;
|
|
10
|
+
/** Instructions (labels removed). */
|
|
11
|
+
insns: Instruction[];
|
|
12
|
+
labels: number[];
|
|
13
|
+
kind: BlockKind;
|
|
14
|
+
/** cond: [taken, fallthrough]; switch: [default, ...cases]; fall/jump: [next]. */
|
|
15
|
+
succs: number[];
|
|
16
|
+
preds: number[];
|
|
17
|
+
/** Stack height on entry (-1 if unreachable). */
|
|
18
|
+
entryHeight: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Cfg {
|
|
21
|
+
blocks: Block[];
|
|
22
|
+
labelBlock: Map<number, number>;
|
|
23
|
+
exceptions: CodeException[];
|
|
24
|
+
/** Immediate dominator per block (-1 for entry / unreachable). */
|
|
25
|
+
idom: number[];
|
|
26
|
+
/** Immediate post-dominator per block (-1 = virtual exit). */
|
|
27
|
+
ipdom: number[];
|
|
28
|
+
/** Reverse post-order index (lower = earlier). */
|
|
29
|
+
order: number[];
|
|
30
|
+
}
|
|
31
|
+
export declare function buildCfg(abc: AbcFile, items: readonly CodeItem[], exceptions: readonly CodeException[]): Cfg;
|
|
32
|
+
/** True if `a` dominates `b`. */
|
|
33
|
+
export declare function dominates(cfg: Cfg, a: number, b: number): boolean;
|
|
34
|
+
export interface Loop {
|
|
35
|
+
header: number;
|
|
36
|
+
body: Set<number>;
|
|
37
|
+
latches: number[];
|
|
38
|
+
}
|
|
39
|
+
/** Natural loops keyed by header block. */
|
|
40
|
+
export declare function findLoops(cfg: Cfg): Map<number, Loop>;
|