luau-obfuscator 1.0.2 → 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 +171 -74
- package/dist/index.d.cts +13 -6
- package/dist/index.d.ts +13 -6
- package/dist/index.js +171 -74
- package/generated/grammers.luau +3654 -5314
- package/generated/tests.luau +2196 -3349
- package/package.json +1 -1
- package/scripts/test.js +1 -3
- package/src/passes/StringsToExpressions.ts +6 -6
- package/src/passes/Vmify.ts +24 -11
- package/src/passes/vmify/compiler.ts +90 -13
- package/src/passes/vmify/opcodes.ts +4 -0
- package/src/passes/vmify/runtime.ts +80 -51
- package/src/passes/vmify/serialize.ts +19 -13
package/dist/index.d.ts
CHANGED
|
@@ -47,12 +47,12 @@ interface EncryptNumbersOptions {
|
|
|
47
47
|
|
|
48
48
|
interface VmifyOptions {
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
50
|
+
* 스코프 분석에 미리 등록해둘 전역 이름 시드 목록. 여기 없는 이름이라도 프로그램에서
|
|
51
|
+
* 실제로 참조되면(로컬/파라미터/업밸류로 안 풀리는 식별자는 전부) 스코프 분석이
|
|
52
|
+
* 자동으로 전역으로 잡아내므로, 브릿지 테이블은 이 옵션이 아니라
|
|
53
|
+
* VmCompiler.getUsedGlobalNames()(실제 사용된 전역 전체)로 만든다 — 옛날엔 이 목록에
|
|
54
|
+
* 없는 이름을 쓰면 컴파일은 되는데 GETGLOBAL이 조용히 nil을 반환해서
|
|
55
|
+
* "attempt to call a nil value"로 터졌었음.
|
|
56
56
|
*/
|
|
57
57
|
builtinGlobals?: readonly string[];
|
|
58
58
|
/**
|
|
@@ -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.js
CHANGED
|
@@ -566,7 +566,7 @@ function splitByteChunks(bytes, min, max) {
|
|
|
566
566
|
return chunks.length > 0 ? chunks : [bytes];
|
|
567
567
|
}
|
|
568
568
|
function stringCharCall(bytes) {
|
|
569
|
-
return call(member(identifier("string"), "char"), bytes.map(
|
|
569
|
+
return call(member(identifier("string"), "char"), bytes.map(vmNumberLiteral));
|
|
570
570
|
}
|
|
571
571
|
function buildConcatChain(chunks) {
|
|
572
572
|
function build(lo, hi) {
|
|
@@ -597,7 +597,7 @@ function buildShuffledTables(chunks) {
|
|
|
597
597
|
for (let slot = 0; slot < n; slot++) {
|
|
598
598
|
order[storageOrder[slot]] = slot + 1;
|
|
599
599
|
}
|
|
600
|
-
const orderFields = order.map((slot) => positionalField(
|
|
600
|
+
const orderFields = order.map((slot) => positionalField(vmNumberLiteral(slot)));
|
|
601
601
|
return {
|
|
602
602
|
storeVar,
|
|
603
603
|
orderVar,
|
|
@@ -616,7 +616,7 @@ function buildForLoopStringExpr(chunks) {
|
|
|
616
616
|
localStatement(accVar, stringLiteral("")),
|
|
617
617
|
numericForStatement(
|
|
618
618
|
iVar,
|
|
619
|
-
|
|
619
|
+
vmNumberLiteral(1),
|
|
620
620
|
unary("#", identifier(orderVar)),
|
|
621
621
|
block([
|
|
622
622
|
assignmentStatement(
|
|
@@ -648,7 +648,7 @@ function buildRecursiveStringExpr(chunks) {
|
|
|
648
648
|
]),
|
|
649
649
|
returnStatement([
|
|
650
650
|
call(identifier(recName), [
|
|
651
|
-
binary("+", identifier(iParam),
|
|
651
|
+
binary("+", identifier(iParam), vmNumberLiteral(1)),
|
|
652
652
|
binary(
|
|
653
653
|
"..",
|
|
654
654
|
identifier(accParam),
|
|
@@ -660,7 +660,7 @@ function buildRecursiveStringExpr(chunks) {
|
|
|
660
660
|
return iife([
|
|
661
661
|
...stmts,
|
|
662
662
|
localFunctionStatement(recName, functionBody([functionParam(iParam), functionParam(accParam)], body)),
|
|
663
|
-
returnStatement([call(identifier(recName), [
|
|
663
|
+
returnStatement([call(identifier(recName), [vmNumberLiteral(1), stringLiteral("")])])
|
|
664
664
|
]);
|
|
665
665
|
}
|
|
666
666
|
function pickControlFlowStrategy() {
|
|
@@ -1738,9 +1738,10 @@ var DEFAULT_BUILTIN_GLOBALS = [
|
|
|
1738
1738
|
"next"
|
|
1739
1739
|
];
|
|
1740
1740
|
var VmCompiler = class {
|
|
1741
|
-
constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap) {
|
|
1741
|
+
constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap, debug = false) {
|
|
1742
1742
|
this.program = program;
|
|
1743
1743
|
this.opcodeMap = opcodeMap;
|
|
1744
|
+
this.debug = debug;
|
|
1744
1745
|
this.analysis = luauparser.analyzeScopes(program, { builtinGlobals });
|
|
1745
1746
|
this.enclosing = buildEnclosingFunctionMap(program);
|
|
1746
1747
|
for (const [id, binding] of this.analysis.bindings) {
|
|
@@ -1750,11 +1751,29 @@ var VmCompiler = class {
|
|
|
1750
1751
|
}
|
|
1751
1752
|
program;
|
|
1752
1753
|
opcodeMap;
|
|
1754
|
+
debug;
|
|
1753
1755
|
analysis;
|
|
1754
1756
|
enclosing;
|
|
1755
1757
|
captured = /* @__PURE__ */ new Set();
|
|
1756
1758
|
protoIdCounter = 0;
|
|
1757
1759
|
declToBinding = /* @__PURE__ */ new Map();
|
|
1760
|
+
/**
|
|
1761
|
+
* 실제로 프로그램이 참조하는 모든 전역 이름(사전 등록된 builtinGlobals +
|
|
1762
|
+
* 스코프 분석이 자동으로 찾아낸, 로컬/파라미터/업밸류로 안 풀리는 나머지 식별자
|
|
1763
|
+
* 전부)을 돌려준다. Vmify.ts가 이 목록으로 브릿지 테이블(globals)을 만든다.
|
|
1764
|
+
*
|
|
1765
|
+
* 예전엔 호출자가 넘긴 builtinGlobals 목록만 브릿지에 넣었는데, 그 목록에
|
|
1766
|
+
* 없는 전역(예: Roblox API나 실행기 전용 전역을 다 못 채운 경우)을 참조하면
|
|
1767
|
+
* 컴파일은 성공하지만 GETGLOBAL이 조용히 nil을 반환해서 "attempt to call a
|
|
1768
|
+
* nil value"로 터진다 — 게다가 원인이 VM 디스패치 쪽 스택에서만 보여서
|
|
1769
|
+
* "opcode 매핑이 깨졌나?" 하고 엉뚱한 곳을 의심하게 만든다. analyzeScopes는
|
|
1770
|
+
* 어차피 로컬/업밸류로 못 푸는 식별자를 전부 global 바인딩으로 잡아두므로,
|
|
1771
|
+
* builtinGlobals에 없던 이름도 여기 globalsByName에는 다 들어있다 —
|
|
1772
|
+
* 하드코딩된 허용목록 대신 이걸 그대로 쓰면 커버리지 문제 자체가 사라진다.
|
|
1773
|
+
*/
|
|
1774
|
+
getUsedGlobalNames() {
|
|
1775
|
+
return Array.from(this.analysis.globalsByName.keys());
|
|
1776
|
+
}
|
|
1758
1777
|
computeCaptured() {
|
|
1759
1778
|
for (const binding of this.analysis.bindings.values()) {
|
|
1760
1779
|
if (binding.kind === "global") continue;
|
|
@@ -1791,9 +1810,39 @@ var VmCompiler = class {
|
|
|
1791
1810
|
}
|
|
1792
1811
|
emit(state, op, a, b, c, comment) {
|
|
1793
1812
|
const physicalOp = this.opcodeMap ? this.opcodeMap[op] : op;
|
|
1794
|
-
state.proto.code.push({ op: physicalOp, a, b, c, comment });
|
|
1813
|
+
state.proto.code.push({ op: physicalOp, a, b, c, comment, dbgLine: this.debug ? state.curLine : void 0 });
|
|
1795
1814
|
return state.proto.code.length - 1;
|
|
1796
1815
|
}
|
|
1816
|
+
/** debug 빌드: 방금 emit한 CALL 인스트럭션에 "호출 대상 표현식 문자열"과 그 소스 줄을
|
|
1817
|
+
* 박아넣는다(런타임이 non-callable을 잡았을 때 사람이 읽을 위치를 찍게). */
|
|
1818
|
+
stampCall(state, idx, callExpr, calleeExpr) {
|
|
1819
|
+
if (!this.debug) return;
|
|
1820
|
+
const instr = state.proto.code[idx];
|
|
1821
|
+
instr.dbgName = this.describeExpr(calleeExpr);
|
|
1822
|
+
const line = calleeExpr.line ?? callExpr.line;
|
|
1823
|
+
if (line) instr.dbgLine = line.start;
|
|
1824
|
+
}
|
|
1825
|
+
/** 표현식을 사람이 읽을 짧은 문자열로. 위치 추적용이라 완벽할 필요는 없음. */
|
|
1826
|
+
describeExpr(e) {
|
|
1827
|
+
switch (e.type) {
|
|
1828
|
+
case "Identifier":
|
|
1829
|
+
return e.name;
|
|
1830
|
+
case "MemberExpression":
|
|
1831
|
+
return `${this.describeExpr(e.object)}.${e.property.name}`;
|
|
1832
|
+
case "MethodCallExpression":
|
|
1833
|
+
return `${this.describeExpr(e.object)}:${e.method.name}(...)`;
|
|
1834
|
+
case "IndexExpression":
|
|
1835
|
+
return `${this.describeExpr(e.object)}[...]`;
|
|
1836
|
+
case "CallExpression":
|
|
1837
|
+
return `${this.describeExpr(e.callee)}(...)`;
|
|
1838
|
+
case "ParenthesizedExpression":
|
|
1839
|
+
return `(${this.describeExpr(e.expression)})`;
|
|
1840
|
+
case "StringLiteral":
|
|
1841
|
+
return `"${String(e.value).slice(0, 20)}"`;
|
|
1842
|
+
default:
|
|
1843
|
+
return `<${e.type}>`;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1797
1846
|
konst(state, value) {
|
|
1798
1847
|
return internConst(state.proto, value);
|
|
1799
1848
|
}
|
|
@@ -1885,6 +1934,7 @@ var VmCompiler = class {
|
|
|
1885
1934
|
state.regs.releaseLocalsTo(saved);
|
|
1886
1935
|
}
|
|
1887
1936
|
compileStatement(stmt, state) {
|
|
1937
|
+
if (this.debug && stmt.line) state.curLine = stmt.line.start;
|
|
1888
1938
|
switch (stmt.type) {
|
|
1889
1939
|
case "LocalStatement": {
|
|
1890
1940
|
const bindingIds = stmt.names.map((n) => this.bindingIdOfDecl(n));
|
|
@@ -1910,11 +1960,26 @@ var VmCompiler = class {
|
|
|
1910
1960
|
return;
|
|
1911
1961
|
}
|
|
1912
1962
|
case "AssignmentStatement": {
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1963
|
+
const targets = stmt.targets;
|
|
1964
|
+
const values = stmt.values;
|
|
1965
|
+
const base = state.regs.top();
|
|
1966
|
+
if (values.length > 0) {
|
|
1967
|
+
for (let i = 0; i < values.length - 1; i++) {
|
|
1968
|
+
this.compileExprTo(values[i], state, base + i);
|
|
1969
|
+
}
|
|
1970
|
+
const lastIdx = values.length - 1;
|
|
1971
|
+
const remaining = Math.max(targets.length - lastIdx, 1);
|
|
1972
|
+
this.compileExprMultiInto(values[lastIdx], state, base + lastIdx, remaining);
|
|
1973
|
+
}
|
|
1974
|
+
const produced = values.length === 0 ? 0 : values.length - 1 + Math.max(targets.length - (values.length - 1), 1);
|
|
1975
|
+
for (let i = produced; i < targets.length; i++) {
|
|
1976
|
+
this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
|
|
1977
|
+
}
|
|
1978
|
+
for (let i = 0; i < targets.length; i++) {
|
|
1979
|
+
state.regs.freeTemp(base + targets.length);
|
|
1980
|
+
this.compileAssignTarget(targets[i], base + i, state);
|
|
1981
|
+
}
|
|
1982
|
+
state.regs.freeTemp(base);
|
|
1918
1983
|
return;
|
|
1919
1984
|
}
|
|
1920
1985
|
case "CompoundAssignmentStatement":
|
|
@@ -2258,7 +2323,7 @@ var VmCompiler = class {
|
|
|
2258
2323
|
compileCallMultiInto(callExpr, state, wantCount) {
|
|
2259
2324
|
const base = this.compileExpr(callExpr.callee, state);
|
|
2260
2325
|
this.compileArgsContiguous(callExpr.arguments, state, base + 1);
|
|
2261
|
-
this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1);
|
|
2326
|
+
this.stampCall(state, this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1), callExpr, callExpr.callee);
|
|
2262
2327
|
return base;
|
|
2263
2328
|
}
|
|
2264
2329
|
loadConst(state, value) {
|
|
@@ -2309,7 +2374,7 @@ var VmCompiler = class {
|
|
|
2309
2374
|
if (expr.type === "CallExpression") {
|
|
2310
2375
|
const calleeReg = this.compileExpr(expr.callee, state);
|
|
2311
2376
|
const b = this.compileCallArgsAndGetB(expr.arguments, state, calleeReg + 1);
|
|
2312
|
-
this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1);
|
|
2377
|
+
this.stampCall(state, this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1), expr, expr.callee);
|
|
2313
2378
|
for (let i = 0; i < wantCount; i++) {
|
|
2314
2379
|
if (calleeReg + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, calleeReg + i, 0);
|
|
2315
2380
|
}
|
|
@@ -2324,7 +2389,7 @@ var VmCompiler = class {
|
|
|
2324
2389
|
state.regs.allocTemp();
|
|
2325
2390
|
const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
|
|
2326
2391
|
const b = argB === 0 ? 0 : expr.arguments.length + 2;
|
|
2327
|
-
this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1);
|
|
2392
|
+
this.stampCall(state, this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1), expr, expr);
|
|
2328
2393
|
for (let i = 0; i < wantCount; i++) {
|
|
2329
2394
|
if (fnSlot + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, fnSlot + i, 0);
|
|
2330
2395
|
}
|
|
@@ -2350,7 +2415,7 @@ var VmCompiler = class {
|
|
|
2350
2415
|
if (calleeReg !== base) this.emit(state, 0 /* MOVE */, base, calleeReg, 0);
|
|
2351
2416
|
state.regs.freeTemp(base + 1);
|
|
2352
2417
|
const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
|
|
2353
|
-
this.emit(state, 27 /* CALL */, base, b, 0);
|
|
2418
|
+
this.stampCall(state, this.emit(state, 27 /* CALL */, base, b, 0), expr, expr.callee);
|
|
2354
2419
|
return;
|
|
2355
2420
|
}
|
|
2356
2421
|
if (expr.type === "MethodCallExpression") {
|
|
@@ -2366,7 +2431,7 @@ var VmCompiler = class {
|
|
|
2366
2431
|
state.regs.freeTemp(base + 2);
|
|
2367
2432
|
const argB = this.compileCallArgsAndGetB(expr.arguments, state, base + 2);
|
|
2368
2433
|
const b = argB === 0 ? 0 : expr.arguments.length + 2;
|
|
2369
|
-
this.emit(state, 27 /* CALL */, base, b, 0);
|
|
2434
|
+
this.stampCall(state, this.emit(state, 27 /* CALL */, base, b, 0), expr, expr);
|
|
2370
2435
|
return;
|
|
2371
2436
|
}
|
|
2372
2437
|
if (expr.type === "VarargExpression") {
|
|
@@ -2393,6 +2458,7 @@ var VmCompiler = class {
|
|
|
2393
2458
|
return args.length + 1;
|
|
2394
2459
|
}
|
|
2395
2460
|
compileExpr(expr, state, discard = false) {
|
|
2461
|
+
if (this.debug && expr.line) state.curLine = expr.line.start;
|
|
2396
2462
|
switch (expr.type) {
|
|
2397
2463
|
case "NilLiteral": {
|
|
2398
2464
|
const r = state.regs.allocTemp();
|
|
@@ -2550,7 +2616,8 @@ var VmCompiler = class {
|
|
|
2550
2616
|
case "CallExpression": {
|
|
2551
2617
|
const base = this.compileExpr(expr.callee, state);
|
|
2552
2618
|
const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
|
|
2553
|
-
this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
|
|
2619
|
+
const callIdx = this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
|
|
2620
|
+
this.stampCall(state, callIdx, expr, expr.callee);
|
|
2554
2621
|
state.regs.freeTemp(base + (discard ? 0 : 1));
|
|
2555
2622
|
return base;
|
|
2556
2623
|
}
|
|
@@ -2562,7 +2629,8 @@ var VmCompiler = class {
|
|
|
2562
2629
|
state.regs.allocTemp();
|
|
2563
2630
|
const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
|
|
2564
2631
|
const b = argB === 0 ? 0 : expr.arguments.length + 2;
|
|
2565
|
-
this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
|
|
2632
|
+
const callIdx = this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
|
|
2633
|
+
this.stampCall(state, callIdx, expr, expr);
|
|
2566
2634
|
state.regs.freeTemp(fnSlot + (discard ? 0 : 1));
|
|
2567
2635
|
return fnSlot;
|
|
2568
2636
|
}
|
|
@@ -2675,15 +2743,20 @@ function serializeConstValue(v) {
|
|
|
2675
2743
|
if (typeof v === "number") return numberLiteral(v);
|
|
2676
2744
|
return booleanLiteral(v);
|
|
2677
2745
|
}
|
|
2678
|
-
function serializeInstrs(proto) {
|
|
2679
|
-
const fields = proto.code.map((instr) =>
|
|
2680
|
-
|
|
2746
|
+
function serializeInstrs(proto, debug) {
|
|
2747
|
+
const fields = proto.code.map((instr) => {
|
|
2748
|
+
const tuple = [
|
|
2681
2749
|
positionalField(vmNumberLiteral(instr.op)),
|
|
2682
2750
|
positionalField(vmNumberLiteral(instr.a)),
|
|
2683
2751
|
positionalField(vmNumberLiteral(instr.b)),
|
|
2684
2752
|
positionalField(vmNumberLiteral(instr.c))
|
|
2685
|
-
]
|
|
2686
|
-
|
|
2753
|
+
];
|
|
2754
|
+
if (debug) {
|
|
2755
|
+
tuple.push(positionalField(numberLiteral(instr.dbgLine ?? 0)));
|
|
2756
|
+
tuple.push(positionalField(stringLiteral(instr.dbgName ?? "")));
|
|
2757
|
+
}
|
|
2758
|
+
return positionalField(table(tuple));
|
|
2759
|
+
});
|
|
2687
2760
|
return table(fields);
|
|
2688
2761
|
}
|
|
2689
2762
|
function serializeUpvalDescs(proto, names) {
|
|
@@ -2695,29 +2768,41 @@ function serializeUpvalDescs(proto, names) {
|
|
|
2695
2768
|
));
|
|
2696
2769
|
return table(fields);
|
|
2697
2770
|
}
|
|
2698
|
-
function serializeProto(proto, names) {
|
|
2771
|
+
function serializeProto(proto, names, debug = false) {
|
|
2699
2772
|
const fields = [
|
|
2700
2773
|
namedField(names.numParams, vmNumberLiteral(proto.numParams)),
|
|
2701
2774
|
namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
|
|
2702
2775
|
namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
|
|
2703
|
-
namedField(names.code, serializeInstrs(proto)),
|
|
2776
|
+
namedField(names.code, serializeInstrs(proto, debug)),
|
|
2704
2777
|
namedField(names.consts, serializeConsts(proto.consts)),
|
|
2705
2778
|
namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
|
|
2706
|
-
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
|
|
2779
|
+
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names, debug)))))
|
|
2707
2780
|
];
|
|
2708
2781
|
return table(fields);
|
|
2709
2782
|
}
|
|
2710
2783
|
|
|
2711
2784
|
// src/passes/vmify/runtime.ts
|
|
2712
|
-
function buildVmRuntimeSource(names, opcodeMap) {
|
|
2785
|
+
function buildVmRuntimeSource(names, opcodeMap, debug = false) {
|
|
2713
2786
|
const N = names;
|
|
2714
2787
|
const op = (o) => opcodeMap[o];
|
|
2715
2788
|
return `
|
|
2716
2789
|
local ${N.handlers} = {}
|
|
2717
2790
|
|
|
2791
|
+
-- \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
|
|
2792
|
+
-- \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
|
|
2793
|
+
-- \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
|
|
2794
|
+
-- \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.
|
|
2718
2795
|
local function ${N.dispatch}(${N.frame}, pc)
|
|
2719
|
-
local
|
|
2720
|
-
|
|
2796
|
+
local ${N.handlers} = ${N.handlers}
|
|
2797
|
+
while true do
|
|
2798
|
+
local instr = ${N.frame}.${N.code}[pc]
|
|
2799
|
+
local next = ${N.handlers}[instr[1]](${N.frame}, pc, instr)
|
|
2800
|
+
if type(next) == "number" then
|
|
2801
|
+
pc = next
|
|
2802
|
+
else
|
|
2803
|
+
return table.unpack(next, 1, next.n)
|
|
2804
|
+
end
|
|
2805
|
+
end
|
|
2721
2806
|
end
|
|
2722
2807
|
|
|
2723
2808
|
local function ${N.rk}(${N.frame}, x)
|
|
@@ -2731,67 +2816,67 @@ end
|
|
|
2731
2816
|
-- MOVE
|
|
2732
2817
|
${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
|
|
2733
2818
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
|
|
2734
|
-
return
|
|
2819
|
+
return pc + 1
|
|
2735
2820
|
end
|
|
2736
2821
|
|
|
2737
2822
|
-- LOADK
|
|
2738
2823
|
${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
|
|
2739
2824
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
|
|
2740
|
-
return
|
|
2825
|
+
return pc + 1
|
|
2741
2826
|
end
|
|
2742
2827
|
|
|
2743
2828
|
-- LOADBOOL
|
|
2744
2829
|
${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
|
|
2745
2830
|
${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
|
|
2746
|
-
return
|
|
2831
|
+
return pc + 1
|
|
2747
2832
|
end
|
|
2748
2833
|
|
|
2749
2834
|
-- LOADNIL
|
|
2750
2835
|
${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
|
|
2751
2836
|
for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
|
|
2752
|
-
return
|
|
2837
|
+
return pc + 1
|
|
2753
2838
|
end
|
|
2754
2839
|
|
|
2755
2840
|
-- GETUPVAL
|
|
2756
2841
|
${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
|
|
2757
2842
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
|
|
2758
|
-
return
|
|
2843
|
+
return pc + 1
|
|
2759
2844
|
end
|
|
2760
2845
|
|
|
2761
2846
|
-- SETUPVAL
|
|
2762
2847
|
${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
|
|
2763
2848
|
${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
|
|
2764
|
-
return
|
|
2849
|
+
return pc + 1
|
|
2765
2850
|
end
|
|
2766
2851
|
|
|
2767
2852
|
-- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
|
|
2768
2853
|
${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
|
|
2769
2854
|
${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
|
|
2770
|
-
return
|
|
2855
|
+
return pc + 1
|
|
2771
2856
|
end
|
|
2772
2857
|
|
|
2773
2858
|
-- SETGLOBAL
|
|
2774
2859
|
${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
|
|
2775
2860
|
${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
|
|
2776
|
-
return
|
|
2861
|
+
return pc + 1
|
|
2777
2862
|
end
|
|
2778
2863
|
|
|
2779
2864
|
-- GETTABLE
|
|
2780
2865
|
${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
|
|
2781
2866
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
|
|
2782
|
-
return
|
|
2867
|
+
return pc + 1
|
|
2783
2868
|
end
|
|
2784
2869
|
|
|
2785
2870
|
-- SETTABLE
|
|
2786
2871
|
${N.handlers}[${op(9 /* SETTABLE */)}] = function(${N.frame}, pc, instr)
|
|
2787
2872
|
${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
|
|
2788
|
-
return
|
|
2873
|
+
return pc + 1
|
|
2789
2874
|
end
|
|
2790
2875
|
|
|
2791
2876
|
-- NEWTABLE
|
|
2792
2877
|
${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
|
|
2793
2878
|
${N.frame}.${N.R}[instr[2]] = {}
|
|
2794
|
-
return
|
|
2879
|
+
return pc + 1
|
|
2795
2880
|
end
|
|
2796
2881
|
|
|
2797
2882
|
-- SELF (R[a] = R[b][key]; R[a+1] = R[b])
|
|
@@ -2799,90 +2884,90 @@ ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
|
|
|
2799
2884
|
local obj = ${N.frame}.${N.R}[instr[3]]
|
|
2800
2885
|
${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
|
|
2801
2886
|
${N.frame}.${N.R}[instr[2] + 1] = obj
|
|
2802
|
-
return
|
|
2887
|
+
return pc + 1
|
|
2803
2888
|
end
|
|
2804
2889
|
|
|
2805
2890
|
-- ADD SUB MUL DIV MOD POW
|
|
2806
2891
|
${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
|
|
2807
2892
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
|
|
2808
|
-
return
|
|
2893
|
+
return pc + 1
|
|
2809
2894
|
end
|
|
2810
2895
|
${N.handlers}[${op(13 /* SUB */)}] = function(${N.frame}, pc, instr)
|
|
2811
2896
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
|
|
2812
|
-
return
|
|
2897
|
+
return pc + 1
|
|
2813
2898
|
end
|
|
2814
2899
|
${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
|
|
2815
2900
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
|
|
2816
|
-
return
|
|
2901
|
+
return pc + 1
|
|
2817
2902
|
end
|
|
2818
2903
|
${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
|
|
2819
2904
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
|
|
2820
|
-
return
|
|
2905
|
+
return pc + 1
|
|
2821
2906
|
end
|
|
2822
2907
|
${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
|
|
2823
2908
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
|
|
2824
|
-
return
|
|
2909
|
+
return pc + 1
|
|
2825
2910
|
end
|
|
2826
2911
|
${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
|
|
2827
2912
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
|
|
2828
|
-
return
|
|
2913
|
+
return pc + 1
|
|
2829
2914
|
end
|
|
2830
2915
|
|
|
2831
2916
|
-- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
|
|
2832
2917
|
${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
|
|
2833
2918
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
|
|
2834
|
-
return
|
|
2919
|
+
return pc + 1
|
|
2835
2920
|
end
|
|
2836
2921
|
|
|
2837
2922
|
-- CONCAT
|
|
2838
2923
|
${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
|
|
2839
2924
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
|
|
2840
|
-
return
|
|
2925
|
+
return pc + 1
|
|
2841
2926
|
end
|
|
2842
2927
|
|
|
2843
2928
|
-- UNM
|
|
2844
2929
|
${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
|
|
2845
2930
|
${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
|
|
2846
|
-
return
|
|
2931
|
+
return pc + 1
|
|
2847
2932
|
end
|
|
2848
2933
|
|
|
2849
2934
|
-- NOT
|
|
2850
2935
|
${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
|
|
2851
2936
|
${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
|
|
2852
|
-
return
|
|
2937
|
+
return pc + 1
|
|
2853
2938
|
end
|
|
2854
2939
|
|
|
2855
2940
|
-- LEN
|
|
2856
2941
|
${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
|
|
2857
2942
|
${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
|
|
2858
|
-
return
|
|
2943
|
+
return pc + 1
|
|
2859
2944
|
end
|
|
2860
2945
|
|
|
2861
2946
|
-- JMP (pc += a)
|
|
2862
2947
|
${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
|
|
2863
|
-
return
|
|
2948
|
+
return pc + instr[2]
|
|
2864
2949
|
end
|
|
2865
2950
|
|
|
2866
2951
|
-- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
|
|
2867
2952
|
${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
|
|
2868
2953
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
|
|
2869
|
-
return
|
|
2954
|
+
return pc + 1
|
|
2870
2955
|
end
|
|
2871
2956
|
${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
|
|
2872
2957
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
|
|
2873
|
-
return
|
|
2958
|
+
return pc + 1
|
|
2874
2959
|
end
|
|
2875
2960
|
${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
|
|
2876
2961
|
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
|
|
2877
|
-
return
|
|
2962
|
+
return pc + 1
|
|
2878
2963
|
end
|
|
2879
2964
|
|
|
2880
2965
|
-- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
|
|
2881
2966
|
${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
|
|
2882
2967
|
if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
|
|
2883
|
-
return
|
|
2968
|
+
return pc + 2
|
|
2884
2969
|
end
|
|
2885
|
-
return
|
|
2970
|
+
return pc + 1
|
|
2886
2971
|
end
|
|
2887
2972
|
|
|
2888
2973
|
-- CALL
|
|
@@ -2897,7 +2982,13 @@ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
|
|
|
2897
2982
|
end
|
|
2898
2983
|
local args = table.create(nargs)
|
|
2899
2984
|
for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
|
|
2900
|
-
|
|
2985
|
+
${debug ? ` if type(fn) ~= "function" then
|
|
2986
|
+
local mt = getmetatable(fn)
|
|
2987
|
+
if not (mt and mt.__call) then
|
|
2988
|
+
error(("[vmdebug] attempt to call a %s value: '%s' (source line %d)"):format(type(fn), tostring(instr[6]), tonumber(instr[5]) or -1), 0)
|
|
2989
|
+
end
|
|
2990
|
+
end
|
|
2991
|
+
` : ""} local rets = table.pack(fn(table.unpack(args, 1, nargs)))
|
|
2901
2992
|
if c == 1 then
|
|
2902
2993
|
-- discard
|
|
2903
2994
|
elseif c == 0 then
|
|
@@ -2907,23 +2998,27 @@ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
|
|
|
2907
2998
|
local want = c - 1
|
|
2908
2999
|
for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
|
|
2909
3000
|
end
|
|
2910
|
-
return
|
|
3001
|
+
return pc + 1
|
|
2911
3002
|
end
|
|
2912
3003
|
|
|
2913
3004
|
-- RETURN
|
|
3005
|
+
-- \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
|
|
3006
|
+
-- \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
|
|
3007
|
+
-- 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
|
|
3008
|
+
-- \uC911\uAC04 nil\uC774 \uC788\uC5B4\uB3C4 \uBC18\uD658 \uAC1C\uC218\uAC00 \uC815\uD655\uD788 \uBCF4\uC874\uB41C\uB2E4.
|
|
2914
3009
|
${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
|
|
2915
3010
|
local a, b = instr[2], instr[3]
|
|
2916
3011
|
if b == 0 then
|
|
2917
|
-
return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
|
|
3012
|
+
return table.pack(table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1))
|
|
2918
3013
|
end
|
|
2919
|
-
return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
|
|
3014
|
+
return table.pack(table.unpack(${N.frame}.${N.R}, a, a + b - 2))
|
|
2920
3015
|
end
|
|
2921
3016
|
|
|
2922
3017
|
-- FORPREP
|
|
2923
3018
|
${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
|
|
2924
3019
|
local a = instr[2]
|
|
2925
3020
|
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
|
|
2926
|
-
return
|
|
3021
|
+
return pc + instr[3]
|
|
2927
3022
|
end
|
|
2928
3023
|
|
|
2929
3024
|
-- FORLOOP
|
|
@@ -2939,9 +3034,9 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
|
|
|
2939
3034
|
end
|
|
2940
3035
|
if ok then
|
|
2941
3036
|
${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
|
|
2942
|
-
return
|
|
3037
|
+
return pc + instr[3]
|
|
2943
3038
|
end
|
|
2944
|
-
return
|
|
3039
|
+
return pc + 1
|
|
2945
3040
|
end
|
|
2946
3041
|
|
|
2947
3042
|
-- CLOSURE
|
|
@@ -2961,7 +3056,7 @@ ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
|
|
|
2961
3056
|
${N.frame}.${N.R}[instr[2]] = function(...)
|
|
2962
3057
|
return ${N.execute}(childProto, capturedUpvals, ...)
|
|
2963
3058
|
end
|
|
2964
|
-
return
|
|
3059
|
+
return pc + 1 + n
|
|
2965
3060
|
end
|
|
2966
3061
|
|
|
2967
3062
|
-- VARARG
|
|
@@ -2973,7 +3068,7 @@ ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
|
|
|
2973
3068
|
if instr[3] == 0 then
|
|
2974
3069
|
${N.frame}.${N.multiTop} = instr[2] + count
|
|
2975
3070
|
end
|
|
2976
|
-
return
|
|
3071
|
+
return pc + 1
|
|
2977
3072
|
end
|
|
2978
3073
|
|
|
2979
3074
|
-- SETLIST
|
|
@@ -2986,7 +3081,7 @@ ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
|
|
|
2986
3081
|
for i = 0, n - 1 do
|
|
2987
3082
|
tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
|
|
2988
3083
|
end
|
|
2989
|
-
return
|
|
3084
|
+
return pc + 1
|
|
2990
3085
|
end
|
|
2991
3086
|
|
|
2992
3087
|
function ${N.execute}(proto, upvals, ...)
|
|
@@ -3073,20 +3168,22 @@ function markRuntimeNumbersAsStructural(body) {
|
|
|
3073
3168
|
});
|
|
3074
3169
|
}
|
|
3075
3170
|
function runVmify(program, options) {
|
|
3076
|
-
const
|
|
3171
|
+
const builtinGlobals = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
|
|
3077
3172
|
const random = options.random ?? Math.random;
|
|
3173
|
+
const debug = options.debug ?? false;
|
|
3078
3174
|
const opcodeMap = createOpcodeMap(random);
|
|
3079
3175
|
const names = generateVmNames(random);
|
|
3080
|
-
const compiler = new VmCompiler(program,
|
|
3176
|
+
const compiler = new VmCompiler(program, builtinGlobals, opcodeMap, debug);
|
|
3081
3177
|
const topProto = compiler.compile();
|
|
3178
|
+
const usedGlobalNames = compiler.getUsedGlobalNames();
|
|
3082
3179
|
const globalsTable = table(
|
|
3083
|
-
|
|
3180
|
+
usedGlobalNames.map((name) => namedField(name, identifier(name)))
|
|
3084
3181
|
);
|
|
3085
|
-
const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
|
|
3182
|
+
const runtimeSource = buildVmRuntimeSource(names, opcodeMap, debug);
|
|
3086
3183
|
const runtimeProgram = luauparser2.parse(runtimeSource);
|
|
3087
3184
|
const runtimeStatements = runtimeProgram.body.statements;
|
|
3088
3185
|
markRuntimeNumbersAsStructural(runtimeProgram.body);
|
|
3089
|
-
const protoLiteral = serializeProto(topProto, names);
|
|
3186
|
+
const protoLiteral = serializeProto(topProto, names, debug);
|
|
3090
3187
|
const newBody = [
|
|
3091
3188
|
localStatement(names.globals, globalsTable),
|
|
3092
3189
|
...runtimeStatements,
|