js-confuser-vm 0.1.0 → 0.1.1
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/CHANGELOG.md +23 -0
- package/README.md +75 -94
- package/bench.ts +146 -0
- package/disassemble.ts +12 -0
- package/dist/build-runtime.js +41 -15
- package/dist/compiler.js +134 -60
- package/dist/disassembler.js +317 -0
- package/dist/index.js +7 -2
- package/dist/runtime.js +68 -46
- package/dist/template.js +116 -0
- package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
- package/dist/transforms/bytecode/controlFlowFlattening.js +451 -0
- package/dist/transforms/bytecode/dispatcher.js +13 -109
- package/dist/transforms/bytecode/macroOpcodes.js +2 -2
- package/dist/transforms/bytecode/resolveConstants.js +100 -0
- package/dist/transforms/bytecode/resolveRegisters.js +4 -0
- package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +18 -10
- package/dist/transforms/bytecode/stringConcealing.js +110 -0
- package/dist/transforms/runtime/classObfuscation.js +43 -0
- package/dist/transforms/runtime/handlerTable.js +91 -0
- package/dist/transforms/runtime/semanticOpcodes.js +35 -0
- package/dist/transforms/runtime/specializedOpcodes.js +11 -5
- package/dist/types.js +1 -1
- package/dist/utils/ast-utils.js +14 -0
- package/dist/utils/op-utils.js +0 -2
- package/dist/utils/pass-utils.js +100 -0
- package/dist/utils/profile-utils.js +3 -0
- package/index.ts +22 -17
- package/jest.config.js +14 -2
- package/output.disassembled.js +41 -0
- package/package.json +2 -1
- package/src/build-runtime.ts +113 -78
- package/src/compiler.ts +2703 -2593
- package/src/disassembler.ts +329 -0
- package/src/index.ts +12 -2
- package/src/options.ts +7 -1
- package/src/runtime.ts +84 -51
- package/src/template.ts +125 -1
- package/src/transforms/bytecode/aliasedOpcodes.ts +4 -1
- package/src/transforms/bytecode/controlFlowFlattening.ts +566 -0
- package/src/transforms/bytecode/dispatcher.ts +19 -125
- package/src/transforms/bytecode/macroOpcodes.ts +2 -2
- package/src/transforms/bytecode/resolveRegisters.ts +5 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +22 -11
- package/src/transforms/bytecode/stringConcealing.ts +130 -0
- package/src/transforms/runtime/classObfuscation.ts +59 -0
- package/src/transforms/runtime/specializedOpcodes.ts +14 -9
- package/src/types.ts +42 -1
- package/src/utils/ast-utils.ts +19 -0
- package/src/utils/op-utils.ts +0 -2
- package/src/utils/pass-utils.ts +126 -0
- package/src/utils/profile-utils.ts +3 -0
- package/tsconfig.json +1 -1
- package/src/transforms/bytecode/microOpcodes.ts +0 -291
- package/src/transforms/runtime/internalVariables.ts +0 -270
- package/src/transforms/runtime/microOpcodes.ts +0 -93
- /package/src/transforms/bytecode/{resolveContants.ts → resolveConstants.ts} +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple bytecode disassembler for debugging.
|
|
3
|
+
*
|
|
4
|
+
* Takes the bytecode debug comment block (as generated by the compiler)
|
|
5
|
+
* and produces a flat pseudo-code listing with registers, gotos, and labels.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Regex to match a single instruction line from the debug comment block.
|
|
9
|
+
// Groups: raw array, opcode name, annotation (rest of line after opcode name)
|
|
10
|
+
const INSTR_RE = /^\s*\/\/\s*\[([^\]]+)\],\s+(\w+)\s+(.*?)$/;
|
|
11
|
+
|
|
12
|
+
// Label line: // label_name:
|
|
13
|
+
const LABEL_RE = /^\s*\/\/\s*(\w+):$/;
|
|
14
|
+
function parseBlock(commentBlock) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
for (const raw of commentBlock.split("\n")) {
|
|
17
|
+
const trimmed = raw.trim();
|
|
18
|
+
if (!trimmed || !trimmed.startsWith("//")) continue;
|
|
19
|
+
const instrMatch = trimmed.match(INSTR_RE);
|
|
20
|
+
if (instrMatch) {
|
|
21
|
+
const nums = instrMatch[1].split(",").map(s => parseInt(s.trim(), 10));
|
|
22
|
+
lines.push({
|
|
23
|
+
kind: "instr",
|
|
24
|
+
instr: {
|
|
25
|
+
raw: nums,
|
|
26
|
+
opName: instrMatch[2],
|
|
27
|
+
annotation: instrMatch[3].trim()
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const labelMatch = trimmed.match(LABEL_RE);
|
|
33
|
+
if (labelMatch) {
|
|
34
|
+
lines.push({
|
|
35
|
+
kind: "label",
|
|
36
|
+
label: labelMatch[1]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return lines;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Extract a label name from an annotation string like "[4, while_exit_8]" or "while_top_7"
|
|
44
|
+
// Also handles "PC=fn_1_1" for closures
|
|
45
|
+
function extractLabel(annotation) {
|
|
46
|
+
// Strip trailing source location info like "3:4-7:5"
|
|
47
|
+
const stripped = annotation.replace(/\s+\d+:\d+-\d+:\d+\s*$/, "").trim();
|
|
48
|
+
const bracketMatch = stripped.match(/\[\d+,\s*(\w+)\]/);
|
|
49
|
+
if (bracketMatch) return bracketMatch[1];
|
|
50
|
+
const pcMatch = stripped.match(/\bPC=(\w+)/);
|
|
51
|
+
if (pcMatch) return pcMatch[1];
|
|
52
|
+
const gotoMatch = stripped.match(/\bgoto\s+(\w+)/);
|
|
53
|
+
if (gotoMatch) return gotoMatch[1];
|
|
54
|
+
|
|
55
|
+
// Bare label at end: last whitespace-separated token
|
|
56
|
+
const lastToken = stripped.split(/\s+/).pop();
|
|
57
|
+
if (lastToken && /^[a-zA-Z_]\w*$/.test(lastToken)) return lastToken;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const ARITH_SYMBOLS = {
|
|
61
|
+
ADD: "+",
|
|
62
|
+
SUB: "-",
|
|
63
|
+
MUL: "*",
|
|
64
|
+
DIV: "/",
|
|
65
|
+
MOD: "%",
|
|
66
|
+
BAND: "&",
|
|
67
|
+
BOR: "|",
|
|
68
|
+
BXOR: "^",
|
|
69
|
+
SHL: "<<",
|
|
70
|
+
SHR: ">>",
|
|
71
|
+
USHR: ">>>"
|
|
72
|
+
};
|
|
73
|
+
const CMP_SYMBOLS = {
|
|
74
|
+
LT: "<",
|
|
75
|
+
GT: ">",
|
|
76
|
+
LTE: "<=",
|
|
77
|
+
GTE: ">=",
|
|
78
|
+
EQ: "===",
|
|
79
|
+
NEQ: "!==",
|
|
80
|
+
LOOSE_EQ: "==",
|
|
81
|
+
LOOSE_NEQ: "!=",
|
|
82
|
+
IN: "in",
|
|
83
|
+
INSTANCEOF: "instanceof"
|
|
84
|
+
};
|
|
85
|
+
const UNARY_SYMBOLS = {
|
|
86
|
+
UNARY_NEG: "-",
|
|
87
|
+
UNARY_POS: "+",
|
|
88
|
+
UNARY_NOT: "!",
|
|
89
|
+
UNARY_BITNOT: "~"
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Extract value from annotation like 'reg[1] = "Hello"' or 'reg[1] = 42'
|
|
93
|
+
function extractConstValue(annotation) {
|
|
94
|
+
const m = annotation.match(/=\s*(.+?)(?:\s+\d+:\d+-\d+:\d+)?$/);
|
|
95
|
+
return m ? m[1].trim() : null;
|
|
96
|
+
}
|
|
97
|
+
function disassembleInstr(instr) {
|
|
98
|
+
const {
|
|
99
|
+
raw,
|
|
100
|
+
opName,
|
|
101
|
+
annotation
|
|
102
|
+
} = instr;
|
|
103
|
+
const r = i => `r${raw[i]}`;
|
|
104
|
+
switch (opName) {
|
|
105
|
+
case "LOAD_CONST":
|
|
106
|
+
{
|
|
107
|
+
const val = extractConstValue(annotation);
|
|
108
|
+
return `${r(1)} = ${val ?? `const[${raw[2]}]`}`;
|
|
109
|
+
}
|
|
110
|
+
case "LOAD_INT":
|
|
111
|
+
return `${r(1)} = ${raw[2]}`;
|
|
112
|
+
case "LOAD_GLOBAL":
|
|
113
|
+
{
|
|
114
|
+
const val = extractConstValue(annotation);
|
|
115
|
+
return `${r(1)} = ${val ?? `global[${raw[2]}]`}`;
|
|
116
|
+
}
|
|
117
|
+
case "LOAD_UPVALUE":
|
|
118
|
+
return `${r(1)} = upvalue[${raw[2]}]`;
|
|
119
|
+
case "LOAD_THIS":
|
|
120
|
+
return `${r(1)} = this`;
|
|
121
|
+
case "MOVE":
|
|
122
|
+
return `${r(1)} = ${r(2)}`;
|
|
123
|
+
case "STORE_GLOBAL":
|
|
124
|
+
{
|
|
125
|
+
// annotation: globals[name] = reg[src]
|
|
126
|
+
const val = extractConstValue(annotation);
|
|
127
|
+
return `global[${val ?? raw[1]}] = ${r(2)}`;
|
|
128
|
+
}
|
|
129
|
+
case "STORE_UPVALUE":
|
|
130
|
+
return `upvalue[${raw[1]}] = ${r(2)}`;
|
|
131
|
+
case "GET_PROP":
|
|
132
|
+
return `${r(1)} = ${r(2)}[${r(3)}]`;
|
|
133
|
+
case "SET_PROP":
|
|
134
|
+
return `${r(1)}[${r(2)}] = ${r(3)}`;
|
|
135
|
+
case "DELETE_PROP":
|
|
136
|
+
return `${r(1)} = delete ${r(2)}[${r(3)}]`;
|
|
137
|
+
|
|
138
|
+
// Arithmetic / bitwise
|
|
139
|
+
case "ADD":
|
|
140
|
+
case "SUB":
|
|
141
|
+
case "MUL":
|
|
142
|
+
case "DIV":
|
|
143
|
+
case "MOD":
|
|
144
|
+
case "BAND":
|
|
145
|
+
case "BOR":
|
|
146
|
+
case "BXOR":
|
|
147
|
+
case "SHL":
|
|
148
|
+
case "SHR":
|
|
149
|
+
case "USHR":
|
|
150
|
+
return `${r(1)} = ${r(2)} ${ARITH_SYMBOLS[opName]} ${r(3)}`;
|
|
151
|
+
|
|
152
|
+
// Comparison
|
|
153
|
+
case "LT":
|
|
154
|
+
case "GT":
|
|
155
|
+
case "LTE":
|
|
156
|
+
case "GTE":
|
|
157
|
+
case "EQ":
|
|
158
|
+
case "NEQ":
|
|
159
|
+
case "LOOSE_EQ":
|
|
160
|
+
case "LOOSE_NEQ":
|
|
161
|
+
case "IN":
|
|
162
|
+
case "INSTANCEOF":
|
|
163
|
+
return `${r(1)} = ${r(2)} ${CMP_SYMBOLS[opName]} ${r(3)}`;
|
|
164
|
+
|
|
165
|
+
// Unary
|
|
166
|
+
case "UNARY_NEG":
|
|
167
|
+
case "UNARY_POS":
|
|
168
|
+
case "UNARY_NOT":
|
|
169
|
+
case "UNARY_BITNOT":
|
|
170
|
+
return `${r(1)} = ${UNARY_SYMBOLS[opName]}${r(2)}`;
|
|
171
|
+
case "TYPEOF":
|
|
172
|
+
return `${r(1)} = typeof ${r(2)}`;
|
|
173
|
+
case "VOID":
|
|
174
|
+
return `${r(1)} = void ${r(2)}`;
|
|
175
|
+
case "TYPEOF_SAFE":
|
|
176
|
+
{
|
|
177
|
+
const val = extractConstValue(annotation);
|
|
178
|
+
return `${r(1)} = typeof ${val ?? `safe[${raw[2]}]`}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Control flow
|
|
182
|
+
case "JUMP":
|
|
183
|
+
{
|
|
184
|
+
const label = extractLabel(annotation);
|
|
185
|
+
return `goto: ${label ?? `pc:${raw[1]}`}`;
|
|
186
|
+
}
|
|
187
|
+
case "JUMP_IF_FALSE":
|
|
188
|
+
{
|
|
189
|
+
const label = extractLabel(annotation);
|
|
190
|
+
return `if (!${r(1)}) goto: ${label ?? `pc:${raw[2]}`}`;
|
|
191
|
+
}
|
|
192
|
+
case "JUMP_IF_TRUE":
|
|
193
|
+
{
|
|
194
|
+
const label = extractLabel(annotation);
|
|
195
|
+
return `if (${r(1)}) goto: ${label ?? `pc:${raw[2]}`}`;
|
|
196
|
+
}
|
|
197
|
+
case "JUMP_REG":
|
|
198
|
+
return `goto: *${r(1)}`;
|
|
199
|
+
|
|
200
|
+
// Calls
|
|
201
|
+
case "CALL":
|
|
202
|
+
{
|
|
203
|
+
const dst = r(1);
|
|
204
|
+
const callee = r(2);
|
|
205
|
+
const argc = raw[3];
|
|
206
|
+
const args = raw.slice(4, 4 + argc).map((_, i) => `r${raw[4 + i]}`);
|
|
207
|
+
return `${dst} = ${callee}(${args.join(", ")})`;
|
|
208
|
+
}
|
|
209
|
+
case "CALL_METHOD":
|
|
210
|
+
{
|
|
211
|
+
const dst = r(1);
|
|
212
|
+
const recv = r(2);
|
|
213
|
+
const callee = r(3);
|
|
214
|
+
const argc = raw[4];
|
|
215
|
+
const args = raw.slice(5, 5 + argc).map((_, i) => `r${raw[5 + i]}`);
|
|
216
|
+
return `${dst} = ${callee}.call(${recv}, ${args.join(", ")})`;
|
|
217
|
+
}
|
|
218
|
+
case "NEW":
|
|
219
|
+
{
|
|
220
|
+
const dst = r(1);
|
|
221
|
+
const callee = r(2);
|
|
222
|
+
const argc = raw[3];
|
|
223
|
+
const args = raw.slice(4, 4 + argc).map((_, i) => `r${raw[4 + i]}`);
|
|
224
|
+
return `${dst} = new ${callee}(${args.join(", ")})`;
|
|
225
|
+
}
|
|
226
|
+
case "RETURN":
|
|
227
|
+
return `return ${r(1)}`;
|
|
228
|
+
case "THROW":
|
|
229
|
+
return `throw ${r(1)}`;
|
|
230
|
+
|
|
231
|
+
// Closures
|
|
232
|
+
case "MAKE_CLOSURE":
|
|
233
|
+
{
|
|
234
|
+
const dst = r(1);
|
|
235
|
+
const startPc = raw[2];
|
|
236
|
+
const paramCount = raw[3];
|
|
237
|
+
const regCount = raw[4];
|
|
238
|
+
const uvCount = raw[5];
|
|
239
|
+
const label = extractLabel(annotation);
|
|
240
|
+
const uvParts = [];
|
|
241
|
+
for (let i = 0; i < uvCount; i++) {
|
|
242
|
+
const isLocal = raw[6 + i * 2];
|
|
243
|
+
const idx = raw[6 + i * 2 + 1];
|
|
244
|
+
uvParts.push(isLocal ? `local[${idx}]` : `uv[${idx}]`);
|
|
245
|
+
}
|
|
246
|
+
const uvStr = uvCount > 0 ? `, upvalues=[${uvParts.join(", ")}]` : "";
|
|
247
|
+
return `${dst} = MakeClosure(${label ?? `pc:${startPc}`}, params=${paramCount}, regs=${regCount}${uvStr})`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Collections
|
|
251
|
+
case "BUILD_ARRAY":
|
|
252
|
+
{
|
|
253
|
+
const dst = r(1);
|
|
254
|
+
const count = raw[2];
|
|
255
|
+
const elems = raw.slice(3, 3 + count).map((_, i) => `r${raw[3 + i]}`);
|
|
256
|
+
return `${dst} = [${elems.join(", ")}]`;
|
|
257
|
+
}
|
|
258
|
+
case "BUILD_OBJECT":
|
|
259
|
+
{
|
|
260
|
+
const dst = r(1);
|
|
261
|
+
const pairCount = raw[2];
|
|
262
|
+
const pairs = [];
|
|
263
|
+
for (let i = 0; i < pairCount; i++) {
|
|
264
|
+
pairs.push(`[r${raw[3 + i * 2]}]: r${raw[4 + i * 2]}`);
|
|
265
|
+
}
|
|
266
|
+
return `${dst} = {${pairs.join(", ")}}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Property definitions
|
|
270
|
+
case "DEFINE_GETTER":
|
|
271
|
+
return `Object.defineGetter(${r(1)}, ${r(2)}, ${r(3)})`;
|
|
272
|
+
case "DEFINE_SETTER":
|
|
273
|
+
return `Object.defineSetter(${r(1)}, ${r(2)}, ${r(3)})`;
|
|
274
|
+
|
|
275
|
+
// For-in
|
|
276
|
+
case "FOR_IN_SETUP":
|
|
277
|
+
return `${r(1)} = ForInSetup(${r(2)})`;
|
|
278
|
+
case "FOR_IN_NEXT":
|
|
279
|
+
{
|
|
280
|
+
const label = extractLabel(annotation);
|
|
281
|
+
return `${r(1)} = ForInNext(${r(2)}) else goto ${label ?? `pc:${raw[3]}`}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Exception handling
|
|
285
|
+
case "TRY_SETUP":
|
|
286
|
+
{
|
|
287
|
+
return `try { catch -> pc:${raw[1]}, exReg=${r(2)}`;
|
|
288
|
+
}
|
|
289
|
+
case "TRY_END":
|
|
290
|
+
return `} // end try`;
|
|
291
|
+
|
|
292
|
+
// Self-modifying
|
|
293
|
+
case "PATCH":
|
|
294
|
+
return `patch(dest=pc:${raw[1]}, src=pc:${raw[2]}..${raw[3]})`;
|
|
295
|
+
case "DEBUGGER":
|
|
296
|
+
return `debugger`;
|
|
297
|
+
default:
|
|
298
|
+
return `??? ${opName} [${raw.join(", ")}]`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
export function disassembleCommentBlock(commentBlock) {
|
|
302
|
+
const lines = parseBlock(commentBlock);
|
|
303
|
+
let bodies = [];
|
|
304
|
+
let currentBody = [];
|
|
305
|
+
for (const line of lines) {
|
|
306
|
+
if (line.kind === "label") {
|
|
307
|
+
let newBody = [];
|
|
308
|
+
newBody.push(`// ${line.label}:`);
|
|
309
|
+
bodies.push(newBody);
|
|
310
|
+
currentBody = newBody;
|
|
311
|
+
} else if (line.instr) {
|
|
312
|
+
currentBody.push(` ${disassembleInstr(line.instr)}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const out = bodies.flatMap(body => body);
|
|
316
|
+
return out.join("\n");
|
|
317
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { compileAndSerialize } from "./compiler.js";
|
|
2
2
|
import { DEFAULT_OPTIONS } from "./options.js";
|
|
3
|
+
import { disassembleCommentBlock } from "./disassembler.js";
|
|
3
4
|
async function obfuscate(source, options = DEFAULT_OPTIONS) {
|
|
4
|
-
const result = compileAndSerialize(source, options);
|
|
5
|
+
const result = await compileAndSerialize(source, options);
|
|
5
6
|
return result;
|
|
6
7
|
}
|
|
8
|
+
async function disassemble(bytecodeComments) {
|
|
9
|
+
return disassembleCommentBlock(bytecodeComments);
|
|
10
|
+
}
|
|
7
11
|
export const JsConfuserVM = {
|
|
8
|
-
obfuscate
|
|
12
|
+
obfuscate,
|
|
13
|
+
disassemble
|
|
9
14
|
};
|
|
10
15
|
export default JsConfuserVM;
|
package/dist/runtime.js
CHANGED
|
@@ -8,14 +8,17 @@ const TIMING_CHECKS = false;
|
|
|
8
8
|
// The text above is not included in the compiled output - for type intellisense only
|
|
9
9
|
// @START
|
|
10
10
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
var b = typeof Buffer !== "undefined" ? Buffer.from(s, "base64") : Uint8Array.from(atob(s), function (c) {
|
|
11
|
+
function base64ToBytes(s) {
|
|
12
|
+
return typeof Buffer !== "undefined" ? Buffer.from(s, "base64") : Uint8Array.from(atob(s), function (c) {
|
|
14
13
|
return c.charCodeAt(0);
|
|
15
14
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
}
|
|
16
|
+
function decodeBytecode(s) {
|
|
17
|
+
if (!ENCODE_BYTECODE) return s;
|
|
18
|
+
var b = base64ToBytes(s);
|
|
19
|
+
// Each slot is a u32 stored as 4 little-endian bytes.
|
|
20
|
+
var r = new Uint32Array(b.length / 4);
|
|
21
|
+
for (var i = 0; i < r.length; i++) r[i] = (b[i * 4] | b[i * 4 + 1] << 8 | b[i * 4 + 2] << 16 | b[i * 4 + 3] << 24) >>> 0;
|
|
19
22
|
return r;
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -88,7 +91,6 @@ function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
|
|
|
88
91
|
startPc: mainStartPc
|
|
89
92
|
};
|
|
90
93
|
this._currentFrame = new Frame(new Closure(mainFn), null, null, undefined, 0, 0);
|
|
91
|
-
this._internals = {};
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// Consume the next slot from the flat bytecode stream and advance the PC.
|
|
@@ -124,9 +126,7 @@ VM.prototype._constant = function (idxIn, keyIn) {
|
|
|
124
126
|
if (!key) return v;
|
|
125
127
|
if (typeof v === "number") return v ^ key;
|
|
126
128
|
// String: base64-decode to u16 LE byte pairs, then XOR each code with (key+i).
|
|
127
|
-
var b =
|
|
128
|
-
return c.charCodeAt(0);
|
|
129
|
-
});
|
|
129
|
+
var b = base64ToBytes(v);
|
|
130
130
|
var out = "";
|
|
131
131
|
for (var i = 0; i < b.length / 2; i++) {
|
|
132
132
|
var code = b[i * 2] | b[i * 2 + 1] << 8; // u16 LE
|
|
@@ -187,6 +187,7 @@ VM.prototype.run = function () {
|
|
|
187
187
|
try {
|
|
188
188
|
var regs = this._regs;
|
|
189
189
|
var base = frame._base;
|
|
190
|
+
|
|
190
191
|
/* @SWITCH */
|
|
191
192
|
switch (op) {
|
|
192
193
|
case OP.LOAD_CONST:
|
|
@@ -231,8 +232,7 @@ VM.prototype.run = function () {
|
|
|
231
232
|
}
|
|
232
233
|
case OP.STORE_GLOBAL:
|
|
233
234
|
{
|
|
234
|
-
//
|
|
235
|
-
// transform can rewrite this._constant() consistently.
|
|
235
|
+
// globals[globalName] = regs[src]
|
|
236
236
|
this.globals[this._constant()] = regs[base + this._operand()];
|
|
237
237
|
break;
|
|
238
238
|
}
|
|
@@ -257,13 +257,14 @@ VM.prototype.run = function () {
|
|
|
257
257
|
var obj = regs[base + this._operand()];
|
|
258
258
|
var key = regs[base + this._operand()];
|
|
259
259
|
var val = regs[base + this._operand()];
|
|
260
|
-
// Reflect.set performs [[Set]] without throwing on failure
|
|
261
|
-
// correctly simulating sloppy-mode assignment from a strict-mode host.
|
|
260
|
+
// Reflect.set performs [[Set]] without throwing on failure (non-strict mode behavior)
|
|
262
261
|
Reflect.set(obj, key, val);
|
|
263
262
|
break;
|
|
264
263
|
}
|
|
265
264
|
case OP.DELETE_PROP:
|
|
266
265
|
{
|
|
266
|
+
// regs[dst] = delete regs[obj][regs[key]]
|
|
267
|
+
// The delete operator returns true if successful which is most cases
|
|
267
268
|
var dst = this._operand();
|
|
268
269
|
var obj = regs[base + this._operand()];
|
|
269
270
|
var key = regs[base + this._operand()];
|
|
@@ -271,7 +272,7 @@ VM.prototype.run = function () {
|
|
|
271
272
|
break;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
//
|
|
275
|
+
// Arithmetic (dst, src1, src2)
|
|
275
276
|
case OP.ADD:
|
|
276
277
|
{
|
|
277
278
|
var dst = this._operand();
|
|
@@ -350,7 +351,7 @@ VM.prototype.run = function () {
|
|
|
350
351
|
break;
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
//
|
|
354
|
+
// Comparison (dst, src1, src2)
|
|
354
355
|
case OP.LT:
|
|
355
356
|
{
|
|
356
357
|
var dst = this._operand();
|
|
@@ -416,13 +417,14 @@ VM.prototype.run = function () {
|
|
|
416
417
|
}
|
|
417
418
|
case OP.INSTANCEOF:
|
|
418
419
|
{
|
|
420
|
+
// regs[dst] = regs[obj] instanceof regs[ctor]
|
|
419
421
|
var dst = this._operand();
|
|
420
422
|
var obj = regs[base + this._operand()];
|
|
421
423
|
var ctor = regs[base + this._operand()];
|
|
422
424
|
if (typeof ctor === "function") {
|
|
423
425
|
regs[base + dst] = obj instanceof ctor;
|
|
424
426
|
} else {
|
|
425
|
-
//
|
|
427
|
+
// TODO: Why is this needed?
|
|
426
428
|
var proto = ctor.prototype;
|
|
427
429
|
var target = Object.getPrototypeOf(obj);
|
|
428
430
|
var result = false;
|
|
@@ -438,7 +440,7 @@ VM.prototype.run = function () {
|
|
|
438
440
|
break;
|
|
439
441
|
}
|
|
440
442
|
|
|
441
|
-
//
|
|
443
|
+
// Unary (dst, src)
|
|
442
444
|
case OP.UNARY_NEG:
|
|
443
445
|
{
|
|
444
446
|
var dst = this._operand();
|
|
@@ -472,13 +474,14 @@ VM.prototype.run = function () {
|
|
|
472
474
|
case OP.VOID:
|
|
473
475
|
{
|
|
474
476
|
var dst = this._operand();
|
|
475
|
-
this._operand(); //
|
|
477
|
+
this._operand(); // consumes argument (intended)
|
|
476
478
|
regs[base + dst] = undefined;
|
|
477
479
|
break;
|
|
478
480
|
}
|
|
479
481
|
case OP.TYPEOF_SAFE:
|
|
480
482
|
{
|
|
481
|
-
// dst
|
|
483
|
+
// regs[dst] = typeof window[name]
|
|
484
|
+
// Never throws ReferenceError, instead returns undefined for undeclared variables
|
|
482
485
|
var dst = this._operand();
|
|
483
486
|
var name = this._constant();
|
|
484
487
|
var val = Object.prototype.hasOwnProperty.call(this.globals, name) ? this.globals[name] : undefined;
|
|
@@ -486,7 +489,7 @@ VM.prototype.run = function () {
|
|
|
486
489
|
break;
|
|
487
490
|
}
|
|
488
491
|
|
|
489
|
-
//
|
|
492
|
+
// Control flow
|
|
490
493
|
case OP.JUMP:
|
|
491
494
|
frame._pc = this._operand();
|
|
492
495
|
break;
|
|
@@ -506,7 +509,7 @@ VM.prototype.run = function () {
|
|
|
506
509
|
break;
|
|
507
510
|
}
|
|
508
511
|
|
|
509
|
-
//
|
|
512
|
+
// Calls
|
|
510
513
|
case OP.CALL:
|
|
511
514
|
{
|
|
512
515
|
// dst, calleeReg, argc, [argReg...]
|
|
@@ -521,8 +524,12 @@ VM.prototype.run = function () {
|
|
|
521
524
|
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
522
525
|
this._regsTop = newBase + closure.fn.regCount;
|
|
523
526
|
var f = new Frame(closure, frame._pc, frame, this.globals, dst, newBase);
|
|
524
|
-
|
|
525
|
-
|
|
527
|
+
if (closure.fn.hasRest) {
|
|
528
|
+
var restSlot = closure.fn.paramCount - 1;
|
|
529
|
+
for (var i = 0; i < restSlot; i++) this._regs[newBase + i] = i < args.length ? args[i] : undefined;
|
|
530
|
+
this._regs[newBase + restSlot] = args.slice(restSlot);
|
|
531
|
+
} else {
|
|
532
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) this._regs[newBase + i] = args[i];
|
|
526
533
|
}
|
|
527
534
|
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
528
535
|
this._regs[newBase + closure.fn.paramCount] = args;
|
|
@@ -549,8 +556,12 @@ VM.prototype.run = function () {
|
|
|
549
556
|
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
550
557
|
this._regsTop = newBase + closure.fn.regCount;
|
|
551
558
|
var f = new Frame(closure, frame._pc, frame, receiver, dst, newBase);
|
|
552
|
-
|
|
553
|
-
|
|
559
|
+
if (closure.fn.hasRest) {
|
|
560
|
+
var restSlot = closure.fn.paramCount - 1;
|
|
561
|
+
for (var i = 0; i < restSlot; i++) this._regs[newBase + i] = i < args.length ? args[i] : undefined;
|
|
562
|
+
this._regs[newBase + restSlot] = args.slice(restSlot);
|
|
563
|
+
} else {
|
|
564
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) this._regs[newBase + i] = args[i];
|
|
554
565
|
}
|
|
555
566
|
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
556
567
|
this._regs[newBase + closure.fn.paramCount] = args;
|
|
@@ -577,8 +588,12 @@ VM.prototype.run = function () {
|
|
|
577
588
|
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
578
589
|
this._regsTop = newBase + closure.fn.regCount;
|
|
579
590
|
var f = new Frame(closure, frame._pc, frame, newObj, dst, newBase);
|
|
580
|
-
|
|
581
|
-
|
|
591
|
+
if (closure.fn.hasRest) {
|
|
592
|
+
var restSlot = closure.fn.paramCount - 1;
|
|
593
|
+
for (var i = 0; i < restSlot; i++) this._regs[newBase + i] = i < args.length ? args[i] : undefined;
|
|
594
|
+
this._regs[newBase + restSlot] = args.slice(restSlot);
|
|
595
|
+
} else {
|
|
596
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) this._regs[newBase + i] = args[i];
|
|
582
597
|
}
|
|
583
598
|
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
584
599
|
this._regs[newBase + closure.fn.paramCount] = args;
|
|
@@ -597,10 +612,14 @@ VM.prototype.run = function () {
|
|
|
597
612
|
{
|
|
598
613
|
var retVal = regs[base + this._operand()];
|
|
599
614
|
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
615
|
+
|
|
616
|
+
// Zero out callee's register window to limit exposing runtime values
|
|
617
|
+
var hi = frame._base + frame.closure.fn.regCount;
|
|
618
|
+
for (var i = frame._base; i < hi; i++) this._regs[i] = undefined;
|
|
600
619
|
this._regsTop = frame._base;
|
|
601
620
|
if (this._frameStack.length === 0) return retVal; // main script returning
|
|
602
621
|
|
|
603
|
-
//
|
|
622
|
+
// NewExpression: When invoking from the 'new' keyword, the newly constructed object is returned instead (if the original function doesn't return an object)
|
|
604
623
|
if (frame._newObj !== null) {
|
|
605
624
|
if (typeof retVal !== "object" || retVal === null) retVal = frame._newObj;
|
|
606
625
|
}
|
|
@@ -612,15 +631,17 @@ VM.prototype.run = function () {
|
|
|
612
631
|
case OP.THROW:
|
|
613
632
|
throw regs[base + this._operand()];
|
|
614
633
|
|
|
615
|
-
//
|
|
634
|
+
// Closures
|
|
616
635
|
case OP.MAKE_CLOSURE:
|
|
617
636
|
{
|
|
618
|
-
// dst, startPc, paramCount, regCount, uvCount, [isLocal, idx, ...]
|
|
637
|
+
// dst, startPc, paramCount, regCount, uvCount, hasRest, [isLocal, idx, ...]
|
|
619
638
|
var dst = this._operand();
|
|
620
639
|
var startPc = this._operand();
|
|
621
640
|
var paramCount = this._operand();
|
|
622
641
|
var regCount = this._operand();
|
|
623
642
|
var uvCount = this._operand();
|
|
643
|
+
var hasRest = this._operand(); // 1 if last param is a rest element
|
|
644
|
+
|
|
624
645
|
var uvDescs = new Array(uvCount);
|
|
625
646
|
for (var i = 0; i < uvCount; i++) {
|
|
626
647
|
var isLocalRaw = this._operand();
|
|
@@ -634,7 +655,8 @@ VM.prototype.run = function () {
|
|
|
634
655
|
paramCount: paramCount,
|
|
635
656
|
regCount: regCount,
|
|
636
657
|
startPc: startPc,
|
|
637
|
-
upvalueDescriptors: uvDescs
|
|
658
|
+
upvalueDescriptors: uvDescs,
|
|
659
|
+
hasRest: hasRest
|
|
638
660
|
};
|
|
639
661
|
var closure = new Closure(fn);
|
|
640
662
|
for (var i = 0; i < uvDescs.length; i++) {
|
|
@@ -656,8 +678,12 @@ VM.prototype.run = function () {
|
|
|
656
678
|
var sub = new VM(self.bytecode, 0, c.fn.regCount, self.constants, self.globals);
|
|
657
679
|
var f = new Frame(c, null, null, this == null ? self.globals : this, 0, 0);
|
|
658
680
|
sub._currentFrame = f;
|
|
659
|
-
|
|
660
|
-
|
|
681
|
+
if (c.fn.hasRest) {
|
|
682
|
+
var restSlot = c.fn.paramCount - 1;
|
|
683
|
+
for (var i = 0; i < restSlot; i++) sub._regs[i] = i < args.length ? args[i] : undefined;
|
|
684
|
+
sub._regs[restSlot] = args.slice(restSlot);
|
|
685
|
+
} else {
|
|
686
|
+
for (var i = 0; i < args.length && i < c.fn.regCount; i++) sub._regs[i] = args[i];
|
|
661
687
|
}
|
|
662
688
|
if (c.fn.paramCount < c.fn.regCount) {
|
|
663
689
|
sub._regs[c.fn.paramCount] = args;
|
|
@@ -671,7 +697,7 @@ VM.prototype.run = function () {
|
|
|
671
697
|
break;
|
|
672
698
|
}
|
|
673
699
|
|
|
674
|
-
//
|
|
700
|
+
// Collections
|
|
675
701
|
case OP.BUILD_ARRAY:
|
|
676
702
|
{
|
|
677
703
|
// dst, count, [elemReg...]
|
|
@@ -697,7 +723,7 @@ VM.prototype.run = function () {
|
|
|
697
723
|
break;
|
|
698
724
|
}
|
|
699
725
|
|
|
700
|
-
//
|
|
726
|
+
// Object methods (getters / setters)
|
|
701
727
|
case OP.DEFINE_GETTER:
|
|
702
728
|
{
|
|
703
729
|
// obj, key, fn
|
|
@@ -799,7 +825,7 @@ VM.prototype.run = function () {
|
|
|
799
825
|
break;
|
|
800
826
|
}
|
|
801
827
|
|
|
802
|
-
//
|
|
828
|
+
// Self-modifying bytecode
|
|
803
829
|
case OP.PATCH:
|
|
804
830
|
{
|
|
805
831
|
// destPc, sliceStart, sliceEnd
|
|
@@ -813,10 +839,7 @@ VM.prototype.run = function () {
|
|
|
813
839
|
}
|
|
814
840
|
case OP.JUMP_REG:
|
|
815
841
|
{
|
|
816
|
-
// Indirect jump:
|
|
817
|
-
// bytecode immediate. Used by the jumpDispatcher pass so that static
|
|
818
|
-
// analysis cannot determine the jump destination without tracking the
|
|
819
|
-
// register value (which contains an encoded PC resolved at runtime).
|
|
842
|
+
// Indirect jump: allows VM to jump based on runtime values.
|
|
820
843
|
frame._pc = regs[base + this._operand()];
|
|
821
844
|
break;
|
|
822
845
|
}
|
|
@@ -829,9 +852,8 @@ VM.prototype.run = function () {
|
|
|
829
852
|
throw new Error("Unknown opcode: " + op + " at pc " + (frame._pc - 1));
|
|
830
853
|
}
|
|
831
854
|
} catch (err) {
|
|
832
|
-
// Exception handler unwinding
|
|
833
|
-
// Walk from the current frame upward until we find a frame that has an open
|
|
834
|
-
// exception handler (TRY_SETUP without a matching TRY_END).
|
|
855
|
+
// Exception handler unwinding
|
|
856
|
+
// Walk from the current frame upward until we find a frame that has an open exception handler (TRY_SETUP without a matching TRY_END).
|
|
835
857
|
// For every frame we abandon along the way, close its captured upvalues.
|
|
836
858
|
var handledFrame = null;
|
|
837
859
|
var searchFrame = this._currentFrame;
|
|
@@ -847,7 +869,7 @@ VM.prototype.run = function () {
|
|
|
847
869
|
searchFrame = this._frameStack.pop();
|
|
848
870
|
this._currentFrame = searchFrame;
|
|
849
871
|
}
|
|
850
|
-
if (!handledFrame) throw err; // no handler
|
|
872
|
+
if (!handledFrame) throw err; // if there's no handler, propagate back to host
|
|
851
873
|
|
|
852
874
|
var h = handledFrame._handlerStack.pop();
|
|
853
875
|
// Discard any call-frames that were pushed inside the try body.
|
|
@@ -862,7 +884,7 @@ VM.prototype.run = function () {
|
|
|
862
884
|
}
|
|
863
885
|
};
|
|
864
886
|
|
|
865
|
-
//
|
|
887
|
+
/* @BOOT */ // <- This comment can't be removed!
|
|
866
888
|
var globals = {}; // global object for globals
|
|
867
889
|
|
|
868
890
|
// Always pull built-ins from globalThis so eval() scoping can't shadow them
|