luau-obfuscator 1.0.6 → 1.0.8
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 +56 -0
- package/dist/index.js +56 -0
- package/generated/grammers.luau +3341 -3396
- package/generated/tests.luau +2762 -1833
- package/package.json +1 -1
- package/smoketest/tests.luau +16 -0
- package/src/passes/vmify/compiler.ts +12 -0
- package/src/passes/vmify/names.ts +4 -0
- package/src/passes/vmify/opcodes.ts +4 -0
- package/src/passes/vmify/runtime.ts +52 -0
package/package.json
CHANGED
package/smoketest/tests.luau
CHANGED
|
@@ -106,6 +106,22 @@ check("string length ascii", #"Roblox", 6)
|
|
|
106
106
|
check("string.upper", string.upper("mixedCase"), "MIXEDCASE")
|
|
107
107
|
check("string.sub", string.sub("Hello, Roblox!", 1, 5), "Hello")
|
|
108
108
|
|
|
109
|
+
-- 11) 제네릭 for의 iterator가 `a.b` / `a:b()`일 때 루프 변수가 밀리지 않는지
|
|
110
|
+
-- (예전엔 `a`를 담은 임시 레지스터가 남아 첫 변수에 `a`가 들어가고 루프가 안 끝났음)
|
|
111
|
+
local holder = { items = { "x", "y" } }
|
|
112
|
+
function holder:GetItems() return self.items end
|
|
113
|
+
local memberParts, methodParts = {}, {}
|
|
114
|
+
for i, v in holder.items do
|
|
115
|
+
if #memberParts > 5 then break end
|
|
116
|
+
memberParts[#memberParts + 1] = tostring(i) .. "=" .. tostring(v)
|
|
117
|
+
end
|
|
118
|
+
for i, v in holder:GetItems() do
|
|
119
|
+
if #methodParts > 5 then break end
|
|
120
|
+
methodParts[#methodParts + 1] = tostring(i) .. "=" .. tostring(v)
|
|
121
|
+
end
|
|
122
|
+
check("generic for over a.b", table.concat(memberParts, ","), "1=x,2=y")
|
|
123
|
+
check("generic for over a:b()", table.concat(methodParts, ","), "1=x,2=y")
|
|
124
|
+
|
|
109
125
|
print("===== ObfuscationTest end =====")
|
|
110
126
|
|
|
111
127
|
if failCount == 0 then
|
|
@@ -642,6 +642,18 @@ export class VmCompiler {
|
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
+
// iterator 표현식이 남긴 임시 레지스터를 반납한다(`a.b` / `a:b()`는 `a`를 담은
|
|
646
|
+
// 레지스터를 잡아둔 채로 끝난다). 아래 CALL은 top()부터 결과를 쓰고 루프 변수는
|
|
647
|
+
// declareLocal()로 base+3부터 잡히므로, 여기서 top을 base+3으로 맞추지 않으면
|
|
648
|
+
// 루프 변수가 한 칸씩 밀려 첫 변수에 `a`가 들어가고 루프가 끝나지 않는다.
|
|
649
|
+
state.regs.freeTemp(base + 3)
|
|
650
|
+
|
|
651
|
+
// Luau 제네릭 반복은 iterator 표현식이 함수가 아니라 테이블일 수 있다
|
|
652
|
+
// (플레인 테이블 → array-then-hash 순회, __iter 메타메서드 → 그 결과). base가
|
|
653
|
+
// 함수가 아니면 루프 진입 전에 한 번 정규화한다 — 안 하면 아래 CALL이 테이블을
|
|
654
|
+
// 호출하려다 "attempt to call a table value"로 터진다.
|
|
655
|
+
this.emit(state, Opcode.NORMITER, base, 0, 0, "normalize-generic-iter")
|
|
656
|
+
|
|
645
657
|
const loopStart = state.proto.code.length
|
|
646
658
|
const nVars = stmt.variables.length
|
|
647
659
|
const callBase = state.regs.top()
|
|
@@ -20,6 +20,9 @@ export interface VmNames {
|
|
|
20
20
|
dispatch: string
|
|
21
21
|
/** local function __vm_rk(frame, x) */
|
|
22
22
|
rk: string
|
|
23
|
+
/** local function __vm_tableIter(t) — 제네릭 for가 플레인 테이블을 순회할 때 쓰는
|
|
24
|
+
* array-then-hash 이터레이터 클로저 팩토리 (NORMITER opcode에서 사용) */
|
|
25
|
+
tableIter: string
|
|
23
26
|
/** __vm_execute/핸들러의 frame 파라미터 이름 */
|
|
24
27
|
frame: string
|
|
25
28
|
// frame 테이블 필드
|
|
@@ -63,6 +66,7 @@ export function generateVmNames(random: () => number = Math.random): VmNames {
|
|
|
63
66
|
const id = makeIdGenerator(random)
|
|
64
67
|
return {
|
|
65
68
|
globals: id(), protoRoot: id(), execute: id(), handlers: id(), dispatch: id(), rk: id(),
|
|
69
|
+
tableIter: id(),
|
|
66
70
|
frame: id(),
|
|
67
71
|
R: id(), K: id(), code: id(), protos: id(), upvals: id(), varargs: id(),
|
|
68
72
|
nVarargs: id(), multiTop: id(),
|
|
@@ -41,6 +41,10 @@ export enum Opcode {
|
|
|
41
41
|
// 생성자 마지막 필드가 Call/MethodCall/Vararg로 다중값을 낼 때 씀 (`{f()}`, `{...}`)
|
|
42
42
|
IDIV, // R[A] = RK(B) // RK(C) (Luau 바닥 나눗셈 연산자, 뒤에 추가되어 기존 opcode
|
|
43
43
|
// 번호를 안 건드림 — enum 중간에 끼워넣으면 runtime.ts의 숫자 인덱스가 전부 밀림)
|
|
44
|
+
NORMITER, // 제네릭 for 진입 시 R[A..A+2] 이터레이터 3-튜플을 정규화. R[A]가 함수가
|
|
45
|
+
// 아니라 테이블이면(플레인 테이블 or __iter 메타메서드) Luau 제네릭 반복
|
|
46
|
+
// 의미대로 (fn, state, ctrl)로 바꿔준다. 안 하면 뒤의 CALL이 테이블을
|
|
47
|
+
// 호출하려다 "attempt to call a table value"로 터짐. (enum 끝에 추가)
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export interface Instr {
|
|
@@ -60,6 +60,34 @@ local function ${N.rk}(${N.frame}, x)
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
-- 플레인 테이블에 대한 Luau 제네릭 반복(for k, v in t) 이터레이터.
|
|
64
|
+
-- 배열부(1..경계)를 순서대로 먼저, 그다음 해시부(이미 낸 정수 키는 건너뜀)를 낸다.
|
|
65
|
+
-- 상태를 클로저에 담으므로 (fn, state, ctrl) 중 state/ctrl은 안 쓴다.
|
|
66
|
+
local function ${N.tableIter}(t)
|
|
67
|
+
local i = 0
|
|
68
|
+
local arrayDone = false
|
|
69
|
+
local hk = nil
|
|
70
|
+
return function()
|
|
71
|
+
if not arrayDone then
|
|
72
|
+
i = i + 1
|
|
73
|
+
local v = t[i]
|
|
74
|
+
if v ~= nil then
|
|
75
|
+
return i, v
|
|
76
|
+
end
|
|
77
|
+
arrayDone = true
|
|
78
|
+
end
|
|
79
|
+
local k, v = next(t, hk)
|
|
80
|
+
while k ~= nil do
|
|
81
|
+
if type(k) ~= "number" or k ~= math.floor(k) or k < 1 or k >= i then
|
|
82
|
+
hk = k
|
|
83
|
+
return k, v
|
|
84
|
+
end
|
|
85
|
+
k, v = next(t, k)
|
|
86
|
+
end
|
|
87
|
+
return nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
63
91
|
-- MOVE
|
|
64
92
|
${N.handlers}[${op(Opcode.MOVE)}] = function(${N.frame}, pc, instr)
|
|
65
93
|
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
|
|
@@ -261,6 +289,30 @@ ${N.handlers}[${op(Opcode.RETURN)}] = function(${N.frame}, pc, instr)
|
|
|
261
289
|
return table.pack(table.unpack(${N.frame}.${N.R}, a, a + b - 2))
|
|
262
290
|
end
|
|
263
291
|
|
|
292
|
+
-- NORMITER: 제네릭 for 진입 시 이터레이터 3-튜플(R[A..A+2])을 정규화.
|
|
293
|
+
-- R[A]가 함수면 그대로 두고(pairs/ipairs/커스텀 이터레이터), 테이블이면 __iter
|
|
294
|
+
-- 메타메서드가 있으면 그걸 호출해 (fn,state,ctrl)로, 없으면 array-then-hash
|
|
295
|
+
-- 클로저 이터레이터로 바꾼다. 이후 CALL 핸들러가 함수를 정상 호출하게 된다.
|
|
296
|
+
${N.handlers}[${op(Opcode.NORMITER)}] = function(${N.frame}, pc, instr)
|
|
297
|
+
local a = instr[2]
|
|
298
|
+
local it = ${N.frame}.${N.R}[a]
|
|
299
|
+
if type(it) ~= "function" then
|
|
300
|
+
local mt = getmetatable(it)
|
|
301
|
+
local customIter = mt and rawget(mt, "__iter")
|
|
302
|
+
if customIter ~= nil then
|
|
303
|
+
local f, s, c = customIter(it)
|
|
304
|
+
${N.frame}.${N.R}[a] = f
|
|
305
|
+
${N.frame}.${N.R}[a + 1] = s
|
|
306
|
+
${N.frame}.${N.R}[a + 2] = c
|
|
307
|
+
elseif type(it) == "table" then
|
|
308
|
+
${N.frame}.${N.R}[a] = ${N.tableIter}(it)
|
|
309
|
+
${N.frame}.${N.R}[a + 1] = nil
|
|
310
|
+
${N.frame}.${N.R}[a + 2] = nil
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
return pc + 1
|
|
314
|
+
end
|
|
315
|
+
|
|
264
316
|
-- FORPREP
|
|
265
317
|
${N.handlers}[${op(Opcode.FORPREP)}] = function(${N.frame}, pc, instr)
|
|
266
318
|
local a = instr[2]
|