11-- This Script is Part of the Prometheus Obfuscator by Levno_710
22--
33-- emit.lua
4+ --
45-- This Script contains the container function body emission for the compiler.
56
67local Ast = require (" prometheus.ast" );
78local Scope = require (" prometheus.scope" );
89local util = require (" prometheus.util" );
910local constants = require (" prometheus.compiler.constants" );
11+ local AstKind = Ast .AstKind ;
1012
1113local MAX_REGS = constants .MAX_REGS ;
1214
1315return function (Compiler )
16+ local function hasAnyEntries (tbl )
17+ return type (tbl ) == " table" and next (tbl ) ~= nil ;
18+ end
19+
20+ local function unionLookupTables (a , b )
21+ local out = {};
22+ for k , v in pairs (a or {}) do
23+ out [k ] = v ;
24+ end
25+ for k , v in pairs (b or {}) do
26+ out [k ] = v ;
27+ end
28+ return out ;
29+ end
30+
31+ local function canMergeParallelAssignmentStatements (statA , statB )
32+ if type (statA ) ~= " table" or type (statB ) ~= " table" then
33+ return false ;
34+ end
35+
36+ if statA .usesUpvals or statB .usesUpvals then
37+ return false ;
38+ end
39+
40+ local a = statA .statement ;
41+ local b = statB .statement ;
42+ if type (a ) ~= " table" or type (b ) ~= " table" then
43+ return false ;
44+ end
45+ if a .kind ~= AstKind .AssignmentStatement or b .kind ~= AstKind .AssignmentStatement then
46+ return false ;
47+ end
48+
49+ if type (a .lhs ) ~= " table" or type (a .rhs ) ~= " table" or type (b .lhs ) ~= " table" or type (b .rhs ) ~= " table" then
50+ return false ;
51+ end
52+
53+ if # a .lhs ~= # a .rhs or # b .lhs ~= # b .rhs then
54+ return false ;
55+ end
56+
57+ -- Avoid merging vararg/call assignments because they can affect multi-return behavior.
58+ local function hasUnsafeRhs (rhsList )
59+ for _ , rhsExpr in ipairs (rhsList ) do
60+ if type (rhsExpr ) ~= " table" then
61+ return true ;
62+ end
63+ local kind = rhsExpr .kind ;
64+ if kind == AstKind .FunctionCallExpression or kind == AstKind .PassSelfFunctionCallExpression or kind == AstKind .VarargExpression then
65+ return true ;
66+ end
67+ end
68+ return false ;
69+ end
70+ if hasUnsafeRhs (a .rhs ) or hasUnsafeRhs (b .rhs ) then
71+ return false ;
72+ end
73+
74+ local aReads = type (statA .reads ) == " table" and statA .reads or {};
75+ local aWrites = type (statA .writes ) == " table" and statA .writes or {};
76+ local bReads = type (statB .reads ) == " table" and statB .reads or {};
77+ local bWrites = type (statB .writes ) == " table" and statB .writes or {};
78+
79+ -- Allow merging even if one statement has no writes (e.g., x = o(x) style assignments)
80+ -- Only require that at least one of them has writes
81+ if not hasAnyEntries (aWrites ) and not hasAnyEntries (bWrites ) then
82+ return false ;
83+ end
84+
85+ for r in pairs (aReads ) do
86+ if bWrites [r ] then
87+ return false ;
88+ end
89+ end
90+
91+ for r , b in pairs (aWrites ) do
92+ if bWrites [r ] or bReads [r ] then
93+ return false ;
94+ end
95+ end
96+
97+ return true ;
98+ end
99+
100+ local function mergeParallelAssignmentStatements (statA , statB )
101+ local lhs = {};
102+ local rhs = {};
103+ local aLhs , bLhs = statA .statement .lhs , statB .statement .lhs ;
104+ local aRhs , bRhs = statA .statement .rhs , statB .statement .rhs ;
105+ for i = 1 , # aLhs do lhs [i ] = aLhs [i ]; end
106+ for i = 1 , # bLhs do lhs [# aLhs + i ] = bLhs [i ]; end
107+ for i = 1 , # aRhs do rhs [i ] = aRhs [i ]; end
108+ for i = 1 , # bRhs do rhs [# aRhs + i ] = bRhs [i ]; end
109+
110+ return {
111+ statement = Ast .AssignmentStatement (lhs , rhs ),
112+ writes = unionLookupTables (statA .writes , statB .writes ),
113+ reads = unionLookupTables (statA .reads , statB .reads ),
114+ usesUpvals = statA .usesUpvals or statB .usesUpvals ,
115+ };
116+ end
117+
118+ local function mergeAdjacentParallelAssignments (blockstats )
119+ local merged = {};
120+ local i = 1 ;
121+ while i <= # blockstats do
122+ local stat = blockstats [i ];
123+ i = i + 1 ;
124+
125+ while i <= # blockstats and canMergeParallelAssignmentStatements (stat , blockstats [i ]) do
126+ stat = mergeParallelAssignmentStatements (stat , blockstats [i ]);
127+ i = i + 1 ;
128+ end
129+
130+ table.insert (merged , stat );
131+ end
132+ return merged ;
133+ end
134+
14135 function Compiler :emitContainerFuncBody ()
15136 local blocks = {};
16137
@@ -70,8 +191,13 @@ return function(Compiler)
70191 end
71192 end
72193
194+ local mergedBlockStats = mergeAdjacentParallelAssignments (blockstats );
195+ for _ = 1 , 7 do
196+ mergedBlockStats = mergeAdjacentParallelAssignments (mergedBlockStats );
197+ end
198+
73199 blockstats = {};
74- for _ , stat in ipairs (block . statements ) do
200+ for _ , stat in ipairs (mergedBlockStats ) do
75201 table.insert (blockstats , stat .statement );
76202 end
77203
@@ -80,37 +206,114 @@ return function(Compiler)
80206 blocks [id ] = block ;
81207 end
82208
83- table.sort (blocks , function (a , b )
84- return a .id < b .id ;
85- end );
86-
87- local function buildIfBlock (scope , id , lBlock , rBlock )
88- local condition = Ast .LessThanExpression (self :pos (scope ), Ast .NumberExpression (id ));
89- return Ast .Block ({
90- Ast .IfStatement (condition , lBlock , {}, rBlock );
91- }, scope );
209+ table.sort (blocks , function (a , b ) return a .id < b .id end );
210+
211+ -- Build a strict threshold condition between adjacent block IDs.
212+ -- Using a midpoint avoids exact-id comparisons while preserving dispatch.
213+ local function buildBlockThresholdCondition (scope , leftId , rightId , useAndOr )
214+ local bound = math.floor ((leftId + rightId ) / 2 );
215+ local posExpr = self :pos (scope );
216+ local boundExpr = Ast .NumberExpression (bound );
217+
218+ if useAndOr then
219+ -- Kept for compatibility with caller variations.
220+ return Ast .LessThanExpression (posExpr , boundExpr );
221+ else
222+ local variant = math.random (1 , 2 );
223+ if variant == 1 then
224+ return Ast .LessThanExpression (posExpr , boundExpr );
225+ else
226+ return Ast .GreaterThanExpression (boundExpr , posExpr );
227+ end
228+ end
92229 end
93230
94- local function buildWhileBody (tb , l , r , pScope , scope )
231+ -- Build an elseif chain for a range of blocks
232+ local function buildElseifChain (tb , l , r , pScope )
233+ -- Handle invalid range by returning an empty block
234+ if r < l then
235+ local emptyScope = Scope :new (pScope );
236+ return Ast .Block ({}, emptyScope );
237+ end
238+
95239 local len = r - l + 1 ;
240+
241+ -- For single block
96242 if len == 1 then
97- tb [r ].block .scope :setParent (pScope );
98- return tb [r ].block ;
99- elseif len == 0 then
100- return nil ;
243+ tb [l ].block .scope :setParent (pScope );
244+ return tb [l ].block ;
101245 end
102246
103- local mid = l + math.ceil (len / 2 );
104- local bound = math.random (tb [mid - 1 ].id + 1 , tb [mid ].id );
105- local ifScope = scope or Scope :new (pScope );
247+ -- For small ranges, use elseif chain
248+ if len <= 4 then
249+ local ifScope = Scope :new (pScope );
250+ local elseifs = {};
251+
252+ -- First block uses the first midpoint threshold
253+ tb [l ].block .scope :setParent (ifScope );
254+ local firstCondition = buildBlockThresholdCondition (ifScope , tb [l ].id , tb [l + 1 ].id , false );
255+ local firstBlock = tb [l ].block ;
256+
257+ -- Middle blocks use their upper midpoint threshold
258+ for i = l + 1 , r - 1 do
259+ tb [i ].block .scope :setParent (ifScope );
260+ local condition = buildBlockThresholdCondition (ifScope , tb [i ].id , tb [i + 1 ].id , false );
261+ table.insert (elseifs , {
262+ condition = condition ,
263+ body = tb [i ].block
264+ });
265+ end
106266
107- local lBlock = buildWhileBody (tb , l , mid - 1 , ifScope );
108- local rBlock = buildWhileBody (tb , mid , r , ifScope );
267+ -- Last block becomes else
268+ tb [r ].block .scope :setParent (ifScope );
269+ local elseBlock = tb [r ].block ;
109270
110- return buildIfBlock (ifScope , bound , lBlock , rBlock );
271+ return Ast .Block ({
272+ Ast .IfStatement (firstCondition , firstBlock , elseifs , elseBlock );
273+ }, ifScope );
274+ end
275+
276+ -- For larger ranges, use binary split with and/or chaining
277+ local mid = l + math.ceil (len / 2 );
278+ local leftMaxId = tb [mid - 1 ].id ;
279+ local rightMinId = tb [mid ].id ;
280+ -- Float-safe split: any bound strictly between adjacent IDs works.
281+ -- Midpoint avoids integer-only math.random(min, max) behavior.
282+ local bound = math.floor ((leftMaxId + rightMinId ) / 2 );
283+ local ifScope = Scope :new (pScope );
284+
285+ local lBlock = buildElseifChain (tb , l , mid - 1 , ifScope );
286+ local rBlock = buildElseifChain (tb , mid , r , ifScope );
287+
288+ -- Randomly choose between different condition styles
289+ local condStyle = math.random (1 , 3 );
290+ local condition ;
291+ local trueBlock , falseBlock ;
292+
293+ if condStyle == 1 then
294+ -- pos < bound
295+ condition = Ast .LessThanExpression (self :pos (ifScope ), Ast .NumberExpression (bound ));
296+ trueBlock , falseBlock = lBlock , rBlock ;
297+ elseif condStyle == 2 then
298+ -- bound > pos
299+ condition = Ast .GreaterThanExpression (Ast .NumberExpression (bound ), self :pos (ifScope ));
300+ trueBlock , falseBlock = lBlock , rBlock ;
301+ else
302+ -- Equivalent split using strict > with branches reversed.
303+ condition = Ast .GreaterThanExpression (self :pos (ifScope ), Ast .NumberExpression (bound ));
304+ trueBlock , falseBlock = rBlock , lBlock ;
305+ end
306+
307+ return Ast .Block ({
308+ Ast .IfStatement (condition , trueBlock , {}, falseBlock );
309+ }, ifScope );
111310 end
112311
113- local whileBody = buildWhileBody (blocks , 1 , # blocks , self .containerFuncScope , self .whileScope );
312+ local whileBody = buildElseifChain (blocks , 1 , # blocks , self .containerFuncScope );
313+ if self .whileScope then
314+ -- Ensure whileScope is properly connected
315+ self .whileScope :setParent (self .containerFuncScope );
316+ end
114317
115318 self .whileScope :addReferenceToHigherScope (self .containerFuncScope , self .returnVar , 1 );
116319 self .whileScope :addReferenceToHigherScope (self .containerFuncScope , self .posVar );
@@ -137,6 +340,7 @@ return function(Compiler)
137340
138341 table.insert (stats , Ast .WhileStatement (whileBody , Ast .VariableExpression (self .containerFuncScope , self .posVar )));
139342
343+
140344 table.insert (stats , Ast .AssignmentStatement ({
141345 Ast .AssignmentVariable (self .containerFuncScope , self .posVar )
142346 }, {
@@ -151,4 +355,4 @@ return function(Compiler)
151355
152356 return Ast .Block (stats , self .containerFuncScope );
153357 end
154- end
358+ end
0 commit comments