luau-obfuscator 1.0.0 → 1.0.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.
- package/.github/workflows/release.yml +56 -56
- package/dist/index.cjs +2186 -95
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +2186 -95
- package/generated/grammers.luau +13643 -0
- package/generated/tests.luau +5567 -0
- package/package.json +2 -2
- package/scripts/test.js +24 -8
- package/{scripts/example.luau → smoketest/grammers.luau} +1174 -1174
- package/smoketest/tests.luau +115 -0
- package/src/config.ts +2 -1
- package/src/passes/ConstantArray.ts +90 -89
- package/src/passes/EncryptNumbers.ts +122 -65
- package/src/passes/EncryptStrings.ts +162 -82
- package/src/passes/GlobalMapping.ts +193 -193
- package/src/passes/InsertJunk.ts +184 -184
- package/src/passes/Minify.ts +5 -5
- package/src/passes/NumbersToExpressions.ts +183 -2
- package/src/passes/RenameVariables.ts +2 -9
- package/src/passes/StringsToExpressions.ts +195 -6
- package/src/passes/StripTypes.ts +191 -191
- package/src/passes/Vmify.ts +87 -0
- package/src/passes/WrapInFunction.ts +31 -31
- package/src/passes/nodeFactory.ts +36 -1
- package/src/passes/vmify/chunk.ts +54 -0
- package/src/passes/vmify/compiler.ts +1026 -0
- package/src/passes/vmify/names.ts +72 -0
- package/src/passes/vmify/opcodes.ts +80 -0
- package/src/passes/vmify/registers.ts +63 -0
- package/src/passes/vmify/runtime.ts +334 -0
- package/src/passes/vmify/scope-walk.ts +175 -0
- package/src/passes/vmify/serialize.ts +68 -0
- package/src/passes/walk.ts +141 -43
- package/src/pipeline.ts +5 -6
- package/tsconfig.json +20 -20
- package/tsup.config.ts +9 -9
- package/generated/final.luau +0 -1029
package/src/passes/InsertJunk.ts
CHANGED
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Program, Block, Statement, Expression,
|
|
3
|
-
} from "luau-parser"
|
|
4
|
-
import {
|
|
5
|
-
identifier, numberLiteral, binary, localStatement, doStatement, block,
|
|
6
|
-
ifStatement, ifClause,
|
|
7
|
-
} from "./nodeFactory"
|
|
8
|
-
|
|
9
|
-
export interface InsertJunkOptions {
|
|
10
|
-
/** 기존 statement 하나 앞에 junk를 끼워넣을 확률 (0~1) */
|
|
11
|
-
probability: number
|
|
12
|
-
/** 블록 하나당 최대 삽입 개수 */
|
|
13
|
-
maxPerBlock: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function randomInt(min: number, max: number): number {
|
|
17
|
-
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function randomName(): string {
|
|
21
|
-
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "")
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function junkNumberExpr(): Expression {
|
|
25
|
-
const op = Math.random() < 0.5 ? "+" : "*"
|
|
26
|
-
return binary(op, numberLiteral(randomInt(1, 999)), numberLiteral(randomInt(1, 999)))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 실행돼도 관찰 가능한 부작용이 전혀 없는 더미 statement 하나를 만듦.
|
|
31
|
-
* - 매번 새 랜덤 이름을 쓰므로 다른 변수와 충돌하지 않음
|
|
32
|
-
* - if 분기 조건이 실제로 참/거짓 어느 쪽이어도 몸통이 쓸모없는 local 선언뿐이라
|
|
33
|
-
* 상관없음 (opaque predicate를 엄밀하게 항상 거짓으로 만들 필요가 없음)
|
|
34
|
-
*/
|
|
35
|
-
function buildJunkStatement(): Statement {
|
|
36
|
-
const variants: Array<() => Statement> = [
|
|
37
|
-
() => localStatement(randomName(), junkNumberExpr()),
|
|
38
|
-
() => doStatement(block([localStatement(randomName(), junkNumberExpr())])),
|
|
39
|
-
() => ifStatement([
|
|
40
|
-
ifClause(
|
|
41
|
-
binary("==", numberLiteral(randomInt(1, 999)), numberLiteral(randomInt(1, 999))),
|
|
42
|
-
block([localStatement(randomName(), junkNumberExpr())]),
|
|
43
|
-
),
|
|
44
|
-
]),
|
|
45
|
-
]
|
|
46
|
-
return variants[randomInt(0, variants.length - 1)]()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function runInsertJunk(program: Program, options: InsertJunkOptions): void {
|
|
50
|
-
function processExpr(expr: Expression): void {
|
|
51
|
-
switch (expr.type) {
|
|
52
|
-
case "InterpolatedStringExpression":
|
|
53
|
-
for (const part of expr.parts) {
|
|
54
|
-
if (part.kind === "expression") processExpr(part.expression)
|
|
55
|
-
}
|
|
56
|
-
return
|
|
57
|
-
case "FunctionExpression":
|
|
58
|
-
processBlock(expr.func.body)
|
|
59
|
-
return
|
|
60
|
-
case "TableExpression":
|
|
61
|
-
for (const field of expr.fields) {
|
|
62
|
-
if (field.type === "TableFieldPositional") processExpr(field.value)
|
|
63
|
-
else if (field.type === "TableFieldNamed") processExpr(field.value)
|
|
64
|
-
else { processExpr(field.key); processExpr(field.value) }
|
|
65
|
-
}
|
|
66
|
-
return
|
|
67
|
-
case "BinaryExpression":
|
|
68
|
-
processExpr(expr.left)
|
|
69
|
-
processExpr(expr.right)
|
|
70
|
-
return
|
|
71
|
-
case "UnaryExpression":
|
|
72
|
-
processExpr(expr.argument)
|
|
73
|
-
return
|
|
74
|
-
case "MemberExpression":
|
|
75
|
-
processExpr(expr.object)
|
|
76
|
-
return
|
|
77
|
-
case "IndexExpression":
|
|
78
|
-
processExpr(expr.object)
|
|
79
|
-
processExpr(expr.index)
|
|
80
|
-
return
|
|
81
|
-
case "CallExpression":
|
|
82
|
-
processExpr(expr.callee)
|
|
83
|
-
expr.arguments.forEach(processExpr)
|
|
84
|
-
return
|
|
85
|
-
case "MethodCallExpression":
|
|
86
|
-
processExpr(expr.object)
|
|
87
|
-
expr.arguments.forEach(processExpr)
|
|
88
|
-
return
|
|
89
|
-
case "ParenthesizedExpression":
|
|
90
|
-
processExpr(expr.expression)
|
|
91
|
-
return
|
|
92
|
-
case "TypeAssertionExpression":
|
|
93
|
-
processExpr(expr.expression)
|
|
94
|
-
return
|
|
95
|
-
case "IfElseExpression":
|
|
96
|
-
for (const clause of expr.clauses) {
|
|
97
|
-
processExpr(clause.condition)
|
|
98
|
-
processExpr(clause.body)
|
|
99
|
-
}
|
|
100
|
-
processExpr(expr.alternate)
|
|
101
|
-
return
|
|
102
|
-
default:
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function processStatement(stmt: Statement): void {
|
|
108
|
-
switch (stmt.type) {
|
|
109
|
-
case "LocalStatement":
|
|
110
|
-
stmt.init.forEach(processExpr)
|
|
111
|
-
return
|
|
112
|
-
case "LocalFunctionStatement":
|
|
113
|
-
processBlock(stmt.func.body)
|
|
114
|
-
return
|
|
115
|
-
case "FunctionDeclarationStatement":
|
|
116
|
-
processBlock(stmt.func.body)
|
|
117
|
-
return
|
|
118
|
-
case "AssignmentStatement":
|
|
119
|
-
stmt.targets.forEach(processExpr)
|
|
120
|
-
stmt.values.forEach(processExpr)
|
|
121
|
-
return
|
|
122
|
-
case "CompoundAssignmentStatement":
|
|
123
|
-
processExpr(stmt.target)
|
|
124
|
-
processExpr(stmt.value)
|
|
125
|
-
return
|
|
126
|
-
case "CallStatement":
|
|
127
|
-
processExpr(stmt.expression)
|
|
128
|
-
return
|
|
129
|
-
case "DoStatement":
|
|
130
|
-
processBlock(stmt.body)
|
|
131
|
-
return
|
|
132
|
-
case "WhileStatement":
|
|
133
|
-
processExpr(stmt.condition)
|
|
134
|
-
processBlock(stmt.body)
|
|
135
|
-
return
|
|
136
|
-
case "RepeatStatement":
|
|
137
|
-
processBlock(stmt.body)
|
|
138
|
-
processExpr(stmt.condition)
|
|
139
|
-
return
|
|
140
|
-
case "IfStatement":
|
|
141
|
-
for (const clause of stmt.clauses) {
|
|
142
|
-
processExpr(clause.condition)
|
|
143
|
-
processBlock(clause.body)
|
|
144
|
-
}
|
|
145
|
-
if (stmt.alternate) processBlock(stmt.alternate)
|
|
146
|
-
return
|
|
147
|
-
case "NumericForStatement":
|
|
148
|
-
processExpr(stmt.start)
|
|
149
|
-
processExpr(stmt.end)
|
|
150
|
-
if (stmt.step) processExpr(stmt.step)
|
|
151
|
-
processBlock(stmt.body)
|
|
152
|
-
return
|
|
153
|
-
case "GenericForStatement":
|
|
154
|
-
stmt.iterators.forEach(processExpr)
|
|
155
|
-
processBlock(stmt.body)
|
|
156
|
-
return
|
|
157
|
-
case "ReturnStatement":
|
|
158
|
-
stmt.arguments.forEach(processExpr)
|
|
159
|
-
return
|
|
160
|
-
default:
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** 자식 블록들부터 먼저 처리한 뒤(재귀 무한 방지 — 새로 끼워넣는 junk는
|
|
166
|
-
* 다시 처리하지 않음), 현재 블록에 junk를 끼워넣음. junk는 기존 statement
|
|
167
|
-
* "앞"에만 넣으므로 return/break/continue(항상 블록 맨 끝에만 올 수 있음)
|
|
168
|
-
* 뒤에 잘못 삽입될 일이 없음. */
|
|
169
|
-
function processBlock(blk: Block): void {
|
|
170
|
-
for (const stmt of blk.statements) processStatement(stmt)
|
|
171
|
-
|
|
172
|
-
const result: Statement[] = []
|
|
173
|
-
let inserted = 0
|
|
174
|
-
for (const stmt of blk.statements) {
|
|
175
|
-
if (inserted < options.maxPerBlock && Math.random() < options.probability) {
|
|
176
|
-
result.push(buildJunkStatement())
|
|
177
|
-
inserted++
|
|
178
|
-
}
|
|
179
|
-
result.push(stmt)
|
|
180
|
-
}
|
|
181
|
-
blk.statements = result
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
processBlock(program.body)
|
|
1
|
+
import type {
|
|
2
|
+
Program, Block, Statement, Expression,
|
|
3
|
+
} from "luau-parser"
|
|
4
|
+
import {
|
|
5
|
+
identifier, numberLiteral, binary, localStatement, doStatement, block,
|
|
6
|
+
ifStatement, ifClause,
|
|
7
|
+
} from "./nodeFactory"
|
|
8
|
+
|
|
9
|
+
export interface InsertJunkOptions {
|
|
10
|
+
/** 기존 statement 하나 앞에 junk를 끼워넣을 확률 (0~1) */
|
|
11
|
+
probability: number
|
|
12
|
+
/** 블록 하나당 최대 삽입 개수 */
|
|
13
|
+
maxPerBlock: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function randomInt(min: number, max: number): number {
|
|
17
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function randomName(): string {
|
|
21
|
+
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function junkNumberExpr(): Expression {
|
|
25
|
+
const op = Math.random() < 0.5 ? "+" : "*"
|
|
26
|
+
return binary(op, numberLiteral(randomInt(1, 999)), numberLiteral(randomInt(1, 999)))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 실행돼도 관찰 가능한 부작용이 전혀 없는 더미 statement 하나를 만듦.
|
|
31
|
+
* - 매번 새 랜덤 이름을 쓰므로 다른 변수와 충돌하지 않음
|
|
32
|
+
* - if 분기 조건이 실제로 참/거짓 어느 쪽이어도 몸통이 쓸모없는 local 선언뿐이라
|
|
33
|
+
* 상관없음 (opaque predicate를 엄밀하게 항상 거짓으로 만들 필요가 없음)
|
|
34
|
+
*/
|
|
35
|
+
function buildJunkStatement(): Statement {
|
|
36
|
+
const variants: Array<() => Statement> = [
|
|
37
|
+
() => localStatement(randomName(), junkNumberExpr()),
|
|
38
|
+
() => doStatement(block([localStatement(randomName(), junkNumberExpr())])),
|
|
39
|
+
() => ifStatement([
|
|
40
|
+
ifClause(
|
|
41
|
+
binary("==", numberLiteral(randomInt(1, 999)), numberLiteral(randomInt(1, 999))),
|
|
42
|
+
block([localStatement(randomName(), junkNumberExpr())]),
|
|
43
|
+
),
|
|
44
|
+
]),
|
|
45
|
+
]
|
|
46
|
+
return variants[randomInt(0, variants.length - 1)]()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function runInsertJunk(program: Program, options: InsertJunkOptions): void {
|
|
50
|
+
function processExpr(expr: Expression): void {
|
|
51
|
+
switch (expr.type) {
|
|
52
|
+
case "InterpolatedStringExpression":
|
|
53
|
+
for (const part of expr.parts) {
|
|
54
|
+
if (part.kind === "expression") processExpr(part.expression)
|
|
55
|
+
}
|
|
56
|
+
return
|
|
57
|
+
case "FunctionExpression":
|
|
58
|
+
processBlock(expr.func.body)
|
|
59
|
+
return
|
|
60
|
+
case "TableExpression":
|
|
61
|
+
for (const field of expr.fields) {
|
|
62
|
+
if (field.type === "TableFieldPositional") processExpr(field.value)
|
|
63
|
+
else if (field.type === "TableFieldNamed") processExpr(field.value)
|
|
64
|
+
else { processExpr(field.key); processExpr(field.value) }
|
|
65
|
+
}
|
|
66
|
+
return
|
|
67
|
+
case "BinaryExpression":
|
|
68
|
+
processExpr(expr.left)
|
|
69
|
+
processExpr(expr.right)
|
|
70
|
+
return
|
|
71
|
+
case "UnaryExpression":
|
|
72
|
+
processExpr(expr.argument)
|
|
73
|
+
return
|
|
74
|
+
case "MemberExpression":
|
|
75
|
+
processExpr(expr.object)
|
|
76
|
+
return
|
|
77
|
+
case "IndexExpression":
|
|
78
|
+
processExpr(expr.object)
|
|
79
|
+
processExpr(expr.index)
|
|
80
|
+
return
|
|
81
|
+
case "CallExpression":
|
|
82
|
+
processExpr(expr.callee)
|
|
83
|
+
expr.arguments.forEach(processExpr)
|
|
84
|
+
return
|
|
85
|
+
case "MethodCallExpression":
|
|
86
|
+
processExpr(expr.object)
|
|
87
|
+
expr.arguments.forEach(processExpr)
|
|
88
|
+
return
|
|
89
|
+
case "ParenthesizedExpression":
|
|
90
|
+
processExpr(expr.expression)
|
|
91
|
+
return
|
|
92
|
+
case "TypeAssertionExpression":
|
|
93
|
+
processExpr(expr.expression)
|
|
94
|
+
return
|
|
95
|
+
case "IfElseExpression":
|
|
96
|
+
for (const clause of expr.clauses) {
|
|
97
|
+
processExpr(clause.condition)
|
|
98
|
+
processExpr(clause.body)
|
|
99
|
+
}
|
|
100
|
+
processExpr(expr.alternate)
|
|
101
|
+
return
|
|
102
|
+
default:
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function processStatement(stmt: Statement): void {
|
|
108
|
+
switch (stmt.type) {
|
|
109
|
+
case "LocalStatement":
|
|
110
|
+
stmt.init.forEach(processExpr)
|
|
111
|
+
return
|
|
112
|
+
case "LocalFunctionStatement":
|
|
113
|
+
processBlock(stmt.func.body)
|
|
114
|
+
return
|
|
115
|
+
case "FunctionDeclarationStatement":
|
|
116
|
+
processBlock(stmt.func.body)
|
|
117
|
+
return
|
|
118
|
+
case "AssignmentStatement":
|
|
119
|
+
stmt.targets.forEach(processExpr)
|
|
120
|
+
stmt.values.forEach(processExpr)
|
|
121
|
+
return
|
|
122
|
+
case "CompoundAssignmentStatement":
|
|
123
|
+
processExpr(stmt.target)
|
|
124
|
+
processExpr(stmt.value)
|
|
125
|
+
return
|
|
126
|
+
case "CallStatement":
|
|
127
|
+
processExpr(stmt.expression)
|
|
128
|
+
return
|
|
129
|
+
case "DoStatement":
|
|
130
|
+
processBlock(stmt.body)
|
|
131
|
+
return
|
|
132
|
+
case "WhileStatement":
|
|
133
|
+
processExpr(stmt.condition)
|
|
134
|
+
processBlock(stmt.body)
|
|
135
|
+
return
|
|
136
|
+
case "RepeatStatement":
|
|
137
|
+
processBlock(stmt.body)
|
|
138
|
+
processExpr(stmt.condition)
|
|
139
|
+
return
|
|
140
|
+
case "IfStatement":
|
|
141
|
+
for (const clause of stmt.clauses) {
|
|
142
|
+
processExpr(clause.condition)
|
|
143
|
+
processBlock(clause.body)
|
|
144
|
+
}
|
|
145
|
+
if (stmt.alternate) processBlock(stmt.alternate)
|
|
146
|
+
return
|
|
147
|
+
case "NumericForStatement":
|
|
148
|
+
processExpr(stmt.start)
|
|
149
|
+
processExpr(stmt.end)
|
|
150
|
+
if (stmt.step) processExpr(stmt.step)
|
|
151
|
+
processBlock(stmt.body)
|
|
152
|
+
return
|
|
153
|
+
case "GenericForStatement":
|
|
154
|
+
stmt.iterators.forEach(processExpr)
|
|
155
|
+
processBlock(stmt.body)
|
|
156
|
+
return
|
|
157
|
+
case "ReturnStatement":
|
|
158
|
+
stmt.arguments.forEach(processExpr)
|
|
159
|
+
return
|
|
160
|
+
default:
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 자식 블록들부터 먼저 처리한 뒤(재귀 무한 방지 — 새로 끼워넣는 junk는
|
|
166
|
+
* 다시 처리하지 않음), 현재 블록에 junk를 끼워넣음. junk는 기존 statement
|
|
167
|
+
* "앞"에만 넣으므로 return/break/continue(항상 블록 맨 끝에만 올 수 있음)
|
|
168
|
+
* 뒤에 잘못 삽입될 일이 없음. */
|
|
169
|
+
function processBlock(blk: Block): void {
|
|
170
|
+
for (const stmt of blk.statements) processStatement(stmt)
|
|
171
|
+
|
|
172
|
+
const result: Statement[] = []
|
|
173
|
+
let inserted = 0
|
|
174
|
+
for (const stmt of blk.statements) {
|
|
175
|
+
if (inserted < options.maxPerBlock && Math.random() < options.probability) {
|
|
176
|
+
result.push(buildJunkStatement())
|
|
177
|
+
inserted++
|
|
178
|
+
}
|
|
179
|
+
result.push(stmt)
|
|
180
|
+
}
|
|
181
|
+
blk.statements = result
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
processBlock(program.body)
|
|
185
185
|
}
|
package/src/passes/Minify.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export function minifyPrinted(code: string): string {
|
|
2
|
-
return code
|
|
3
|
-
.replace(/\r\n|\r|\n/g, " ")
|
|
4
|
-
.replace(/[ \t]+/g, " ")
|
|
5
|
-
.trim()
|
|
1
|
+
export function minifyPrinted(code: string): string {
|
|
2
|
+
return code
|
|
3
|
+
.replace(/\r\n|\r|\n/g, " ")
|
|
4
|
+
.replace(/[ \t]+/g, " ")
|
|
5
|
+
.trim()
|
|
6
6
|
}
|
|
@@ -1,25 +1,48 @@
|
|
|
1
|
-
import type { Program, Expression } from "luau-parser"
|
|
1
|
+
import type { Program, Expression, Statement } from "luau-parser"
|
|
2
2
|
import { transformExpressions } from "./walk"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
numberLiteral, binary, paren, identifier, call, index, table, positionalField,
|
|
5
|
+
localStatement, localFunctionStatement, functionParam, functionBody, functionExpression,
|
|
6
|
+
block, assignmentStatement, numericForStatement, whileStatement, ifStatement, ifClause,
|
|
7
|
+
returnStatement, unary, vmStructuralNumbers,
|
|
8
|
+
} from "./nodeFactory"
|
|
4
9
|
|
|
5
10
|
export interface NumbersToExpressionsOptions {
|
|
6
11
|
/** 랜덤 보조항의 최소 절댓값 */
|
|
7
12
|
min: number
|
|
8
13
|
/** 랜덤 보조항의 최대 절댓값 */
|
|
9
14
|
max: number
|
|
15
|
+
/**
|
|
16
|
+
* 0~1. 정수 리터럴을 순수 산술식(depth 얕은 a+b 형태) 대신
|
|
17
|
+
* for/while 루프나 재귀 함수 호출로 "런타임에 조립"할 확률.
|
|
18
|
+
* 단순 트리 순회형 상수 접기(constant folding)는 문(statement)/제어흐름을
|
|
19
|
+
* 해석하지 못하므로 이 경로로 만든 값은 그런 도구로는 복원되지 않는다.
|
|
20
|
+
* 기본값 0.35.
|
|
21
|
+
*/
|
|
22
|
+
controlFlowProbability?: number
|
|
10
23
|
}
|
|
11
24
|
|
|
12
25
|
const MAX_DEPTH = 1
|
|
13
26
|
const NEST_PROBABILITY = 0.35
|
|
27
|
+
const CONTROL_FLOW_PROBABILITY_DEFAULT = 0.35
|
|
14
28
|
|
|
15
29
|
function randomInt(min: number, max: number): number {
|
|
16
30
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
17
31
|
}
|
|
18
32
|
|
|
33
|
+
function randomName(): string {
|
|
34
|
+
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "")
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
function randSign(): 1 | -1 {
|
|
20
38
|
return Math.random() < 0.5 ? 1 : -1
|
|
21
39
|
}
|
|
22
40
|
|
|
41
|
+
/** (function() ... end)() — 문(statement)들을 하나의 표현식으로 감싸는 즉시실행 함수. */
|
|
42
|
+
function iife(statements: Statement[]): Expression {
|
|
43
|
+
return call(paren(functionExpression(functionBody([], block(statements)))), [])
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
type Strategy = "add" | "sub" | "mul"
|
|
24
47
|
|
|
25
48
|
/** value를 min~max 범위 정수로 나눌 수 있으면 mul도 후보에 추가 */
|
|
@@ -44,6 +67,7 @@ function operand(n: number, min: number, max: number, depth: number): Expression
|
|
|
44
67
|
return numberLiteral(n)
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
/** 얕은 산술식(a+b, a-b, a*b) 하나로 값을 위장 — 기존 방식, 가벼운 기본 경로로 유지. */
|
|
47
71
|
function buildNumberExpr(value: number, min: number, max: number, depth = 0): Expression {
|
|
48
72
|
const strategy = pickStrategy(value, min, max)
|
|
49
73
|
let inner: Expression
|
|
@@ -78,9 +102,166 @@ function buildNumberExpr(value: number, min: number, max: number, depth = 0): Ex
|
|
|
78
102
|
return paren(inner)
|
|
79
103
|
}
|
|
80
104
|
|
|
105
|
+
/** n개의 정수 조각(합이 정확히 value)으로 분할. 마지막 조각은 나머지를 정확히 채운다. */
|
|
106
|
+
function randomPartition(value: number, n: number, min: number, max: number): number[] {
|
|
107
|
+
const parts: number[] = []
|
|
108
|
+
let remaining = value
|
|
109
|
+
for (let i = 0; i < n - 1; i++) {
|
|
110
|
+
const part = randomInt(min, max) * randSign()
|
|
111
|
+
parts.push(part)
|
|
112
|
+
remaining -= part
|
|
113
|
+
}
|
|
114
|
+
parts.push(remaining)
|
|
115
|
+
return parts
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* (function()
|
|
120
|
+
* local steps = { s1, s2, ..., sn }
|
|
121
|
+
* local acc = 0
|
|
122
|
+
* for i = 1, #steps do
|
|
123
|
+
* acc = acc + steps[i]
|
|
124
|
+
* end
|
|
125
|
+
* return acc
|
|
126
|
+
* end)()
|
|
127
|
+
*/
|
|
128
|
+
function buildForLoopNumberExpr(value: number, min: number, max: number): Expression {
|
|
129
|
+
const n = randomInt(3, 6)
|
|
130
|
+
const steps = randomPartition(value, n, min, max)
|
|
131
|
+
|
|
132
|
+
const accVar = randomName()
|
|
133
|
+
const stepsVar = randomName()
|
|
134
|
+
const iVar = randomName()
|
|
135
|
+
|
|
136
|
+
const stmts: Statement[] = [
|
|
137
|
+
localStatement(stepsVar, table(steps.map((s) => positionalField(numberLiteral(s))))),
|
|
138
|
+
localStatement(accVar, numberLiteral(0)),
|
|
139
|
+
numericForStatement(
|
|
140
|
+
iVar,
|
|
141
|
+
numberLiteral(1),
|
|
142
|
+
unary("#", identifier(stepsVar)),
|
|
143
|
+
block([
|
|
144
|
+
assignmentStatement(
|
|
145
|
+
[identifier(accVar)],
|
|
146
|
+
[binary("+", identifier(accVar), index(identifier(stepsVar), identifier(iVar)))],
|
|
147
|
+
),
|
|
148
|
+
]),
|
|
149
|
+
),
|
|
150
|
+
returnStatement([identifier(accVar)]),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
return iife(stmts)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* (function()
|
|
158
|
+
* local acc = BASE
|
|
159
|
+
* local n = N
|
|
160
|
+
* while n > 0 do
|
|
161
|
+
* acc = acc + STEP
|
|
162
|
+
* n = n - 1
|
|
163
|
+
* end
|
|
164
|
+
* return acc
|
|
165
|
+
* end)()
|
|
166
|
+
*/
|
|
167
|
+
function buildWhileLoopNumberExpr(value: number, min: number, max: number): Expression {
|
|
168
|
+
const n = randomInt(3, 6)
|
|
169
|
+
const step = randomInt(min, max) * randSign()
|
|
170
|
+
const base = value - n * step
|
|
171
|
+
|
|
172
|
+
const accVar = randomName()
|
|
173
|
+
const nVar = randomName()
|
|
174
|
+
|
|
175
|
+
const stmts: Statement[] = [
|
|
176
|
+
localStatement(accVar, numberLiteral(base)),
|
|
177
|
+
localStatement(nVar, numberLiteral(n)),
|
|
178
|
+
whileStatement(
|
|
179
|
+
binary(">", identifier(nVar), numberLiteral(0)),
|
|
180
|
+
block([
|
|
181
|
+
assignmentStatement([identifier(accVar)], [binary("+", identifier(accVar), numberLiteral(step))]),
|
|
182
|
+
assignmentStatement([identifier(nVar)], [binary("-", identifier(nVar), numberLiteral(1))]),
|
|
183
|
+
]),
|
|
184
|
+
),
|
|
185
|
+
returnStatement([identifier(accVar)]),
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
return iife(stmts)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* (function()
|
|
193
|
+
* local function <rec>(n)
|
|
194
|
+
* if n <= 0 then
|
|
195
|
+
* return BASE
|
|
196
|
+
* end
|
|
197
|
+
* return STEP + <rec>(n - 1)
|
|
198
|
+
* end
|
|
199
|
+
* return <rec>(N)
|
|
200
|
+
* end)()
|
|
201
|
+
*/
|
|
202
|
+
function buildRecursiveNumberExpr(value: number, min: number, max: number): Expression {
|
|
203
|
+
const n = randomInt(3, 6)
|
|
204
|
+
const step = randomInt(min, max) * randSign()
|
|
205
|
+
const base = value - n * step
|
|
206
|
+
|
|
207
|
+
const recName = randomName()
|
|
208
|
+
const nParam = randomName()
|
|
209
|
+
|
|
210
|
+
const body = block([
|
|
211
|
+
ifStatement([
|
|
212
|
+
ifClause(
|
|
213
|
+
binary("<=", identifier(nParam), numberLiteral(0)),
|
|
214
|
+
block([returnStatement([numberLiteral(base)])]),
|
|
215
|
+
),
|
|
216
|
+
]),
|
|
217
|
+
returnStatement([
|
|
218
|
+
binary(
|
|
219
|
+
"+",
|
|
220
|
+
numberLiteral(step),
|
|
221
|
+
call(identifier(recName), [binary("-", identifier(nParam), numberLiteral(1))]),
|
|
222
|
+
),
|
|
223
|
+
]),
|
|
224
|
+
])
|
|
225
|
+
|
|
226
|
+
return iife([
|
|
227
|
+
localFunctionStatement(recName, functionBody([functionParam(nParam)], body)),
|
|
228
|
+
returnStatement([call(identifier(recName), [numberLiteral(n)])]),
|
|
229
|
+
])
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
type ControlFlowStrategy = "forLoop" | "whileLoop" | "recursive"
|
|
233
|
+
|
|
234
|
+
function pickControlFlowStrategy(): ControlFlowStrategy {
|
|
235
|
+
const options: ControlFlowStrategy[] = ["forLoop", "whileLoop", "recursive"]
|
|
236
|
+
return options[randomInt(0, options.length - 1)]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildControlFlowNumberExpr(value: number, min: number, max: number): Expression {
|
|
240
|
+
switch (pickControlFlowStrategy()) {
|
|
241
|
+
case "forLoop":
|
|
242
|
+
return buildForLoopNumberExpr(value, min, max)
|
|
243
|
+
case "whileLoop":
|
|
244
|
+
return buildWhileLoopNumberExpr(value, min, max)
|
|
245
|
+
case "recursive":
|
|
246
|
+
return buildRecursiveNumberExpr(value, min, max)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
81
250
|
export function runNumbersToExpressions(program: Program, options: NumbersToExpressionsOptions): void {
|
|
251
|
+
const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT
|
|
252
|
+
|
|
82
253
|
transformExpressions(program, (expr) => {
|
|
83
254
|
if (expr.type !== "NumberLiteral") return
|
|
255
|
+
// Vmify가 만든 op/a/b/c 같은 내부 메타데이터는 건드리지 않는다
|
|
256
|
+
// (개수가 코드 크기에 비례해 폭증하고, 로직은 이미 Vmify가 구조적으로 숨긴 값).
|
|
257
|
+
if (vmStructuralNumbers.has(expr)) return
|
|
258
|
+
|
|
259
|
+
// 루프/재귀 조립은 정수 배분(합이 정확히 일치)에 의존하므로 정수에만 적용하고,
|
|
260
|
+
// 실수/범위를 벗어난 값은 기존 산술식 위장으로 처리한다.
|
|
261
|
+
if (Number.isInteger(expr.value) && Math.random() < controlFlowProbability) {
|
|
262
|
+
return buildControlFlowNumberExpr(expr.value, options.min, options.max)
|
|
263
|
+
}
|
|
264
|
+
|
|
84
265
|
return buildNumberExpr(expr.value, options.min, options.max)
|
|
85
266
|
})
|
|
86
267
|
}
|
|
@@ -2,24 +2,17 @@ import type { Program } from "luau-parser"
|
|
|
2
2
|
import { analyzeScopes, isGlobal } from "luau-parser"
|
|
3
3
|
|
|
4
4
|
export interface RenameVariablesOptions {
|
|
5
|
-
random
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
let counter = 0
|
|
9
|
-
function defaultRandomName(): string {
|
|
10
|
-
counter += 1
|
|
11
|
-
return `_l${counter.toString(36)}`
|
|
5
|
+
random: () => string
|
|
12
6
|
}
|
|
13
7
|
|
|
14
8
|
export function runRenameVariables(program: Program, options: RenameVariablesOptions): void {
|
|
15
9
|
const analysis = analyzeScopes(program)
|
|
16
|
-
const makeName = options.random ?? defaultRandomName
|
|
17
10
|
|
|
18
11
|
for (const binding of analysis.bindings.values()) {
|
|
19
12
|
if (isGlobal(binding)) continue
|
|
20
13
|
if (binding.kind === "self") continue
|
|
21
14
|
|
|
22
|
-
const newName =
|
|
15
|
+
const newName = options.random()
|
|
23
16
|
binding.name = newName
|
|
24
17
|
|
|
25
18
|
if (binding.declarationNode) {
|