js-confuser-vm 0.1.0 → 0.1.2

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.
Files changed (63) hide show
  1. package/README.md +281 -147
  2. package/dist/build-runtime.js +41 -15
  3. package/dist/compiler.js +714 -265
  4. package/dist/disassembler.js +367 -0
  5. package/dist/index.js +7 -2
  6. package/dist/runtime.js +160 -119
  7. package/dist/template.js +163 -42
  8. package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
  9. package/dist/transforms/bytecode/concealConstants.js +2 -2
  10. package/dist/transforms/bytecode/controlFlowFlattening.js +569 -0
  11. package/dist/transforms/bytecode/dispatcher.js +15 -111
  12. package/dist/transforms/bytecode/macroOpcodes.js +2 -2
  13. package/{src/transforms/bytecode/resolveContants.ts → dist/transforms/bytecode/resolveConstants.js} +30 -56
  14. package/dist/transforms/bytecode/resolveRegisters.js +23 -4
  15. package/dist/transforms/bytecode/selfModifying.js +88 -21
  16. package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
  17. package/dist/transforms/bytecode/specializedOpcodes.js +23 -12
  18. package/dist/transforms/bytecode/stringConcealing.js +288 -0
  19. package/dist/transforms/runtime/classObfuscation.js +43 -0
  20. package/dist/transforms/runtime/handlerTable.js +91 -0
  21. package/dist/transforms/runtime/semanticOpcodes.js +35 -0
  22. package/dist/transforms/runtime/specializedOpcodes.js +11 -5
  23. package/dist/types.js +1 -1
  24. package/dist/utils/ast-utils.js +75 -0
  25. package/dist/utils/op-utils.js +1 -2
  26. package/dist/utils/pass-utils.js +100 -0
  27. package/dist/utils/profile-utils.js +3 -0
  28. package/package.json +8 -1
  29. package/.gitmodules +0 -4
  30. package/.prettierignore +0 -1
  31. package/CHANGELOG.md +0 -335
  32. package/babel-plugin-inline-runtime.cjs +0 -34
  33. package/babel.config.json +0 -23
  34. package/index.ts +0 -38
  35. package/jest-strip-types.js +0 -10
  36. package/jest.config.js +0 -52
  37. package/src/build-runtime.ts +0 -78
  38. package/src/compiler.ts +0 -2593
  39. package/src/index.ts +0 -14
  40. package/src/minify.ts +0 -21
  41. package/src/options.ts +0 -18
  42. package/src/runtime.ts +0 -923
  43. package/src/template.ts +0 -141
  44. package/src/transforms/bytecode/aliasedOpcodes.ts +0 -148
  45. package/src/transforms/bytecode/concealConstants.ts +0 -52
  46. package/src/transforms/bytecode/dispatcher.ts +0 -398
  47. package/src/transforms/bytecode/macroOpcodes.ts +0 -193
  48. package/src/transforms/bytecode/microOpcodes.ts +0 -291
  49. package/src/transforms/bytecode/resolveLabels.ts +0 -112
  50. package/src/transforms/bytecode/resolveRegisters.ts +0 -221
  51. package/src/transforms/bytecode/selfModifying.ts +0 -121
  52. package/src/transforms/bytecode/specializedOpcodes.ts +0 -153
  53. package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
  54. package/src/transforms/runtime/internalVariables.ts +0 -270
  55. package/src/transforms/runtime/macroOpcodes.ts +0 -138
  56. package/src/transforms/runtime/microOpcodes.ts +0 -93
  57. package/src/transforms/runtime/minify.ts +0 -1
  58. package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
  59. package/src/transforms/runtime/specializedOpcodes.ts +0 -156
  60. package/src/types.ts +0 -93
  61. package/src/utils/op-utils.ts +0 -48
  62. package/src/utils/random-utils.ts +0 -31
  63. package/tsconfig.json +0 -12
package/dist/template.js CHANGED
@@ -5,13 +5,13 @@
5
5
  //
6
6
  // ── Usage ─────────────────────────────────────────────────────────────────────
7
7
  //
8
- // const tmpl = new Template(`
8
+ // const template = new Template(`
9
9
  // function {name}(x, y) {
10
10
  // return x + y;
11
11
  // }
12
12
  // `);
13
13
  //
14
- // const bc = tmpl.compile({ name: "myHelper" }, parentCompiler);
14
+ // const bc = template.compile({ name: "myHelper" }, parentCompiler);
15
15
  // result.push(...bc);
16
16
  //
17
17
  // ── How it works ──────────────────────────────────────────────────────────────
@@ -45,7 +45,7 @@
45
45
  // • Opcodes with no JS equivalent (JUMP_REG, BXOR used as decode, etc.) cannot
46
46
  // be expressed in a template; write those instruction arrays manually.
47
47
 
48
- import { Compiler } from "./compiler.js";
48
+ import { Compiler, SOURCE_NODE_SYM } from "./compiler.js";
49
49
  import { DEFAULT_OPTIONS } from "./options.js";
50
50
  export class Template {
51
51
  constructor(source) {
@@ -62,6 +62,67 @@ export class Template {
62
62
  });
63
63
  }
64
64
 
65
+ // ── Shared child-compiler setup ───────────────────────────────────────────
66
+ // Creates a child Compiler that inherits the parent's OP table and label
67
+ // counter, shares fnDescriptors (so inner functions auto-register), then
68
+ // compiles `code` to raw IR. Returns startIdx so callers can slice out
69
+ // the descriptors that belong to this template invocation.
70
+ _setupChild(code, parentCompiler) {
71
+ const child = new Compiler({
72
+ ...DEFAULT_OPTIONS,
73
+ randomizeOpcodes: false
74
+ });
75
+ child.OP = {
76
+ ...parentCompiler.OP
77
+ };
78
+ child.OP_NAME = {
79
+ ...parentCompiler.OP_NAME
80
+ };
81
+ child.JUMP_OPS = new Set(parentCompiler.JUMP_OPS);
82
+ child.SENTINELS = {
83
+ ...parentCompiler.SENTINELS
84
+ };
85
+ child._makeLabel = parentCompiler._makeLabel.bind(parentCompiler);
86
+ const startIdx = parentCompiler.fnDescriptors.length;
87
+ child.fnDescriptors = parentCompiler.fnDescriptors;
88
+ child.compile(code);
89
+ return {
90
+ startIdx
91
+ };
92
+ }
93
+
94
+ // ── Inner-function bytecode collection ───────────────────────────────────
95
+ // Gathers the descriptors for functions declared inside the template (all
96
+ // entries after startIdx in fnDescriptors), assembles their defineLabel +
97
+ // body into a flat bytecode array, and strips template source locations so
98
+ // they never leak into the parent's debug output.
99
+ _collectInnerFunctions(parentCompiler, startIdx) {
100
+ const innerFns = parentCompiler.fnDescriptors.slice(startIdx + 1);
101
+ const innerBytecode = [];
102
+ for (const desc of innerFns) {
103
+ innerBytecode.push([null, {
104
+ type: "defineLabel",
105
+ label: desc.entryLabel
106
+ }]);
107
+ for (const instr of desc.bytecode) {
108
+ innerBytecode.push(instr);
109
+ }
110
+ }
111
+ this._stripSourceNodes(innerBytecode);
112
+ return {
113
+ innerFns,
114
+ innerBytecode
115
+ };
116
+ }
117
+
118
+ // ── Source-location stripping ─────────────────────────────────────────────
119
+ // Removes the SOURCE_NODE_SYM symbol from every instruction so that template-
120
+ // internal line numbers (which point into the template string, not the user's
121
+ // source) never appear in the bytecode comment block.
122
+ _stripSourceNodes(bytecode) {
123
+ for (const instr of bytecode) delete instr[SOURCE_NODE_SYM];
124
+ }
125
+
65
126
  // ── Main entry point ───────────────────────────────────────────────────────
66
127
  /**
67
128
  * Compile the template and return the inner (non-main) function descriptors
@@ -90,53 +151,113 @@ export class Template {
90
151
  * template's main-scope instructions.
91
152
  */
92
153
  compile(variables, parentCompiler) {
93
- // ── 1. Interpolate ────────────────────────────────────────────────────
94
154
  const code = this._interpolate(variables);
95
-
96
- // ── 2. Create child compiler, inherit parent's OP table ───────────────
97
- // randomizeOpcodes is disabled — we copy the parent's already-randomized
98
- // mapping directly so all opcode numbers are identical.
99
- const child = new Compiler({
100
- ...DEFAULT_OPTIONS,
101
- randomizeOpcodes: false
102
- });
103
- child.OP = {
104
- ...parentCompiler.OP
105
- };
106
- child.OP_NAME = {
107
- ...parentCompiler.OP_NAME
155
+ const {
156
+ startIdx
157
+ } = this._setupChild(code, parentCompiler);
158
+ const {
159
+ innerFns,
160
+ innerBytecode
161
+ } = this._collectInnerFunctions(parentCompiler, startIdx);
162
+ return {
163
+ functions: innerFns,
164
+ bytecode: innerBytecode
108
165
  };
109
- child.JUMP_OPS = new Set(parentCompiler.JUMP_OPS);
110
- child._makeLabel = parentCompiler._makeLabel.bind(parentCompiler);
166
+ }
111
167
 
112
- // Record how many descriptors the parent already has so we can find the
113
- // child's main (index = startIdx) and inner functions (startIdx+1 …).
114
- const startIdx = parentCompiler.fnDescriptors.length;
115
- child.fnDescriptors = parentCompiler.fnDescriptors; // share inner functions auto-register
168
+ // ── Inline compilation ───────────────────────────────────────────────────
169
+ /**
170
+ * Compile the template and return the **main scope** bytecode, with all
171
+ * register operands remapped to belong to `targetFnId`. This allows
172
+ * bytecode transforms to express high-level JS control flow (while-loops,
173
+ * if-chains, variable declarations) via Template and splice the result
174
+ * directly into an existing function's instruction stream.
175
+ *
176
+ * The implicit trailing RETURN added by _compileFunctionDecl is stripped —
177
+ * inline code should flow into the surrounding bytecode, not return.
178
+ *
179
+ * @param variables Substitution map for {name} placeholders.
180
+ * @param parentCompiler The Compiler whose OP table, label counter, and
181
+ * fnDescriptors are shared.
182
+ * @param targetFnId The function whose register file the template's
183
+ * registers should be remapped into.
184
+ * @param maxId Live map of max register id per fnId — updated
185
+ * in-place as new registers are allocated.
186
+ *
187
+ * @returns
188
+ * bytecode — main-scope IR (no entry defineLabel, no trailing RETURN),
189
+ * ready to splice into the target function's instruction stream.
190
+ * registers — mapping of JS variable names → remapped RegisterOperands,
191
+ * so the caller can reference template-declared variables
192
+ * (e.g. the `state` variable in CFF).
193
+ * functions — inner function descriptors (same as compile()).
194
+ * innerBytecode — inner function bytecode blocks (same as compile()).
195
+ */
196
+ compileInline(variables, parentCompiler, targetFnId, maxId) {
197
+ const code = this._interpolate(variables);
198
+ const {
199
+ startIdx
200
+ } = this._setupChild(code, parentCompiler);
201
+ const mainDesc = parentCompiler.fnDescriptors[startIdx];
202
+ const mainFnId = mainDesc._fnIdx;
203
+ const mainBc = mainDesc.bytecode;
116
204
 
117
- // ── 3. Compile to raw IR (no passes) ──────────────────────────────────
118
- child.compile(code);
205
+ // ── Remap registers from the template's main fnId → targetFnId ────────
206
+ // Build a mapping: old register id → new RegisterOperand in targetFnId.
207
+ const regRemap = new Map();
208
+ const remapReg = id => {
209
+ if (!regRemap.has(id)) {
210
+ const next = (maxId.get(targetFnId) ?? -1) + 1;
211
+ maxId.set(targetFnId, next);
212
+ regRemap.set(id, {
213
+ type: "register",
214
+ id: next,
215
+ fnId: targetFnId
216
+ });
217
+ }
218
+ return regRemap.get(id);
219
+ };
220
+ for (const instr of mainBc) {
221
+ for (let j = 1; j < instr.length; j++) {
222
+ const op = instr[j];
223
+ if (op && typeof op === "object" && op.type === "register" && op.fnId === mainFnId) {
224
+ const mapped = remapReg(op.id);
225
+ op.id = mapped.id;
226
+ op.fnId = mapped.fnId;
227
+ }
228
+ }
229
+ }
119
230
 
120
- // parentCompiler.fnDescriptors[startIdx] child's main (discard)
121
- // parentCompiler.fnDescriptors[startIdx+1…] inner helper functions
122
- const innerDescs = parentCompiler.fnDescriptors.slice(startIdx + 1);
231
+ // ── Build variable name remapped register mapping ───────────────────
232
+ const registers = new Map();
233
+ const locals = mainDesc.ctx.scope._locals;
234
+ for (const [name, reg] of locals) {
235
+ const mapped = regRemap.get(reg.id);
236
+ if (mapped) registers.set(name, mapped);
237
+ }
123
238
 
124
- // Build bytecode blocks for inner functions only.
125
- // child.bytecode was assembled by _compileMain from ALL fnDescriptors
126
- // starting at startIdx. We rebuild it here from the inner descs only.
127
- const innerBytecode = [];
128
- for (const desc of innerDescs) {
129
- innerBytecode.push([null, {
130
- type: "defineLabel",
131
- label: desc.entryLabel
132
- }]);
133
- for (const instr of desc.bytecode) {
134
- innerBytecode.push(instr);
135
- }
239
+ // ── Strip entry defineLabel and trailing implicit RETURN ───────────────
240
+ let bytecode = mainBc.filter(instr => {
241
+ const op0 = instr[1];
242
+ return !(instr[0] === null && op0?.type === "defineLabel" && op0.label === mainDesc.entryLabel);
243
+ });
244
+
245
+ // Remove trailing LOAD_CONST undefined + RETURN (implicit return added
246
+ // by _compileFunctionDecl).
247
+ const OP = parentCompiler.OP;
248
+ if (bytecode.length >= 2 && bytecode[bytecode.length - 1][0] === OP.RETURN && bytecode[bytecode.length - 2][0] === OP.LOAD_CONST) {
249
+ bytecode = bytecode.slice(0, -2);
136
250
  }
251
+ this._stripSourceNodes(bytecode);
252
+ const {
253
+ innerFns,
254
+ innerBytecode
255
+ } = this._collectInnerFunctions(parentCompiler, startIdx);
137
256
  return {
138
- functions: innerDescs,
139
- bytecode: innerBytecode
257
+ bytecode,
258
+ registers,
259
+ functions: innerFns,
260
+ innerBytecode
140
261
  };
141
262
  }
142
263
  }
@@ -1,4 +1,4 @@
1
- import { SOURCE_NODE_SYM } from "../../compiler.js";
1
+ import { OP_ORIGINAL, SOURCE_NODE_SYM } from "../../compiler.js";
2
2
  import { nextFreeSlot } from "../../utils/op-utils.js";
3
3
  import { shuffle } from "../../utils/random-utils.js";
4
4
 
@@ -41,6 +41,9 @@ export function aliasedOpcodes(bc, compiler) {
41
41
  const arity = instr.length - 1;
42
42
  if (arity < 1) continue; // 0-operand opcodes have nothing to permute
43
43
 
44
+ const opName = compiler.OP_NAME[op];
45
+ if (!OP_ORIGINAL[opName]) continue; // only consider original ops, not already-specialized ones
46
+
44
47
  const existing = opStats.get(op);
45
48
  if (!existing) {
46
49
  opStats.set(op, {
@@ -2,8 +2,8 @@ export function concealConstants(bytecode, compiler) {
2
2
  const newBytecode = [];
3
3
  for (const instr of bytecode) {
4
4
  const [op, ...operands] = instr;
5
- const hasContant = operands.some(o => o !== undefined && o !== null && typeof o === "object" && o.type === "constant");
6
- if (!hasContant) {
5
+ const hasConstant = operands.some(o => o !== undefined && o !== null && typeof o === "object" && o.type === "constant");
6
+ if (!hasConstant) {
7
7
  newBytecode.push(instr);
8
8
  continue;
9
9
  }