luau-obfuscator 1.0.3 → 1.0.4

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/dist/index.cjs CHANGED
@@ -1766,9 +1766,10 @@ var DEFAULT_BUILTIN_GLOBALS = [
1766
1766
  "next"
1767
1767
  ];
1768
1768
  var VmCompiler = class {
1769
- constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap) {
1769
+ constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap, debug = false) {
1770
1770
  this.program = program;
1771
1771
  this.opcodeMap = opcodeMap;
1772
+ this.debug = debug;
1772
1773
  this.analysis = import_luau_parser3.luauparser.analyzeScopes(program, { builtinGlobals });
1773
1774
  this.enclosing = buildEnclosingFunctionMap(program);
1774
1775
  for (const [id, binding] of this.analysis.bindings) {
@@ -1778,6 +1779,7 @@ var VmCompiler = class {
1778
1779
  }
1779
1780
  program;
1780
1781
  opcodeMap;
1782
+ debug;
1781
1783
  analysis;
1782
1784
  enclosing;
1783
1785
  captured = /* @__PURE__ */ new Set();
@@ -1836,9 +1838,39 @@ var VmCompiler = class {
1836
1838
  }
1837
1839
  emit(state, op, a, b, c, comment) {
1838
1840
  const physicalOp = this.opcodeMap ? this.opcodeMap[op] : op;
1839
- state.proto.code.push({ op: physicalOp, a, b, c, comment });
1841
+ state.proto.code.push({ op: physicalOp, a, b, c, comment, dbgLine: this.debug ? state.curLine : void 0 });
1840
1842
  return state.proto.code.length - 1;
1841
1843
  }
1844
+ /** debug 빌드: 방금 emit한 CALL 인스트럭션에 "호출 대상 표현식 문자열"과 그 소스 줄을
1845
+ * 박아넣는다(런타임이 non-callable을 잡았을 때 사람이 읽을 위치를 찍게). */
1846
+ stampCall(state, idx, callExpr, calleeExpr) {
1847
+ if (!this.debug) return;
1848
+ const instr = state.proto.code[idx];
1849
+ instr.dbgName = this.describeExpr(calleeExpr);
1850
+ const line = calleeExpr.line ?? callExpr.line;
1851
+ if (line) instr.dbgLine = line.start;
1852
+ }
1853
+ /** 표현식을 사람이 읽을 짧은 문자열로. 위치 추적용이라 완벽할 필요는 없음. */
1854
+ describeExpr(e) {
1855
+ switch (e.type) {
1856
+ case "Identifier":
1857
+ return e.name;
1858
+ case "MemberExpression":
1859
+ return `${this.describeExpr(e.object)}.${e.property.name}`;
1860
+ case "MethodCallExpression":
1861
+ return `${this.describeExpr(e.object)}:${e.method.name}(...)`;
1862
+ case "IndexExpression":
1863
+ return `${this.describeExpr(e.object)}[...]`;
1864
+ case "CallExpression":
1865
+ return `${this.describeExpr(e.callee)}(...)`;
1866
+ case "ParenthesizedExpression":
1867
+ return `(${this.describeExpr(e.expression)})`;
1868
+ case "StringLiteral":
1869
+ return `"${String(e.value).slice(0, 20)}"`;
1870
+ default:
1871
+ return `<${e.type}>`;
1872
+ }
1873
+ }
1842
1874
  konst(state, value) {
1843
1875
  return internConst(state.proto, value);
1844
1876
  }
@@ -1930,6 +1962,7 @@ var VmCompiler = class {
1930
1962
  state.regs.releaseLocalsTo(saved);
1931
1963
  }
1932
1964
  compileStatement(stmt, state) {
1965
+ if (this.debug && stmt.line) state.curLine = stmt.line.start;
1933
1966
  switch (stmt.type) {
1934
1967
  case "LocalStatement": {
1935
1968
  const bindingIds = stmt.names.map((n) => this.bindingIdOfDecl(n));
@@ -1955,11 +1988,26 @@ var VmCompiler = class {
1955
1988
  return;
1956
1989
  }
1957
1990
  case "AssignmentStatement": {
1958
- const valueRegs = stmt.values.map((e) => this.compileExpr(e, state));
1959
- stmt.targets.forEach((target, i) => {
1960
- const srcReg = valueRegs[i] ?? valueRegs[valueRegs.length - 1];
1961
- this.compileAssignTarget(target, srcReg, state);
1962
- });
1991
+ const targets = stmt.targets;
1992
+ const values = stmt.values;
1993
+ const base = state.regs.top();
1994
+ if (values.length > 0) {
1995
+ for (let i = 0; i < values.length - 1; i++) {
1996
+ this.compileExprTo(values[i], state, base + i);
1997
+ }
1998
+ const lastIdx = values.length - 1;
1999
+ const remaining = Math.max(targets.length - lastIdx, 1);
2000
+ this.compileExprMultiInto(values[lastIdx], state, base + lastIdx, remaining);
2001
+ }
2002
+ const produced = values.length === 0 ? 0 : values.length - 1 + Math.max(targets.length - (values.length - 1), 1);
2003
+ for (let i = produced; i < targets.length; i++) {
2004
+ this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
2005
+ }
2006
+ for (let i = 0; i < targets.length; i++) {
2007
+ state.regs.freeTemp(base + targets.length);
2008
+ this.compileAssignTarget(targets[i], base + i, state);
2009
+ }
2010
+ state.regs.freeTemp(base);
1963
2011
  return;
1964
2012
  }
1965
2013
  case "CompoundAssignmentStatement":
@@ -2303,7 +2351,7 @@ var VmCompiler = class {
2303
2351
  compileCallMultiInto(callExpr, state, wantCount) {
2304
2352
  const base = this.compileExpr(callExpr.callee, state);
2305
2353
  this.compileArgsContiguous(callExpr.arguments, state, base + 1);
2306
- this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1);
2354
+ this.stampCall(state, this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1), callExpr, callExpr.callee);
2307
2355
  return base;
2308
2356
  }
2309
2357
  loadConst(state, value) {
@@ -2354,7 +2402,7 @@ var VmCompiler = class {
2354
2402
  if (expr.type === "CallExpression") {
2355
2403
  const calleeReg = this.compileExpr(expr.callee, state);
2356
2404
  const b = this.compileCallArgsAndGetB(expr.arguments, state, calleeReg + 1);
2357
- this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1);
2405
+ this.stampCall(state, this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1), expr, expr.callee);
2358
2406
  for (let i = 0; i < wantCount; i++) {
2359
2407
  if (calleeReg + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, calleeReg + i, 0);
2360
2408
  }
@@ -2369,7 +2417,7 @@ var VmCompiler = class {
2369
2417
  state.regs.allocTemp();
2370
2418
  const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
2371
2419
  const b = argB === 0 ? 0 : expr.arguments.length + 2;
2372
- this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1);
2420
+ this.stampCall(state, this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1), expr, expr);
2373
2421
  for (let i = 0; i < wantCount; i++) {
2374
2422
  if (fnSlot + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, fnSlot + i, 0);
2375
2423
  }
@@ -2395,7 +2443,7 @@ var VmCompiler = class {
2395
2443
  if (calleeReg !== base) this.emit(state, 0 /* MOVE */, base, calleeReg, 0);
2396
2444
  state.regs.freeTemp(base + 1);
2397
2445
  const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
2398
- this.emit(state, 27 /* CALL */, base, b, 0);
2446
+ this.stampCall(state, this.emit(state, 27 /* CALL */, base, b, 0), expr, expr.callee);
2399
2447
  return;
2400
2448
  }
2401
2449
  if (expr.type === "MethodCallExpression") {
@@ -2411,7 +2459,7 @@ var VmCompiler = class {
2411
2459
  state.regs.freeTemp(base + 2);
2412
2460
  const argB = this.compileCallArgsAndGetB(expr.arguments, state, base + 2);
2413
2461
  const b = argB === 0 ? 0 : expr.arguments.length + 2;
2414
- this.emit(state, 27 /* CALL */, base, b, 0);
2462
+ this.stampCall(state, this.emit(state, 27 /* CALL */, base, b, 0), expr, expr);
2415
2463
  return;
2416
2464
  }
2417
2465
  if (expr.type === "VarargExpression") {
@@ -2438,6 +2486,7 @@ var VmCompiler = class {
2438
2486
  return args.length + 1;
2439
2487
  }
2440
2488
  compileExpr(expr, state, discard = false) {
2489
+ if (this.debug && expr.line) state.curLine = expr.line.start;
2441
2490
  switch (expr.type) {
2442
2491
  case "NilLiteral": {
2443
2492
  const r = state.regs.allocTemp();
@@ -2595,7 +2644,8 @@ var VmCompiler = class {
2595
2644
  case "CallExpression": {
2596
2645
  const base = this.compileExpr(expr.callee, state);
2597
2646
  const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
2598
- this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
2647
+ const callIdx = this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
2648
+ this.stampCall(state, callIdx, expr, expr.callee);
2599
2649
  state.regs.freeTemp(base + (discard ? 0 : 1));
2600
2650
  return base;
2601
2651
  }
@@ -2607,7 +2657,8 @@ var VmCompiler = class {
2607
2657
  state.regs.allocTemp();
2608
2658
  const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
2609
2659
  const b = argB === 0 ? 0 : expr.arguments.length + 2;
2610
- this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
2660
+ const callIdx = this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
2661
+ this.stampCall(state, callIdx, expr, expr);
2611
2662
  state.regs.freeTemp(fnSlot + (discard ? 0 : 1));
2612
2663
  return fnSlot;
2613
2664
  }
@@ -2720,15 +2771,20 @@ function serializeConstValue(v) {
2720
2771
  if (typeof v === "number") return numberLiteral(v);
2721
2772
  return booleanLiteral(v);
2722
2773
  }
2723
- function serializeInstrs(proto) {
2724
- const fields = proto.code.map((instr) => positionalField(
2725
- table([
2774
+ function serializeInstrs(proto, debug) {
2775
+ const fields = proto.code.map((instr) => {
2776
+ const tuple = [
2726
2777
  positionalField(vmNumberLiteral(instr.op)),
2727
2778
  positionalField(vmNumberLiteral(instr.a)),
2728
2779
  positionalField(vmNumberLiteral(instr.b)),
2729
2780
  positionalField(vmNumberLiteral(instr.c))
2730
- ])
2731
- ));
2781
+ ];
2782
+ if (debug) {
2783
+ tuple.push(positionalField(numberLiteral(instr.dbgLine ?? 0)));
2784
+ tuple.push(positionalField(stringLiteral(instr.dbgName ?? "")));
2785
+ }
2786
+ return positionalField(table(tuple));
2787
+ });
2732
2788
  return table(fields);
2733
2789
  }
2734
2790
  function serializeUpvalDescs(proto, names) {
@@ -2740,29 +2796,41 @@ function serializeUpvalDescs(proto, names) {
2740
2796
  ));
2741
2797
  return table(fields);
2742
2798
  }
2743
- function serializeProto(proto, names) {
2799
+ function serializeProto(proto, names, debug = false) {
2744
2800
  const fields = [
2745
2801
  namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2746
2802
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2747
2803
  namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2748
- namedField(names.code, serializeInstrs(proto)),
2804
+ namedField(names.code, serializeInstrs(proto, debug)),
2749
2805
  namedField(names.consts, serializeConsts(proto.consts)),
2750
2806
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2751
- namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
2807
+ namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names, debug)))))
2752
2808
  ];
2753
2809
  return table(fields);
2754
2810
  }
2755
2811
 
2756
2812
  // src/passes/vmify/runtime.ts
2757
- function buildVmRuntimeSource(names, opcodeMap) {
2813
+ function buildVmRuntimeSource(names, opcodeMap, debug = false) {
2758
2814
  const N = names;
2759
2815
  const op = (o) => opcodeMap[o];
2760
2816
  return `
2761
2817
  local ${N.handlers} = {}
2762
2818
 
2819
+ -- \uAC01 \uD578\uB4E4\uB7EC\uB294 "\uB2E4\uC74C pc(\uC22B\uC790)"\uB97C \uBC18\uD658\uD55C\uB2E4. RETURN \uD578\uB4E4\uB7EC\uB9CC table.pack \uACB0\uACFC(\uD14C\uC774\uBE14)\uB97C
2820
+ -- \uBC18\uD658\uD558\uBA70, \uADF8\uAC8C \uD568\uC218 \uC2E4\uD589 \uC885\uB8CC \uC2E0\uD638\uB2E4. Luau\uB294 proper tail call\uC744 \uD558\uC9C0 \uC54A\uC73C\uBBC0\uB85C
2821
+ -- \uD578\uB4E4\uB7EC\uB4E4\uC774 \uC11C\uB85C \uAF2C\uB9AC \uD638\uCD9C\uB85C \uC774\uC5B4\uC9C0\uBA74 \uBA85\uB839\uC5B4\uB2F9 \uC2A4\uD0DD \uD504\uB808\uC784\uC774 \uC313\uC5EC \uD130\uC9C4\uB2E4 \u2014 \uADF8\uB798\uC11C
2822
+ -- \uC5EC\uAE30\uC11C while \uB8E8\uD504\uB85C \uB3CC\uB824 \uC2A4\uD0DD \uAE4A\uC774\uB97C \uD568\uC218 \uC2E4\uD589 \uB0B4\uB0B4 \uC77C\uC815\uD558\uAC8C \uC720\uC9C0\uD55C\uB2E4.
2763
2823
  local function ${N.dispatch}(${N.frame}, pc)
2764
- local instr = ${N.frame}.${N.code}[pc]
2765
- return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2824
+ local ${N.handlers} = ${N.handlers}
2825
+ while true do
2826
+ local instr = ${N.frame}.${N.code}[pc]
2827
+ local next = ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2828
+ if type(next) == "number" then
2829
+ pc = next
2830
+ else
2831
+ return table.unpack(next, 1, next.n)
2832
+ end
2833
+ end
2766
2834
  end
2767
2835
 
2768
2836
  local function ${N.rk}(${N.frame}, x)
@@ -2776,67 +2844,67 @@ end
2776
2844
  -- MOVE
2777
2845
  ${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
2778
2846
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2779
- return ${N.dispatch}(${N.frame}, pc + 1)
2847
+ return pc + 1
2780
2848
  end
2781
2849
 
2782
2850
  -- LOADK
2783
2851
  ${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
2784
2852
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2785
- return ${N.dispatch}(${N.frame}, pc + 1)
2853
+ return pc + 1
2786
2854
  end
2787
2855
 
2788
2856
  -- LOADBOOL
2789
2857
  ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2790
2858
  ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2791
- return ${N.dispatch}(${N.frame}, pc + 1)
2859
+ return pc + 1
2792
2860
  end
2793
2861
 
2794
2862
  -- LOADNIL
2795
2863
  ${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
2796
2864
  for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2797
- return ${N.dispatch}(${N.frame}, pc + 1)
2865
+ return pc + 1
2798
2866
  end
2799
2867
 
2800
2868
  -- GETUPVAL
2801
2869
  ${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
2802
2870
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2803
- return ${N.dispatch}(${N.frame}, pc + 1)
2871
+ return pc + 1
2804
2872
  end
2805
2873
 
2806
2874
  -- SETUPVAL
2807
2875
  ${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
2808
2876
  ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2809
- return ${N.dispatch}(${N.frame}, pc + 1)
2877
+ return pc + 1
2810
2878
  end
2811
2879
 
2812
2880
  -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2813
2881
  ${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
2814
2882
  ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2815
- return ${N.dispatch}(${N.frame}, pc + 1)
2883
+ return pc + 1
2816
2884
  end
2817
2885
 
2818
2886
  -- SETGLOBAL
2819
2887
  ${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
2820
2888
  ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2821
- return ${N.dispatch}(${N.frame}, pc + 1)
2889
+ return pc + 1
2822
2890
  end
2823
2891
 
2824
2892
  -- GETTABLE
2825
2893
  ${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
2826
2894
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2827
- return ${N.dispatch}(${N.frame}, pc + 1)
2895
+ return pc + 1
2828
2896
  end
2829
2897
 
2830
2898
  -- SETTABLE
2831
2899
  ${N.handlers}[${op(9 /* SETTABLE */)}] = function(${N.frame}, pc, instr)
2832
2900
  ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
2833
- return ${N.dispatch}(${N.frame}, pc + 1)
2901
+ return pc + 1
2834
2902
  end
2835
2903
 
2836
2904
  -- NEWTABLE
2837
2905
  ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2838
2906
  ${N.frame}.${N.R}[instr[2]] = {}
2839
- return ${N.dispatch}(${N.frame}, pc + 1)
2907
+ return pc + 1
2840
2908
  end
2841
2909
 
2842
2910
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
@@ -2844,90 +2912,90 @@ ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
2844
2912
  local obj = ${N.frame}.${N.R}[instr[3]]
2845
2913
  ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2846
2914
  ${N.frame}.${N.R}[instr[2] + 1] = obj
2847
- return ${N.dispatch}(${N.frame}, pc + 1)
2915
+ return pc + 1
2848
2916
  end
2849
2917
 
2850
2918
  -- ADD SUB MUL DIV MOD POW
2851
2919
  ${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
2852
2920
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2853
- return ${N.dispatch}(${N.frame}, pc + 1)
2921
+ return pc + 1
2854
2922
  end
2855
2923
  ${N.handlers}[${op(13 /* SUB */)}] = function(${N.frame}, pc, instr)
2856
2924
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
2857
- return ${N.dispatch}(${N.frame}, pc + 1)
2925
+ return pc + 1
2858
2926
  end
2859
2927
  ${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
2860
2928
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2861
- return ${N.dispatch}(${N.frame}, pc + 1)
2929
+ return pc + 1
2862
2930
  end
2863
2931
  ${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
2864
2932
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2865
- return ${N.dispatch}(${N.frame}, pc + 1)
2933
+ return pc + 1
2866
2934
  end
2867
2935
  ${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
2868
2936
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2869
- return ${N.dispatch}(${N.frame}, pc + 1)
2937
+ return pc + 1
2870
2938
  end
2871
2939
  ${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
2872
2940
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2873
- return ${N.dispatch}(${N.frame}, pc + 1)
2941
+ return pc + 1
2874
2942
  end
2875
2943
 
2876
2944
  -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2877
2945
  ${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
2878
2946
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2879
- return ${N.dispatch}(${N.frame}, pc + 1)
2947
+ return pc + 1
2880
2948
  end
2881
2949
 
2882
2950
  -- CONCAT
2883
2951
  ${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
2884
2952
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2885
- return ${N.dispatch}(${N.frame}, pc + 1)
2953
+ return pc + 1
2886
2954
  end
2887
2955
 
2888
2956
  -- UNM
2889
2957
  ${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
2890
2958
  ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2891
- return ${N.dispatch}(${N.frame}, pc + 1)
2959
+ return pc + 1
2892
2960
  end
2893
2961
 
2894
2962
  -- NOT
2895
2963
  ${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
2896
2964
  ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2897
- return ${N.dispatch}(${N.frame}, pc + 1)
2965
+ return pc + 1
2898
2966
  end
2899
2967
 
2900
2968
  -- LEN
2901
2969
  ${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
2902
2970
  ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2903
- return ${N.dispatch}(${N.frame}, pc + 1)
2971
+ return pc + 1
2904
2972
  end
2905
2973
 
2906
2974
  -- JMP (pc += a)
2907
2975
  ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2908
- return ${N.dispatch}(${N.frame}, pc + instr[2])
2976
+ return pc + instr[2]
2909
2977
  end
2910
2978
 
2911
2979
  -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2912
2980
  ${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
2913
2981
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2914
- return ${N.dispatch}(${N.frame}, pc + 1)
2982
+ return pc + 1
2915
2983
  end
2916
2984
  ${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
2917
2985
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2918
- return ${N.dispatch}(${N.frame}, pc + 1)
2986
+ return pc + 1
2919
2987
  end
2920
2988
  ${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
2921
2989
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2922
- return ${N.dispatch}(${N.frame}, pc + 1)
2990
+ return pc + 1
2923
2991
  end
2924
2992
 
2925
2993
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2926
2994
  ${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
2927
2995
  if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2928
- return ${N.dispatch}(${N.frame}, pc + 2)
2996
+ return pc + 2
2929
2997
  end
2930
- return ${N.dispatch}(${N.frame}, pc + 1)
2998
+ return pc + 1
2931
2999
  end
2932
3000
 
2933
3001
  -- CALL
@@ -2942,7 +3010,13 @@ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2942
3010
  end
2943
3011
  local args = table.create(nargs)
2944
3012
  for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
2945
- local rets = table.pack(fn(table.unpack(args, 1, nargs)))
3013
+ ${debug ? ` if type(fn) ~= "function" then
3014
+ local mt = getmetatable(fn)
3015
+ if not (mt and mt.__call) then
3016
+ error(("[vmdebug] attempt to call a %s value: '%s' (source line %d)"):format(type(fn), tostring(instr[6]), tonumber(instr[5]) or -1), 0)
3017
+ end
3018
+ end
3019
+ ` : ""} local rets = table.pack(fn(table.unpack(args, 1, nargs)))
2946
3020
  if c == 1 then
2947
3021
  -- discard
2948
3022
  elseif c == 0 then
@@ -2952,23 +3026,27 @@ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2952
3026
  local want = c - 1
2953
3027
  for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
2954
3028
  end
2955
- return ${N.dispatch}(${N.frame}, pc + 1)
3029
+ return pc + 1
2956
3030
  end
2957
3031
 
2958
3032
  -- RETURN
3033
+ -- \uB2E4\uB978 \uD578\uB4E4\uB7EC\uB294 "\uB2E4\uC74C pc(\uC22B\uC790)"\uB97C \uBC18\uD658\uD558\uC9C0\uB9CC, RETURN\uB9CC\uC740 \uACB0\uACFC\uAC12\uC744 table.pack\uC73C\uB85C \uC2F8\uC11C
3034
+ -- \uD14C\uC774\uBE14\uC744 \uBC18\uD658\uD55C\uB2E4 \u2014 dispatch \uB8E8\uD504\uB294 \uBC18\uD658\uAC12\uC774 \uC22B\uC790\uAC00 \uC544\uB2C8\uBA74 \uC774\uAC78 \uD568\uC218 \uC885\uB8CC \uC2E0\uD638\uB85C \uBCF4\uACE0
3035
+ -- table.unpack(next, 1, next.n)\uC73C\uB85C \uD480\uC5B4 \uCD5C\uC885 \uBC18\uD658\uD55C\uB2E4. table.pack\uC774 n\uC744 \uCC44\uC6CC \uC8FC\uBBC0\uB85C
3036
+ -- \uC911\uAC04 nil\uC774 \uC788\uC5B4\uB3C4 \uBC18\uD658 \uAC1C\uC218\uAC00 \uC815\uD655\uD788 \uBCF4\uC874\uB41C\uB2E4.
2959
3037
  ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2960
3038
  local a, b = instr[2], instr[3]
2961
3039
  if b == 0 then
2962
- return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
3040
+ return table.pack(table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1))
2963
3041
  end
2964
- return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
3042
+ return table.pack(table.unpack(${N.frame}.${N.R}, a, a + b - 2))
2965
3043
  end
2966
3044
 
2967
3045
  -- FORPREP
2968
3046
  ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2969
3047
  local a = instr[2]
2970
3048
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
2971
- return ${N.dispatch}(${N.frame}, pc + instr[3])
3049
+ return pc + instr[3]
2972
3050
  end
2973
3051
 
2974
3052
  -- FORLOOP
@@ -2984,9 +3062,9 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2984
3062
  end
2985
3063
  if ok then
2986
3064
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2987
- return ${N.dispatch}(${N.frame}, pc + instr[3])
3065
+ return pc + instr[3]
2988
3066
  end
2989
- return ${N.dispatch}(${N.frame}, pc + 1)
3067
+ return pc + 1
2990
3068
  end
2991
3069
 
2992
3070
  -- CLOSURE
@@ -3006,7 +3084,7 @@ ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
3006
3084
  ${N.frame}.${N.R}[instr[2]] = function(...)
3007
3085
  return ${N.execute}(childProto, capturedUpvals, ...)
3008
3086
  end
3009
- return ${N.dispatch}(${N.frame}, pc + 1 + n)
3087
+ return pc + 1 + n
3010
3088
  end
3011
3089
 
3012
3090
  -- VARARG
@@ -3018,7 +3096,7 @@ ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
3018
3096
  if instr[3] == 0 then
3019
3097
  ${N.frame}.${N.multiTop} = instr[2] + count
3020
3098
  end
3021
- return ${N.dispatch}(${N.frame}, pc + 1)
3099
+ return pc + 1
3022
3100
  end
3023
3101
 
3024
3102
  -- SETLIST
@@ -3031,7 +3109,7 @@ ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
3031
3109
  for i = 0, n - 1 do
3032
3110
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
3033
3111
  end
3034
- return ${N.dispatch}(${N.frame}, pc + 1)
3112
+ return pc + 1
3035
3113
  end
3036
3114
 
3037
3115
  function ${N.execute}(proto, upvals, ...)
@@ -3120,19 +3198,20 @@ function markRuntimeNumbersAsStructural(body) {
3120
3198
  function runVmify(program, options) {
3121
3199
  const builtinGlobals = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
3122
3200
  const random = options.random ?? Math.random;
3201
+ const debug = options.debug ?? false;
3123
3202
  const opcodeMap = createOpcodeMap(random);
3124
3203
  const names = generateVmNames(random);
3125
- const compiler = new VmCompiler(program, builtinGlobals, opcodeMap);
3204
+ const compiler = new VmCompiler(program, builtinGlobals, opcodeMap, debug);
3126
3205
  const topProto = compiler.compile();
3127
3206
  const usedGlobalNames = compiler.getUsedGlobalNames();
3128
3207
  const globalsTable = table(
3129
3208
  usedGlobalNames.map((name) => namedField(name, identifier(name)))
3130
3209
  );
3131
- const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
3210
+ const runtimeSource = buildVmRuntimeSource(names, opcodeMap, debug);
3132
3211
  const runtimeProgram = import_luau_parser4.luauparser.parse(runtimeSource);
3133
3212
  const runtimeStatements = runtimeProgram.body.statements;
3134
3213
  markRuntimeNumbersAsStructural(runtimeProgram.body);
3135
- const protoLiteral = serializeProto(topProto, names);
3214
+ const protoLiteral = serializeProto(topProto, names, debug);
3136
3215
  const newBody = [
3137
3216
  localStatement(names.globals, globalsTable),
3138
3217
  ...runtimeStatements,
package/dist/index.d.cts CHANGED
@@ -60,6 +60,13 @@ interface VmifyOptions {
60
60
  * Math.random. 재현 가능한 빌드가 필요하면(테스트 등) 시드 고정 PRNG를 넘길 것.
61
61
  */
62
62
  random?: () => number;
63
+ /**
64
+ * 디버그 빌드. 켜면 각 인스트럭션에 원본 소스 줄 번호를, CALL에는 호출 대상 이름을
65
+ * 함께 직렬화하고, 런타임 CALL 핸들러가 non-callable 값을 부르려 할 때 그냥
66
+ * "attempt to call a nil value"가 아니라 "[vmdebug] ... '대상이름' (source line N)"으로
67
+ * 원본 위치를 찍어준다. 산출물이 조금 커지고 VM 시그니처가 노출되므로 진단용으로만 쓸 것.
68
+ */
69
+ debug?: boolean;
63
70
  }
64
71
 
65
72
  type ObfuscateConfigEach<C> = ({
package/dist/index.d.ts CHANGED
@@ -60,6 +60,13 @@ interface VmifyOptions {
60
60
  * Math.random. 재현 가능한 빌드가 필요하면(테스트 등) 시드 고정 PRNG를 넘길 것.
61
61
  */
62
62
  random?: () => number;
63
+ /**
64
+ * 디버그 빌드. 켜면 각 인스트럭션에 원본 소스 줄 번호를, CALL에는 호출 대상 이름을
65
+ * 함께 직렬화하고, 런타임 CALL 핸들러가 non-callable 값을 부르려 할 때 그냥
66
+ * "attempt to call a nil value"가 아니라 "[vmdebug] ... '대상이름' (source line N)"으로
67
+ * 원본 위치를 찍어준다. 산출물이 조금 커지고 VM 시그니처가 노출되므로 진단용으로만 쓸 것.
68
+ */
69
+ debug?: boolean;
63
70
  }
64
71
 
65
72
  type ObfuscateConfigEach<C> = ({