unidecompiler-plugin-lua 0.1.1__py3-none-any.whl
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.
- unidecompiler_plugin_lua/__init__.py +2 -0
- unidecompiler_plugin_lua/chunk54.py +587 -0
- unidecompiler_plugin_lua/lifter.py +1631 -0
- unidecompiler_plugin_lua/luac.py +558 -0
- unidecompiler_plugin_lua/normalize.py +241 -0
- unidecompiler_plugin_lua/plugin.py +108 -0
- unidecompiler_plugin_lua/simulation.py +78 -0
- unidecompiler_plugin_lua/support.py +15 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/METADATA +25 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/RECORD +13 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/WHEEL +5 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/entry_points.txt +2 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1631 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from unidecompiler.core.ir import (
|
|
7
|
+
Assign,
|
|
8
|
+
BinaryOp,
|
|
9
|
+
Call,
|
|
10
|
+
CapturedVar,
|
|
11
|
+
Const,
|
|
12
|
+
Expr,
|
|
13
|
+
Global,
|
|
14
|
+
GetItem,
|
|
15
|
+
MapLiteral,
|
|
16
|
+
MultiReturn,
|
|
17
|
+
SourceRef,
|
|
18
|
+
StoreItem,
|
|
19
|
+
UnaryOp,
|
|
20
|
+
Var,
|
|
21
|
+
)
|
|
22
|
+
from unidecompiler.core.effects import AssignManyValues, AssignValue, AssignValueOnBranch, Emit, ReturnValues, UnknownOpcode
|
|
23
|
+
from unidecompiler.core.vm_module import assemble_vm_module
|
|
24
|
+
from unidecompiler.core.vm_effect_table import VMEffectTable
|
|
25
|
+
from unidecompiler.core.vm_function import (
|
|
26
|
+
VMFunctionSpec,
|
|
27
|
+
empty_vm_function,
|
|
28
|
+
recover_vm_function,
|
|
29
|
+
lift_steps,
|
|
30
|
+
lift_vm_step_function,
|
|
31
|
+
)
|
|
32
|
+
from unidecompiler.core.vm_bytecode import VMBytecodeStep
|
|
33
|
+
from unidecompiler.core.vm_hints import VMHint
|
|
34
|
+
from unidecompiler.core.vm_operands import VMDecodedInstruction, VMOperand
|
|
35
|
+
from unidecompiler.core.vm_region import (
|
|
36
|
+
VMLinearState,
|
|
37
|
+
VMRegionOpcodeClasses,
|
|
38
|
+
VMRegionProfile,
|
|
39
|
+
VMStatefulCallbacks,
|
|
40
|
+
build_hint_region_profile,
|
|
41
|
+
)
|
|
42
|
+
from unidecompiler_plugin_lua.luac import LuaChunk, LuaFunctionListing
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
BINARY_OPS = {
|
|
46
|
+
"ADD": "+",
|
|
47
|
+
"SUB": "-",
|
|
48
|
+
"MUL": "*",
|
|
49
|
+
"DIV": "/",
|
|
50
|
+
"IDIV": "//",
|
|
51
|
+
"MOD": "%",
|
|
52
|
+
}
|
|
53
|
+
IMMEDIATE_BINARY_OPS = {
|
|
54
|
+
"ADDI": "+",
|
|
55
|
+
}
|
|
56
|
+
COMPARISON_OPS = {
|
|
57
|
+
"LT": "<",
|
|
58
|
+
"LE": "<=",
|
|
59
|
+
"EQ": "==",
|
|
60
|
+
}
|
|
61
|
+
IMMEDIATE_COMPARISON_OPS = {
|
|
62
|
+
"EQI": "==",
|
|
63
|
+
"LTI": "<",
|
|
64
|
+
"LEI": "<=",
|
|
65
|
+
"GTI": ">",
|
|
66
|
+
"GEI": ">=",
|
|
67
|
+
}
|
|
68
|
+
CONDITIONAL_OPS = frozenset(
|
|
69
|
+
{
|
|
70
|
+
"EQ",
|
|
71
|
+
"LT",
|
|
72
|
+
"LE",
|
|
73
|
+
"EQK",
|
|
74
|
+
"EQI",
|
|
75
|
+
"LTI",
|
|
76
|
+
"LEI",
|
|
77
|
+
"GTI",
|
|
78
|
+
"GEI",
|
|
79
|
+
"TEST",
|
|
80
|
+
"TESTSET",
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
JUMP_OPS = frozenset({"JMP", "FORPREP", "FORLOOP", "TFORPREP", "TFORLOOP"})
|
|
84
|
+
CONTROL_OPS = CONDITIONAL_OPS | JUMP_OPS
|
|
85
|
+
LUA_REGION_OPCODE_CLASSES = VMRegionOpcodeClasses(
|
|
86
|
+
control=CONTROL_OPS,
|
|
87
|
+
jumps=JUMP_OPS,
|
|
88
|
+
forward_jumps=JUMP_OPS,
|
|
89
|
+
backward_jumps=JUMP_OPS,
|
|
90
|
+
conditional_jumps=CONDITIONAL_OPS | frozenset({"FORLOOP", "TFORLOOP"}),
|
|
91
|
+
)
|
|
92
|
+
IGNORED_OPS = {
|
|
93
|
+
"MMBIN",
|
|
94
|
+
"MMBINI",
|
|
95
|
+
"MMBINK",
|
|
96
|
+
"VARARGPREP",
|
|
97
|
+
"EXTRAARG",
|
|
98
|
+
"LFALSESKIP",
|
|
99
|
+
"CLOSE",
|
|
100
|
+
"TBC",
|
|
101
|
+
*(CONTROL_OPS - frozenset({"FORPREP", "FORLOOP", "TESTSET", "TFORPREP", "TFORLOOP"})),
|
|
102
|
+
}
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class LuaEffectContext:
|
|
105
|
+
listing: LuaFunctionListing
|
|
106
|
+
constants: dict[int, object]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _lua_no_effect(_context: LuaEffectContext, _instruction, _source: SourceRef) -> tuple:
|
|
110
|
+
return ()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _lua_unknown_opcode_effect(
|
|
114
|
+
_context: LuaEffectContext,
|
|
115
|
+
instruction,
|
|
116
|
+
source: SourceRef,
|
|
117
|
+
) -> tuple:
|
|
118
|
+
return (
|
|
119
|
+
UnknownOpcode(
|
|
120
|
+
source=source,
|
|
121
|
+
opcode=instruction.opcode,
|
|
122
|
+
raw=f"{instruction.pc}: {instruction.opcode} {' '.join(instruction.operands)}".strip(),
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _lua_load_const_value(value):
|
|
128
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple:
|
|
129
|
+
operands = instruction.operands
|
|
130
|
+
if len(operands) < 1:
|
|
131
|
+
return None
|
|
132
|
+
return (_lua_assign(context.listing, int(operands[0]), instruction.pc, Const(value=value, source=source), source),)
|
|
133
|
+
|
|
134
|
+
return factory
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _lua_load_i(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
138
|
+
operands = instruction.operands
|
|
139
|
+
if len(operands) < 2:
|
|
140
|
+
return None
|
|
141
|
+
return (_lua_assign(context.listing, int(operands[0]), instruction.pc, Const(value=int(operands[1]), source=source), source),)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _lua_load_nil(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
145
|
+
operands = instruction.operands
|
|
146
|
+
if len(operands) < 2:
|
|
147
|
+
return None
|
|
148
|
+
start = int(operands[0])
|
|
149
|
+
count = int(operands[1]) + 1
|
|
150
|
+
return tuple(
|
|
151
|
+
_lua_assign(context.listing, register, instruction.pc, Const(value=None, source=source), source)
|
|
152
|
+
for register in range(start, start + count)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _lua_load_k(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
157
|
+
operands = instruction.operands
|
|
158
|
+
if len(operands) < 2:
|
|
159
|
+
return None
|
|
160
|
+
return (_lua_assign(context.listing, int(operands[0]), instruction.pc, Const(value=context.constants.get(int(operands[1])), source=source), source),)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _lua_move(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
164
|
+
operands = instruction.operands
|
|
165
|
+
if len(operands) < 2:
|
|
166
|
+
return None
|
|
167
|
+
return (
|
|
168
|
+
_lua_assign(
|
|
169
|
+
context.listing,
|
|
170
|
+
int(operands[0]),
|
|
171
|
+
instruction.pc,
|
|
172
|
+
Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
173
|
+
source,
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _lua_binary(op: str):
|
|
179
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
180
|
+
operands = instruction.operands
|
|
181
|
+
if len(operands) < 3:
|
|
182
|
+
return None
|
|
183
|
+
return (
|
|
184
|
+
_lua_assign(
|
|
185
|
+
context.listing,
|
|
186
|
+
int(operands[0]),
|
|
187
|
+
instruction.pc,
|
|
188
|
+
BinaryOp(
|
|
189
|
+
source=source,
|
|
190
|
+
op=op,
|
|
191
|
+
left=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
192
|
+
right=Var(name=_read_register_name(context.listing, int(operands[2]), instruction.pc), source=source),
|
|
193
|
+
semantics="dynamic",
|
|
194
|
+
),
|
|
195
|
+
source,
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return factory
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _lua_unary(op: str):
|
|
203
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
204
|
+
operands = instruction.operands
|
|
205
|
+
if len(operands) < 2:
|
|
206
|
+
return None
|
|
207
|
+
return (
|
|
208
|
+
_lua_assign(
|
|
209
|
+
context.listing,
|
|
210
|
+
int(operands[0]),
|
|
211
|
+
instruction.pc,
|
|
212
|
+
UnaryOp(
|
|
213
|
+
source=source,
|
|
214
|
+
op=op,
|
|
215
|
+
value=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
216
|
+
),
|
|
217
|
+
source,
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return factory
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _lua_immediate_binary(op: str):
|
|
225
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
226
|
+
operands = instruction.operands
|
|
227
|
+
if len(operands) < 3:
|
|
228
|
+
return None
|
|
229
|
+
return (
|
|
230
|
+
_lua_assign(
|
|
231
|
+
context.listing,
|
|
232
|
+
int(operands[0]),
|
|
233
|
+
instruction.pc,
|
|
234
|
+
BinaryOp(
|
|
235
|
+
source=source,
|
|
236
|
+
op=op,
|
|
237
|
+
left=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
238
|
+
right=Const(value=int(operands[2]), source=source),
|
|
239
|
+
semantics="dynamic",
|
|
240
|
+
),
|
|
241
|
+
source,
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return factory
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _lua_shift_immediate(op: str, immediate_on_left: bool = False):
|
|
249
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
250
|
+
operands = instruction.operands
|
|
251
|
+
if len(operands) < 3:
|
|
252
|
+
return None
|
|
253
|
+
shift_op = op
|
|
254
|
+
immediate_value = int(operands[2])
|
|
255
|
+
if immediate_value < 0 and op in {"<<", ">>"}:
|
|
256
|
+
shift_op = "<<" if op == ">>" else ">>"
|
|
257
|
+
immediate_value = -immediate_value
|
|
258
|
+
immediate = Const(value=immediate_value, source=source)
|
|
259
|
+
register = Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source)
|
|
260
|
+
left, right = (immediate, register) if immediate_on_left else (register, immediate)
|
|
261
|
+
return (
|
|
262
|
+
_lua_assign(
|
|
263
|
+
context.listing,
|
|
264
|
+
int(operands[0]),
|
|
265
|
+
instruction.pc,
|
|
266
|
+
BinaryOp(source=source, op=shift_op, left=left, right=right, semantics="dynamic"),
|
|
267
|
+
source,
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return factory
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _lua_constant_binary(op: str):
|
|
275
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
276
|
+
operands = instruction.operands
|
|
277
|
+
if len(operands) < 3:
|
|
278
|
+
return None
|
|
279
|
+
return (
|
|
280
|
+
_lua_assign(
|
|
281
|
+
context.listing,
|
|
282
|
+
int(operands[0]),
|
|
283
|
+
instruction.pc,
|
|
284
|
+
BinaryOp(
|
|
285
|
+
source=source,
|
|
286
|
+
op=op,
|
|
287
|
+
left=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
288
|
+
right=Const(value=context.constants.get(int(operands[2])), source=source),
|
|
289
|
+
semantics="dynamic",
|
|
290
|
+
),
|
|
291
|
+
source,
|
|
292
|
+
),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
return factory
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _lua_get_table(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
299
|
+
operands = instruction.operands
|
|
300
|
+
if len(operands) < 3:
|
|
301
|
+
return None
|
|
302
|
+
return (
|
|
303
|
+
_lua_assign(
|
|
304
|
+
context.listing,
|
|
305
|
+
int(operands[0]),
|
|
306
|
+
instruction.pc,
|
|
307
|
+
GetItem(
|
|
308
|
+
source=source,
|
|
309
|
+
obj=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
310
|
+
key=Var(name=_read_register_name(context.listing, int(operands[2]), instruction.pc), source=source),
|
|
311
|
+
),
|
|
312
|
+
source,
|
|
313
|
+
),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _lua_get_tabup(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
318
|
+
operands = instruction.operands
|
|
319
|
+
if len(operands) < 3:
|
|
320
|
+
return None
|
|
321
|
+
key = _constant_operand_expr(context.constants, operands[2], source)
|
|
322
|
+
upvalue = _lua_upvalue_expr(context.listing, operands[1], source)
|
|
323
|
+
if not _is_lua_env_upvalue(upvalue):
|
|
324
|
+
return (
|
|
325
|
+
_lua_assign(
|
|
326
|
+
context.listing,
|
|
327
|
+
int(operands[0]),
|
|
328
|
+
instruction.pc,
|
|
329
|
+
GetItem(source=source, obj=upvalue, key=key),
|
|
330
|
+
source,
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
value: Expr = (
|
|
334
|
+
Global(name=key.value, source=source)
|
|
335
|
+
if isinstance(key, Const) and isinstance(key.value, str)
|
|
336
|
+
else GetItem(source=source, obj=upvalue, key=key)
|
|
337
|
+
)
|
|
338
|
+
return (_lua_assign(context.listing, int(operands[0]), instruction.pc, value, source),)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _lua_get_field(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
342
|
+
operands = instruction.operands
|
|
343
|
+
if len(operands) < 3:
|
|
344
|
+
return None
|
|
345
|
+
return (
|
|
346
|
+
_lua_assign(
|
|
347
|
+
context.listing,
|
|
348
|
+
int(operands[0]),
|
|
349
|
+
instruction.pc,
|
|
350
|
+
GetItem(
|
|
351
|
+
source=source,
|
|
352
|
+
obj=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
353
|
+
key=_constant_operand_expr(context.constants, operands[2], source),
|
|
354
|
+
),
|
|
355
|
+
source,
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _lua_get_i(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
361
|
+
operands = instruction.operands
|
|
362
|
+
if len(operands) < 3:
|
|
363
|
+
return None
|
|
364
|
+
return (
|
|
365
|
+
_lua_assign(
|
|
366
|
+
context.listing,
|
|
367
|
+
int(operands[0]),
|
|
368
|
+
instruction.pc,
|
|
369
|
+
GetItem(
|
|
370
|
+
source=source,
|
|
371
|
+
obj=Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source),
|
|
372
|
+
key=Const(value=int(operands[2]), source=source),
|
|
373
|
+
),
|
|
374
|
+
source,
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _lua_set_table(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
380
|
+
operands = instruction.operands
|
|
381
|
+
if len(operands) < 3:
|
|
382
|
+
return None
|
|
383
|
+
return (
|
|
384
|
+
Emit(
|
|
385
|
+
source=source,
|
|
386
|
+
statement=StoreItem(
|
|
387
|
+
source=source,
|
|
388
|
+
obj=Var(name=_read_register_name(context.listing, int(operands[0]), instruction.pc), source=source),
|
|
389
|
+
key=_operand_expr(context.listing, context.constants, operands[1], instruction.pc, source),
|
|
390
|
+
value=_operand_expr(context.listing, context.constants, operands[2], instruction.pc, source),
|
|
391
|
+
),
|
|
392
|
+
),
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _lua_set_tabup(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
397
|
+
operands = instruction.operands
|
|
398
|
+
if len(operands) < 3:
|
|
399
|
+
return None
|
|
400
|
+
return (
|
|
401
|
+
Emit(
|
|
402
|
+
source=source,
|
|
403
|
+
statement=StoreItem(
|
|
404
|
+
source=source,
|
|
405
|
+
obj=_lua_upvalue_expr(context.listing, operands[0], source),
|
|
406
|
+
key=_constant_operand_expr(context.constants, operands[1], source),
|
|
407
|
+
value=_operand_expr(context.listing, context.constants, operands[2], instruction.pc, source),
|
|
408
|
+
),
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _lua_set_i(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
414
|
+
operands = instruction.operands
|
|
415
|
+
if len(operands) < 3:
|
|
416
|
+
return None
|
|
417
|
+
return (
|
|
418
|
+
Emit(
|
|
419
|
+
source=source,
|
|
420
|
+
statement=StoreItem(
|
|
421
|
+
source=source,
|
|
422
|
+
obj=Var(name=_read_register_name(context.listing, int(operands[0]), instruction.pc), source=source),
|
|
423
|
+
key=Const(value=int(operands[1]), source=source),
|
|
424
|
+
value=_operand_expr(context.listing, context.constants, operands[2], instruction.pc, source),
|
|
425
|
+
),
|
|
426
|
+
),
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _lua_set_field(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
431
|
+
operands = instruction.operands
|
|
432
|
+
if len(operands) < 3:
|
|
433
|
+
return None
|
|
434
|
+
return (
|
|
435
|
+
Emit(
|
|
436
|
+
source=source,
|
|
437
|
+
statement=StoreItem(
|
|
438
|
+
source=source,
|
|
439
|
+
obj=Var(name=_read_register_name(context.listing, int(operands[0]), instruction.pc), source=source),
|
|
440
|
+
key=_constant_operand_expr(context.constants, operands[1], source),
|
|
441
|
+
value=_operand_expr(context.listing, context.constants, operands[2], instruction.pc, source),
|
|
442
|
+
),
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _lua_new_table(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
448
|
+
operands = instruction.operands
|
|
449
|
+
if len(operands) < 1:
|
|
450
|
+
return None
|
|
451
|
+
return (_lua_assign(context.listing, int(operands[0]), instruction.pc, MapLiteral(source=source), source),)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _lua_self(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
455
|
+
operands = instruction.operands
|
|
456
|
+
if len(operands) < 3:
|
|
457
|
+
return None
|
|
458
|
+
base = int(operands[0])
|
|
459
|
+
receiver = Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source)
|
|
460
|
+
method = GetItem(
|
|
461
|
+
source=source,
|
|
462
|
+
obj=receiver,
|
|
463
|
+
key=_constant_operand_expr(context.constants, operands[2], source),
|
|
464
|
+
)
|
|
465
|
+
return (
|
|
466
|
+
_lua_assign(context.listing, base + 1, instruction.pc, receiver, source),
|
|
467
|
+
_lua_assign(context.listing, base, instruction.pc, method, source),
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _lua_concat(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
472
|
+
operands = instruction.operands
|
|
473
|
+
if len(operands) < 2:
|
|
474
|
+
return None
|
|
475
|
+
target = int(operands[0])
|
|
476
|
+
if len(operands) == 2:
|
|
477
|
+
start = target
|
|
478
|
+
end = target + int(operands[1]) - 1
|
|
479
|
+
else:
|
|
480
|
+
start = int(operands[1])
|
|
481
|
+
end = int(operands[2])
|
|
482
|
+
if end < start:
|
|
483
|
+
return None
|
|
484
|
+
expr: Expr = Var(name=_read_register_name(context.listing, start, instruction.pc), source=source)
|
|
485
|
+
for register in range(start + 1, end + 1):
|
|
486
|
+
expr = BinaryOp(
|
|
487
|
+
source=source,
|
|
488
|
+
op="..",
|
|
489
|
+
left=expr,
|
|
490
|
+
right=Var(name=_read_register_name(context.listing, register, instruction.pc), source=source),
|
|
491
|
+
semantics="dynamic",
|
|
492
|
+
)
|
|
493
|
+
return (_lua_assign(context.listing, target, instruction.pc, expr, source),)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _lua_set_list(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
497
|
+
operands = instruction.operands
|
|
498
|
+
if len(operands) < 2:
|
|
499
|
+
return None
|
|
500
|
+
base = int(operands[0])
|
|
501
|
+
count = int(operands[1])
|
|
502
|
+
if count <= 0:
|
|
503
|
+
return ()
|
|
504
|
+
table = Var(name=_read_register_name(context.listing, base, instruction.pc), source=source)
|
|
505
|
+
return tuple(
|
|
506
|
+
Emit(
|
|
507
|
+
source=source,
|
|
508
|
+
statement=StoreItem(
|
|
509
|
+
source=source,
|
|
510
|
+
obj=table,
|
|
511
|
+
key=Const(value=index + 1, source=source),
|
|
512
|
+
value=Var(name=_read_register_name(context.listing, base + 1 + index, instruction.pc), source=source),
|
|
513
|
+
),
|
|
514
|
+
)
|
|
515
|
+
for index in range(count)
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _lua_forloop_effect(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
520
|
+
operands = instruction.operands
|
|
521
|
+
if not operands:
|
|
522
|
+
return None
|
|
523
|
+
base = int(operands[0])
|
|
524
|
+
index_name = _read_register_name(context.listing, base, instruction.pc)
|
|
525
|
+
step_name = _read_register_name(context.listing, base + 2, instruction.pc)
|
|
526
|
+
body_target = _lua_jump_target(instruction)
|
|
527
|
+
visible_index_name = _write_register_name(
|
|
528
|
+
context.listing,
|
|
529
|
+
base + 3,
|
|
530
|
+
body_target if body_target is not None else instruction.pc,
|
|
531
|
+
)
|
|
532
|
+
next_index = BinaryOp(
|
|
533
|
+
source=source,
|
|
534
|
+
op="+",
|
|
535
|
+
left=Var(name=index_name, source=source),
|
|
536
|
+
right=Var(name=step_name, source=source),
|
|
537
|
+
semantics="dynamic",
|
|
538
|
+
)
|
|
539
|
+
return (
|
|
540
|
+
AssignValue(
|
|
541
|
+
source=source,
|
|
542
|
+
name=index_name,
|
|
543
|
+
target=Var(name=index_name, source=source),
|
|
544
|
+
value=next_index,
|
|
545
|
+
),
|
|
546
|
+
AssignValue(
|
|
547
|
+
source=source,
|
|
548
|
+
name=visible_index_name,
|
|
549
|
+
target=Var(name=visible_index_name, source=source),
|
|
550
|
+
value=Var(name=index_name, source=source),
|
|
551
|
+
),
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _lua_forprep_effect(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
556
|
+
operands = instruction.operands
|
|
557
|
+
if not operands:
|
|
558
|
+
return None
|
|
559
|
+
base = int(operands[0])
|
|
560
|
+
index_name = _read_register_name(context.listing, base, instruction.pc)
|
|
561
|
+
step_name = _read_register_name(context.listing, base + 2, instruction.pc)
|
|
562
|
+
prepared_index = BinaryOp(
|
|
563
|
+
source=source,
|
|
564
|
+
op="-",
|
|
565
|
+
left=Var(name=index_name, source=source),
|
|
566
|
+
right=Var(name=step_name, source=source),
|
|
567
|
+
semantics="dynamic",
|
|
568
|
+
)
|
|
569
|
+
return (
|
|
570
|
+
AssignValue(
|
|
571
|
+
source=source,
|
|
572
|
+
name=index_name,
|
|
573
|
+
target=Var(name=index_name, source=source),
|
|
574
|
+
value=prepared_index,
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _lua_testset_effect(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
580
|
+
operands = instruction.operands
|
|
581
|
+
if len(operands) < 2:
|
|
582
|
+
return None
|
|
583
|
+
target_name = _write_register_name(context.listing, int(operands[0]), instruction.pc)
|
|
584
|
+
value = Var(name=_read_register_name(context.listing, int(operands[1]), instruction.pc), source=source)
|
|
585
|
+
return (
|
|
586
|
+
AssignValueOnBranch(
|
|
587
|
+
source=source,
|
|
588
|
+
name=target_name,
|
|
589
|
+
target=Var(name=target_name, source=source),
|
|
590
|
+
value=value,
|
|
591
|
+
branch="false",
|
|
592
|
+
),
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _lua_tforcall_effect(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
597
|
+
operands = instruction.operands
|
|
598
|
+
if len(operands) < 3:
|
|
599
|
+
return None
|
|
600
|
+
base = int(operands[0])
|
|
601
|
+
count = int(operands[2])
|
|
602
|
+
if count <= 0:
|
|
603
|
+
return ()
|
|
604
|
+
names = tuple(
|
|
605
|
+
_write_register_name(context.listing, register, instruction.pc)
|
|
606
|
+
for register in range(base + 4, base + 4 + count)
|
|
607
|
+
)
|
|
608
|
+
return (
|
|
609
|
+
AssignManyValues(
|
|
610
|
+
source=source,
|
|
611
|
+
names=names,
|
|
612
|
+
targets=tuple(Var(name=name, source=source) for name in names),
|
|
613
|
+
values=(
|
|
614
|
+
MultiReturn(
|
|
615
|
+
source=source,
|
|
616
|
+
value=Call(
|
|
617
|
+
source=source,
|
|
618
|
+
callee=Global(name="lua_generic_for_next", source=source),
|
|
619
|
+
args=(
|
|
620
|
+
Var(name=_read_register_name(context.listing, base, instruction.pc), source=source),
|
|
621
|
+
Var(name=_read_register_name(context.listing, base + 1, instruction.pc), source=source),
|
|
622
|
+
Var(name=_read_register_name(context.listing, base + 2, instruction.pc), source=source),
|
|
623
|
+
),
|
|
624
|
+
),
|
|
625
|
+
),
|
|
626
|
+
),
|
|
627
|
+
),
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _lua_tforloop_effect(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
632
|
+
operands = instruction.operands
|
|
633
|
+
if not operands:
|
|
634
|
+
return None
|
|
635
|
+
base = int(operands[0])
|
|
636
|
+
control_name = _write_register_name(context.listing, base + 2, instruction.pc)
|
|
637
|
+
value_name = _read_register_name(context.listing, base + 4, instruction.pc)
|
|
638
|
+
return (
|
|
639
|
+
AssignValueOnBranch(
|
|
640
|
+
source=source,
|
|
641
|
+
name=control_name,
|
|
642
|
+
target=Var(name=control_name, source=source),
|
|
643
|
+
value=Var(name=value_name, source=source),
|
|
644
|
+
branch="true",
|
|
645
|
+
),
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def _lua_vararg(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
650
|
+
operands = instruction.operands
|
|
651
|
+
if len(operands) < 2:
|
|
652
|
+
return None
|
|
653
|
+
base = int(operands[0])
|
|
654
|
+
count = int(operands[1]) - 1
|
|
655
|
+
varargs = Call(source=source, callee=Global(name="varargs", source=source), returns="unknown")
|
|
656
|
+
if count <= 0:
|
|
657
|
+
return (_lua_assign(context.listing, base, instruction.pc, varargs, source),)
|
|
658
|
+
return tuple(
|
|
659
|
+
_lua_assign(
|
|
660
|
+
context.listing,
|
|
661
|
+
base + index,
|
|
662
|
+
instruction.pc,
|
|
663
|
+
GetItem(source=source, obj=varargs, key=Const(value=index, source=source)),
|
|
664
|
+
source,
|
|
665
|
+
)
|
|
666
|
+
for index in range(count)
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _lua_closure(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
671
|
+
operands = instruction.operands
|
|
672
|
+
if len(operands) < 2:
|
|
673
|
+
return None
|
|
674
|
+
function_index = int(operands[1])
|
|
675
|
+
function_name = (
|
|
676
|
+
context.listing.child_function_names[function_index]
|
|
677
|
+
if 0 <= function_index < len(context.listing.child_function_names)
|
|
678
|
+
else f"<function_{function_index}>"
|
|
679
|
+
)
|
|
680
|
+
return (
|
|
681
|
+
_lua_assign(
|
|
682
|
+
context.listing,
|
|
683
|
+
int(operands[0]),
|
|
684
|
+
instruction.pc,
|
|
685
|
+
Global(name=function_name, source=source),
|
|
686
|
+
source,
|
|
687
|
+
),
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _lua_call(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
692
|
+
operands = instruction.operands
|
|
693
|
+
if len(operands) < 3:
|
|
694
|
+
return None
|
|
695
|
+
base = int(operands[0])
|
|
696
|
+
return_count = int(operands[2]) - 1
|
|
697
|
+
arg_count = int(operands[1]) - 1
|
|
698
|
+
if arg_count < 0:
|
|
699
|
+
arg_count = _lua_open_call_arg_count(context.listing, instruction)
|
|
700
|
+
args = tuple(
|
|
701
|
+
_lua_call_argument_expr(context.listing, register, instruction.pc, source)
|
|
702
|
+
for register in range(base + 1, base + 1 + arg_count)
|
|
703
|
+
)
|
|
704
|
+
call = Call(
|
|
705
|
+
source=source,
|
|
706
|
+
callee=Var(name=_read_register_name(context.listing, base, instruction.pc), source=source),
|
|
707
|
+
args=args,
|
|
708
|
+
returns=return_count if return_count >= 0 else "unknown",
|
|
709
|
+
)
|
|
710
|
+
if return_count == 0:
|
|
711
|
+
if instruction.opcode == "TAILCALL":
|
|
712
|
+
return (ReturnValues(source=source, values=(call,)),)
|
|
713
|
+
return (_lua_assign(context.listing, base, instruction.pc, call, source),)
|
|
714
|
+
if return_count > 1:
|
|
715
|
+
names = tuple(
|
|
716
|
+
_write_register_name(context.listing, register, instruction.pc)
|
|
717
|
+
for register in range(base, base + return_count)
|
|
718
|
+
)
|
|
719
|
+
return (
|
|
720
|
+
AssignManyValues(
|
|
721
|
+
source=source,
|
|
722
|
+
names=names,
|
|
723
|
+
targets=tuple(Var(name=name, source=source) for name in names),
|
|
724
|
+
values=(MultiReturn(source=source, value=call),),
|
|
725
|
+
),
|
|
726
|
+
)
|
|
727
|
+
return (_lua_assign(context.listing, base, instruction.pc, call, source),)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _lua_return1(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
731
|
+
operands = instruction.operands
|
|
732
|
+
if len(operands) < 1:
|
|
733
|
+
return None
|
|
734
|
+
return (
|
|
735
|
+
ReturnValues(
|
|
736
|
+
source=source,
|
|
737
|
+
values=(Var(name=_read_register_name(context.listing, int(operands[0]), instruction.pc), source=source),),
|
|
738
|
+
),
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def _lua_return_with_base_count(default_count: int):
|
|
743
|
+
def factory(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
744
|
+
operands = instruction.operands
|
|
745
|
+
if len(operands) < 1:
|
|
746
|
+
return None
|
|
747
|
+
start = int(operands[0])
|
|
748
|
+
return (
|
|
749
|
+
ReturnValues(
|
|
750
|
+
source=source,
|
|
751
|
+
values=tuple(
|
|
752
|
+
Var(name=_read_register_name(context.listing, register, instruction.pc), source=source)
|
|
753
|
+
for register in range(start, start + default_count)
|
|
754
|
+
),
|
|
755
|
+
),
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
return factory
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def _lua_return(context: LuaEffectContext, instruction, source: SourceRef) -> tuple | None:
|
|
762
|
+
operands = instruction.operands
|
|
763
|
+
if len(operands) < 1:
|
|
764
|
+
return None
|
|
765
|
+
start = int(operands[0])
|
|
766
|
+
count = int(operands[1]) - 1 if len(operands) >= 2 else 1
|
|
767
|
+
if count < 0:
|
|
768
|
+
count = 1
|
|
769
|
+
return (
|
|
770
|
+
ReturnValues(
|
|
771
|
+
source=source,
|
|
772
|
+
values=tuple(
|
|
773
|
+
Var(name=_read_register_name(context.listing, register, instruction.pc), source=source)
|
|
774
|
+
for register in range(start, start + count)
|
|
775
|
+
),
|
|
776
|
+
),
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
LUA_EFFECT_TABLE = VMEffectTable(
|
|
781
|
+
opcode_attr="opcode",
|
|
782
|
+
ignored=frozenset(IGNORED_OPS),
|
|
783
|
+
exact={
|
|
784
|
+
"LOADI": _lua_load_i,
|
|
785
|
+
"LOADF": _lua_load_i,
|
|
786
|
+
"LOADTRUE": _lua_load_const_value(True),
|
|
787
|
+
"LOADFALSE": _lua_load_const_value(False),
|
|
788
|
+
"LOADNIL": _lua_load_nil,
|
|
789
|
+
"LOADK": _lua_load_k,
|
|
790
|
+
"LOADKX": _lua_load_k,
|
|
791
|
+
"MOVE": _lua_move,
|
|
792
|
+
"GETUPVAL": lambda context, instruction, source: (
|
|
793
|
+
_lua_assign(
|
|
794
|
+
context.listing,
|
|
795
|
+
int(instruction.operands[0]),
|
|
796
|
+
instruction.pc,
|
|
797
|
+
_lua_upvalue_expr(context.listing, instruction.operands[1], source),
|
|
798
|
+
source,
|
|
799
|
+
),
|
|
800
|
+
) if len(instruction.operands) >= 2 else None,
|
|
801
|
+
"SETUPVAL": lambda context, instruction, source: (
|
|
802
|
+
Emit(
|
|
803
|
+
source=source,
|
|
804
|
+
statement=Assign(
|
|
805
|
+
source=source,
|
|
806
|
+
target=_lua_upvalue_expr(context.listing, instruction.operands[1], source),
|
|
807
|
+
value=Var(
|
|
808
|
+
name=_read_register_name(context.listing, int(instruction.operands[0]), instruction.pc),
|
|
809
|
+
source=source,
|
|
810
|
+
),
|
|
811
|
+
),
|
|
812
|
+
),
|
|
813
|
+
) if len(instruction.operands) >= 2 else None,
|
|
814
|
+
"UNM": _lua_unary("-"),
|
|
815
|
+
"BNOT": _lua_unary("~"),
|
|
816
|
+
"NOT": _lua_unary("not "),
|
|
817
|
+
"LEN": _lua_unary("#"),
|
|
818
|
+
**{opcode: _lua_binary(op) for opcode, op in BINARY_OPS.items()},
|
|
819
|
+
**{opcode: _lua_immediate_binary(op) for opcode, op in IMMEDIATE_BINARY_OPS.items()},
|
|
820
|
+
**{f"{opcode}K": _lua_constant_binary(op) for opcode, op in BINARY_OPS.items()},
|
|
821
|
+
"POW": _lua_binary("^"),
|
|
822
|
+
"BAND": _lua_binary("&"),
|
|
823
|
+
"BOR": _lua_binary("|"),
|
|
824
|
+
"BXOR": _lua_binary("~"),
|
|
825
|
+
"SHL": _lua_binary("<<"),
|
|
826
|
+
"SHR": _lua_binary(">>"),
|
|
827
|
+
"POWK": _lua_constant_binary("^"),
|
|
828
|
+
"BANDK": _lua_constant_binary("&"),
|
|
829
|
+
"BORK": _lua_constant_binary("|"),
|
|
830
|
+
"BXORK": _lua_constant_binary("~"),
|
|
831
|
+
"SHRI": _lua_shift_immediate(">>"),
|
|
832
|
+
"SHLI": _lua_shift_immediate("<<", immediate_on_left=True),
|
|
833
|
+
"GETTABLE": _lua_get_table,
|
|
834
|
+
"GETTABUP": _lua_get_tabup,
|
|
835
|
+
"GETFIELD": _lua_get_field,
|
|
836
|
+
"GETI": _lua_get_i,
|
|
837
|
+
"SETTABUP": _lua_set_tabup,
|
|
838
|
+
"SETTABLE": _lua_set_table,
|
|
839
|
+
"SETFIELD": _lua_set_field,
|
|
840
|
+
"SETI": _lua_set_i,
|
|
841
|
+
"NEWTABLE": _lua_new_table,
|
|
842
|
+
"SELF": _lua_self,
|
|
843
|
+
"CONCAT": _lua_concat,
|
|
844
|
+
"SETLIST": _lua_set_list,
|
|
845
|
+
"VARARG": _lua_vararg,
|
|
846
|
+
"CLOSURE": _lua_closure,
|
|
847
|
+
"CALL": _lua_call,
|
|
848
|
+
"TAILCALL": _lua_call,
|
|
849
|
+
"FORPREP": _lua_forprep_effect,
|
|
850
|
+
"FORLOOP": _lua_forloop_effect,
|
|
851
|
+
"TFORPREP": _lua_no_effect,
|
|
852
|
+
"TFORCALL": _lua_tforcall_effect,
|
|
853
|
+
"TFORLOOP": _lua_tforloop_effect,
|
|
854
|
+
"TESTSET": _lua_testset_effect,
|
|
855
|
+
"RETURN1": _lua_return1,
|
|
856
|
+
"RETURN0": lambda _context, _instruction, source: (ReturnValues(source=source, values=()),),
|
|
857
|
+
"RETURN2": _lua_return_with_base_count(2),
|
|
858
|
+
"RETURNI": _lua_return_with_base_count(1),
|
|
859
|
+
"RETURNK": _lua_return_with_base_count(1),
|
|
860
|
+
"RETURN": _lua_return,
|
|
861
|
+
},
|
|
862
|
+
fallback=_lua_unknown_opcode_effect,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def lift_lua_chunk(chunk: LuaChunk, metadata: dict) -> ModuleIR:
|
|
867
|
+
functions: tuple[FunctionIR, ...]
|
|
868
|
+
if chunk.functions:
|
|
869
|
+
root, next_index = _lift_lua_function_tree(chunk.functions, 0)
|
|
870
|
+
if next_index != len(chunk.functions):
|
|
871
|
+
raise ValueError("incomplete Lua function tree reconstruction")
|
|
872
|
+
functions = (root,)
|
|
873
|
+
else:
|
|
874
|
+
functions = ()
|
|
875
|
+
|
|
876
|
+
if not functions:
|
|
877
|
+
functions = (
|
|
878
|
+
empty_vm_function(VMFunctionSpec(name="<chunk>", params=(), frontend="lua", instruction_count=0)),
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
return assemble_vm_module(
|
|
882
|
+
name=chunk.filename or "<lua-chunk>",
|
|
883
|
+
source_language="lua",
|
|
884
|
+
metadata={
|
|
885
|
+
"frontend": metadata,
|
|
886
|
+
"bytecode_format": "luac",
|
|
887
|
+
"lua_version": chunk.header.version_label,
|
|
888
|
+
"lua_disassembly": chunk.disassembly,
|
|
889
|
+
},
|
|
890
|
+
functions=functions,
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def _lift_lua_function_tree(
|
|
895
|
+
listings: tuple[LuaFunctionListing, ...],
|
|
896
|
+
index: int,
|
|
897
|
+
) -> tuple[FunctionIR, int]:
|
|
898
|
+
listing = listings[index]
|
|
899
|
+
function_ir = recover_vm_function(
|
|
900
|
+
_lua_function_spec(listing),
|
|
901
|
+
lambda: lift_lua_function(listing),
|
|
902
|
+
raw=tuple(
|
|
903
|
+
f"{instruction.pc}: {instruction.opcode} {' '.join(instruction.operands)}".strip()
|
|
904
|
+
for instruction in listing.instructions
|
|
905
|
+
),
|
|
906
|
+
)
|
|
907
|
+
next_index = index + 1
|
|
908
|
+
nested_functions: list[FunctionIR] = []
|
|
909
|
+
for _ in range(listing.child_function_count):
|
|
910
|
+
nested_function, next_index = _lift_lua_function_tree(listings, next_index)
|
|
911
|
+
nested_functions.append(nested_function)
|
|
912
|
+
return replace(function_ir, nested_functions=tuple(nested_functions)), next_index
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def lift_lua_function(listing: LuaFunctionListing) -> FunctionIR | None:
|
|
916
|
+
constants = {constant.index: constant.value for constant in listing.constants}
|
|
917
|
+
local_names = {
|
|
918
|
+
local.name
|
|
919
|
+
for local in listing.locals
|
|
920
|
+
if local.slot >= listing.param_count and _looks_user_named(local.name)
|
|
921
|
+
}
|
|
922
|
+
steps = tuple(_lua_bytecode_step(listing, constants, instruction) for instruction in listing.instructions)
|
|
923
|
+
return lift_vm_step_function(
|
|
924
|
+
_lua_function_spec(listing, tuple(sorted(local_names))),
|
|
925
|
+
steps,
|
|
926
|
+
profile=_lua_region_profile(steps, listing.instructions),
|
|
927
|
+
stateful_callbacks=VMStatefulCallbacks(
|
|
928
|
+
initial_locals=lambda: {},
|
|
929
|
+
lift_linear=lambda start, end, locals, stack: _lua_linear_state(
|
|
930
|
+
listing,
|
|
931
|
+
constants,
|
|
932
|
+
listing.instructions,
|
|
933
|
+
start,
|
|
934
|
+
end,
|
|
935
|
+
locals,
|
|
936
|
+
stack,
|
|
937
|
+
),
|
|
938
|
+
branch_condition=lambda branch, stack: _lua_branch_condition(
|
|
939
|
+
_lua_instruction_for_step(listing.instructions, branch),
|
|
940
|
+
constants,
|
|
941
|
+
listing,
|
|
942
|
+
stack,
|
|
943
|
+
),
|
|
944
|
+
branch_stack_width=lambda branch: _lua_branch_stack_width(_lua_instruction_for_step(listing.instructions, branch)),
|
|
945
|
+
),
|
|
946
|
+
raw_window=lambda index: _lua_raw_instruction_window(listing.instructions, index),
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def _lua_region_profile(
|
|
951
|
+
steps: tuple[VMBytecodeStep, ...],
|
|
952
|
+
instructions: tuple[object, ...],
|
|
953
|
+
) -> VMRegionProfile[VMBytecodeStep]:
|
|
954
|
+
return build_hint_region_profile(
|
|
955
|
+
steps,
|
|
956
|
+
frontend="lua",
|
|
957
|
+
opcode_classes=LUA_REGION_OPCODE_CLASSES,
|
|
958
|
+
raw_window=lambda index: _lua_raw_instruction_window(instructions, index),
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def _lua_linear_state(
|
|
963
|
+
listing: LuaFunctionListing,
|
|
964
|
+
constants: dict[int, object],
|
|
965
|
+
instructions: tuple[object, ...],
|
|
966
|
+
start: int,
|
|
967
|
+
end: int,
|
|
968
|
+
initial_locals: dict[str, Expr],
|
|
969
|
+
initial_stack: tuple[Expr, ...],
|
|
970
|
+
) -> VMLinearState | None:
|
|
971
|
+
steps = tuple(_lua_bytecode_step(listing, constants, instruction) for instruction in instructions[start:end])
|
|
972
|
+
result = lift_steps(steps, initial_locals=initial_locals, initial_stack=initial_stack)
|
|
973
|
+
if result.state.diagnostics:
|
|
974
|
+
return None
|
|
975
|
+
if result.stopped_at is not None and result.state.terminator is None:
|
|
976
|
+
return None
|
|
977
|
+
if result.state.terminator is not None and (
|
|
978
|
+
result.stopped_at is None
|
|
979
|
+
or result.stopped_at.source.offset != steps[-1].source.offset
|
|
980
|
+
):
|
|
981
|
+
return None
|
|
982
|
+
return VMLinearState(
|
|
983
|
+
locals=result.state.locals,
|
|
984
|
+
stack=tuple(result.state.stack),
|
|
985
|
+
statements=tuple(result.state.statements),
|
|
986
|
+
terminator=result.state.terminator,
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def _lua_instruction_for_step(instructions: tuple[object, ...], step: VMBytecodeStep):
|
|
991
|
+
for instruction in instructions:
|
|
992
|
+
if instruction.pc == step.source.offset:
|
|
993
|
+
return instruction
|
|
994
|
+
return instructions[0]
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
def _lua_branch_stack_width(_instruction) -> int:
|
|
998
|
+
return 0
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def _lua_branch_condition(
|
|
1002
|
+
instruction,
|
|
1003
|
+
constants: dict[int, object],
|
|
1004
|
+
listing: LuaFunctionListing,
|
|
1005
|
+
_stack: tuple[Expr, ...],
|
|
1006
|
+
) -> Expr | None:
|
|
1007
|
+
source = SourceRef(frontend="lua", offset=instruction.pc, line=instruction.line)
|
|
1008
|
+
operands = instruction.operands
|
|
1009
|
+
if instruction.opcode in COMPARISON_OPS and len(operands) >= 3:
|
|
1010
|
+
condition: Expr = BinaryOp(
|
|
1011
|
+
source=source,
|
|
1012
|
+
op=COMPARISON_OPS[instruction.opcode],
|
|
1013
|
+
left=Var(name=_read_register_name(listing, int(operands[0]), instruction.pc), source=source),
|
|
1014
|
+
right=Var(name=_read_register_name(listing, int(operands[1]), instruction.pc), source=source),
|
|
1015
|
+
semantics="dynamic",
|
|
1016
|
+
)
|
|
1017
|
+
if len(operands) >= 3 and int(operands[2]) != 0:
|
|
1018
|
+
condition = UnaryOp(source=source, op="not ", value=condition)
|
|
1019
|
+
return condition
|
|
1020
|
+
if instruction.opcode == "EQK" and len(operands) >= 2:
|
|
1021
|
+
condition = BinaryOp(
|
|
1022
|
+
source=source,
|
|
1023
|
+
op="==",
|
|
1024
|
+
left=Var(name=_read_register_name(listing, int(operands[0]), instruction.pc), source=source),
|
|
1025
|
+
right=_constant_operand_expr(constants, operands[1], source),
|
|
1026
|
+
semantics="dynamic",
|
|
1027
|
+
)
|
|
1028
|
+
if len(operands) >= 3 and int(operands[2]) != 0:
|
|
1029
|
+
condition = UnaryOp(source=source, op="not ", value=condition)
|
|
1030
|
+
return condition
|
|
1031
|
+
if instruction.opcode in IMMEDIATE_COMPARISON_OPS and len(operands) >= 2:
|
|
1032
|
+
condition = BinaryOp(
|
|
1033
|
+
source=source,
|
|
1034
|
+
op=IMMEDIATE_COMPARISON_OPS[instruction.opcode],
|
|
1035
|
+
left=Var(name=_read_register_name(listing, int(operands[0]), instruction.pc), source=source),
|
|
1036
|
+
right=Const(value=int(operands[1]), source=source),
|
|
1037
|
+
semantics="dynamic",
|
|
1038
|
+
)
|
|
1039
|
+
if len(operands) >= 3 and int(operands[2]) != 0:
|
|
1040
|
+
condition = UnaryOp(source=source, op="not ", value=condition)
|
|
1041
|
+
return condition
|
|
1042
|
+
if instruction.opcode == "TESTSET" and len(operands) >= 2:
|
|
1043
|
+
value = Var(name=_read_register_name(listing, int(operands[1]), instruction.pc), source=source)
|
|
1044
|
+
if len(operands) >= 3 and int(operands[2]) != 0:
|
|
1045
|
+
return UnaryOp(source=source, op="not ", value=value)
|
|
1046
|
+
return value
|
|
1047
|
+
if instruction.opcode == "TEST" and operands:
|
|
1048
|
+
value = Var(name=_read_register_name(listing, int(operands[0]), instruction.pc), source=source)
|
|
1049
|
+
if len(operands) >= 2 and int(operands[1]) != 0:
|
|
1050
|
+
return UnaryOp(source=source, op="not ", value=value)
|
|
1051
|
+
return value
|
|
1052
|
+
if instruction.opcode == "FORLOOP" and operands:
|
|
1053
|
+
base = int(operands[0])
|
|
1054
|
+
index = Var(name=_read_register_name(listing, base, instruction.pc), source=source)
|
|
1055
|
+
limit = Var(name=_read_register_name(listing, base + 1, instruction.pc), source=source)
|
|
1056
|
+
step = Var(name=_read_register_name(listing, base + 2, instruction.pc), source=source)
|
|
1057
|
+
return Call(
|
|
1058
|
+
source=source,
|
|
1059
|
+
callee=Global(name="vm_forloop_continues", source=source),
|
|
1060
|
+
args=(index, limit, step),
|
|
1061
|
+
)
|
|
1062
|
+
if instruction.opcode == "TFORLOOP" and operands:
|
|
1063
|
+
base = int(operands[0])
|
|
1064
|
+
return BinaryOp(
|
|
1065
|
+
source=source,
|
|
1066
|
+
op="!=",
|
|
1067
|
+
left=Var(name=_read_register_name(listing, base + 4, instruction.pc), source=source),
|
|
1068
|
+
right=Const(value=None, source=source),
|
|
1069
|
+
semantics="dynamic",
|
|
1070
|
+
)
|
|
1071
|
+
return None
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
def _local_for_slot_at_pc(
|
|
1075
|
+
listing: LuaFunctionListing,
|
|
1076
|
+
slot: int,
|
|
1077
|
+
pc: int,
|
|
1078
|
+
):
|
|
1079
|
+
for local in listing.locals:
|
|
1080
|
+
if local.slot == slot and local.start_pc <= pc < local.end_pc:
|
|
1081
|
+
return local
|
|
1082
|
+
return None
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def _lua_bytecode_step(
|
|
1086
|
+
listing: LuaFunctionListing,
|
|
1087
|
+
constants: dict[int, object],
|
|
1088
|
+
instruction,
|
|
1089
|
+
) -> VMBytecodeStep:
|
|
1090
|
+
source = SourceRef(frontend="lua", offset=instruction.pc, line=instruction.line, detail=f"pc={instruction.pc}")
|
|
1091
|
+
decoded = _lua_decoded_instruction(instruction, source)
|
|
1092
|
+
return VMBytecodeStep(
|
|
1093
|
+
opcode=decoded.opcode,
|
|
1094
|
+
source=source,
|
|
1095
|
+
effects=_lua_instruction_effects(listing, constants, instruction, source),
|
|
1096
|
+
raw=decoded.raw,
|
|
1097
|
+
decoded=decoded,
|
|
1098
|
+
hints=_lua_instruction_hints(listing, instruction, source),
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def _lua_decoded_instruction(instruction, source: SourceRef) -> VMDecodedInstruction:
|
|
1103
|
+
return VMDecodedInstruction(
|
|
1104
|
+
opcode=instruction.opcode,
|
|
1105
|
+
source=source,
|
|
1106
|
+
operands=tuple(VMOperand(role=_lua_operand_role(operand), value=operand, text=operand) for operand in instruction.operands),
|
|
1107
|
+
raw=f"{instruction.pc}: {instruction.opcode} {' '.join(instruction.operands)}".strip(),
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
def _lua_operand_role(operand: str):
|
|
1112
|
+
if operand.endswith("k"):
|
|
1113
|
+
return "constant"
|
|
1114
|
+
if operand.lstrip("-").isdigit():
|
|
1115
|
+
return "register"
|
|
1116
|
+
return "raw"
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def _lua_instruction_hints(listing: LuaFunctionListing, instruction, source: SourceRef) -> tuple[VMHint, ...]:
|
|
1120
|
+
if instruction.opcode in CONDITIONAL_OPS:
|
|
1121
|
+
return (
|
|
1122
|
+
VMHint(
|
|
1123
|
+
kind="branch-target",
|
|
1124
|
+
source=source,
|
|
1125
|
+
target=instruction.pc + 2,
|
|
1126
|
+
label=instruction.opcode,
|
|
1127
|
+
detail="target-if-true",
|
|
1128
|
+
flow="conditional",
|
|
1129
|
+
),
|
|
1130
|
+
)
|
|
1131
|
+
if instruction.opcode not in JUMP_OPS:
|
|
1132
|
+
return ()
|
|
1133
|
+
if instruction.opcode == "TFORLOOP":
|
|
1134
|
+
target = _lua_tforloop_body_target(listing, instruction)
|
|
1135
|
+
if target is None:
|
|
1136
|
+
return ()
|
|
1137
|
+
return (
|
|
1138
|
+
VMHint(
|
|
1139
|
+
kind="loop-backedge",
|
|
1140
|
+
source=source,
|
|
1141
|
+
target=target,
|
|
1142
|
+
label=instruction.opcode,
|
|
1143
|
+
detail="target-if-true",
|
|
1144
|
+
flow="conditional",
|
|
1145
|
+
),
|
|
1146
|
+
)
|
|
1147
|
+
target = _lua_jump_target(instruction)
|
|
1148
|
+
kind = "loop-backedge" if target is not None and target <= instruction.pc else "branch-target"
|
|
1149
|
+
detail = "target-if-true" if instruction.opcode == "FORLOOP" else None
|
|
1150
|
+
flow = "conditional" if instruction.opcode in {"FORLOOP", "TFORLOOP"} else "unconditional"
|
|
1151
|
+
return (VMHint(kind=kind, source=source, target=target, label=instruction.opcode, detail=detail, flow=flow),)
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
def _lua_tforloop_body_target(listing: LuaFunctionListing, instruction) -> int | None:
|
|
1155
|
+
if not instruction.operands:
|
|
1156
|
+
return None
|
|
1157
|
+
base = instruction.operands[0]
|
|
1158
|
+
for prior in reversed(tuple(candidate for candidate in listing.instructions if candidate.pc < instruction.pc)):
|
|
1159
|
+
if prior.opcode == "TFORPREP" and prior.operands and prior.operands[0] == base:
|
|
1160
|
+
return prior.pc + 1
|
|
1161
|
+
return None
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
def _lua_comment_target(instruction) -> int | None:
|
|
1165
|
+
comment = getattr(instruction, "comment", None)
|
|
1166
|
+
if not comment:
|
|
1167
|
+
return None
|
|
1168
|
+
match = re.search(r"\bto\s+(-?\d+)\b", comment)
|
|
1169
|
+
if match is None:
|
|
1170
|
+
return None
|
|
1171
|
+
return int(match.group(1))
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
def _lua_jump_target(instruction) -> int | None:
|
|
1175
|
+
target = _lua_comment_target(instruction)
|
|
1176
|
+
if target is not None or not instruction.operands:
|
|
1177
|
+
return target
|
|
1178
|
+
try:
|
|
1179
|
+
return instruction.pc + 1 + int(instruction.operands[-1])
|
|
1180
|
+
except ValueError:
|
|
1181
|
+
return None
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
def _lua_function_spec(listing: LuaFunctionListing, local_names: tuple[str, ...] = ()) -> VMFunctionSpec:
|
|
1185
|
+
return VMFunctionSpec(
|
|
1186
|
+
name=listing.inferred_name or "<function>",
|
|
1187
|
+
params=tuple(_parameter_name(listing, index) for index in range(listing.param_count)),
|
|
1188
|
+
frontend="lua",
|
|
1189
|
+
instruction_count=len(listing.instructions),
|
|
1190
|
+
local_names=local_names,
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def _lua_upvalue_expr(listing: LuaFunctionListing, index: object, source: SourceRef) -> CapturedVar:
|
|
1195
|
+
upvalue_index = int(index)
|
|
1196
|
+
if 0 <= upvalue_index < len(listing.upvalues):
|
|
1197
|
+
name = listing.upvalues[upvalue_index].name
|
|
1198
|
+
if name:
|
|
1199
|
+
return CapturedVar(name=name, source=source)
|
|
1200
|
+
return CapturedVar(name=f"upvalue_{index}", source=source)
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
def _is_lua_env_upvalue(expr: Expr) -> bool:
|
|
1204
|
+
return isinstance(expr, CapturedVar) and expr.name == "_ENV"
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def _lua_instruction_effects(
|
|
1208
|
+
listing: LuaFunctionListing,
|
|
1209
|
+
constants: dict[int, object],
|
|
1210
|
+
instruction,
|
|
1211
|
+
source: SourceRef,
|
|
1212
|
+
) -> tuple[object, ...] | None:
|
|
1213
|
+
return LUA_EFFECT_TABLE.effects_for(LuaEffectContext(listing=listing, constants=constants), instruction, source)
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
def _lua_assign(
|
|
1217
|
+
listing: LuaFunctionListing,
|
|
1218
|
+
register: int,
|
|
1219
|
+
pc: int,
|
|
1220
|
+
value: Expr,
|
|
1221
|
+
source: SourceRef,
|
|
1222
|
+
) -> AssignValue:
|
|
1223
|
+
target_name = _write_register_name(listing, register, pc)
|
|
1224
|
+
return AssignValue(
|
|
1225
|
+
source=source,
|
|
1226
|
+
name=target_name,
|
|
1227
|
+
target=Var(name=target_name, source=source),
|
|
1228
|
+
value=value,
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
def _local_starting_near_write(
|
|
1233
|
+
listing: LuaFunctionListing,
|
|
1234
|
+
slot: int,
|
|
1235
|
+
pc: int,
|
|
1236
|
+
):
|
|
1237
|
+
current = _local_for_slot_at_pc(listing, slot, pc)
|
|
1238
|
+
if current is not None and current.end_pc <= pc:
|
|
1239
|
+
for local in listing.locals:
|
|
1240
|
+
if local.slot == slot and local.start_pc == pc + 1 and _looks_user_named(local.name):
|
|
1241
|
+
return local
|
|
1242
|
+
if current is not None and _looks_user_named(current.name):
|
|
1243
|
+
shadowing = (
|
|
1244
|
+
local
|
|
1245
|
+
for local in listing.locals
|
|
1246
|
+
if local.slot == slot
|
|
1247
|
+
and local.start_pc <= pc < local.end_pc
|
|
1248
|
+
and _looks_user_named(local.name)
|
|
1249
|
+
)
|
|
1250
|
+
return max(shadowing, key=lambda local: local.start_pc, default=current)
|
|
1251
|
+
active = _local_for_slot_at_pc(listing, slot, pc + 1)
|
|
1252
|
+
if active is not None:
|
|
1253
|
+
return active
|
|
1254
|
+
for lookahead_pc in (pc + 1, pc + 2):
|
|
1255
|
+
for local in listing.locals:
|
|
1256
|
+
if local.slot == slot and local.start_pc == lookahead_pc and _looks_user_named(local.name):
|
|
1257
|
+
return local
|
|
1258
|
+
table_local = _local_starting_after_table_initializer(listing, slot, pc)
|
|
1259
|
+
if table_local is not None:
|
|
1260
|
+
return table_local
|
|
1261
|
+
if current is not None:
|
|
1262
|
+
return current
|
|
1263
|
+
for lookahead_pc in (pc + 1, pc + 2):
|
|
1264
|
+
for local in listing.locals:
|
|
1265
|
+
if local.slot == slot and local.start_pc == lookahead_pc:
|
|
1266
|
+
return local
|
|
1267
|
+
return None
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
def _parameter_name(listing: LuaFunctionListing, register: int) -> str:
|
|
1271
|
+
local = _local_for_slot_at_pc(listing, register, 1)
|
|
1272
|
+
if local is not None:
|
|
1273
|
+
return _local_display_name(local, register)
|
|
1274
|
+
return f"arg{register}"
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
def _read_register_name(listing: LuaFunctionListing, register: int, pc: int) -> str:
|
|
1278
|
+
local = _local_for_slot_at_pc(listing, register, pc)
|
|
1279
|
+
if local is not None:
|
|
1280
|
+
return _local_display_name(local, register)
|
|
1281
|
+
initializer_local = _table_initializer_local_for_slot_at_pc(listing, register, pc)
|
|
1282
|
+
if initializer_local is not None:
|
|
1283
|
+
return _local_display_name(initializer_local, register)
|
|
1284
|
+
initializer_local = _local_starting_after_setlist_read(listing, register, pc)
|
|
1285
|
+
if initializer_local is not None:
|
|
1286
|
+
return _local_display_name(initializer_local, register)
|
|
1287
|
+
if register < listing.param_count:
|
|
1288
|
+
return _parameter_name(listing, register)
|
|
1289
|
+
return f"r{register}"
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def _write_register_name(listing: LuaFunctionListing, register: int, pc: int) -> str:
|
|
1293
|
+
local = _local_starting_near_write(listing, register, pc)
|
|
1294
|
+
if local is not None:
|
|
1295
|
+
if local.start_pc > pc + 1 and _next_instruction_reads_register(listing, pc, register):
|
|
1296
|
+
return f"r{register}"
|
|
1297
|
+
return _local_display_name(local, register)
|
|
1298
|
+
if register < listing.param_count:
|
|
1299
|
+
return _parameter_name(listing, register)
|
|
1300
|
+
return f"r{register}"
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
def _local_display_name(local, register: int) -> str:
|
|
1304
|
+
if _looks_user_named(local.name):
|
|
1305
|
+
return local.name
|
|
1306
|
+
return f"r{register}"
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
def _local_starting_after_table_initializer(
|
|
1310
|
+
listing: LuaFunctionListing,
|
|
1311
|
+
slot: int,
|
|
1312
|
+
pc: int,
|
|
1313
|
+
):
|
|
1314
|
+
instruction = _instruction_at_pc(listing, pc)
|
|
1315
|
+
if instruction is None or instruction.opcode != "NEWTABLE" or not instruction.operands:
|
|
1316
|
+
return None
|
|
1317
|
+
if int(instruction.operands[0]) != slot:
|
|
1318
|
+
return None
|
|
1319
|
+
# Debug-local ranges begin after the final initializer instruction. A
|
|
1320
|
+
# table literal can contain more than the old fixed lookahead window, so
|
|
1321
|
+
# find that boundary from bytecode facts instead of guessing a source-size
|
|
1322
|
+
# limit. Values for SETLIST may be prepared in other registers, but the
|
|
1323
|
+
# table register itself must never be overwritten or cross a control-flow
|
|
1324
|
+
# boundary before its debug-local range starts.
|
|
1325
|
+
saw_table_write = False
|
|
1326
|
+
for candidate_instruction in listing.instructions:
|
|
1327
|
+
if candidate_instruction.pc <= pc:
|
|
1328
|
+
continue
|
|
1329
|
+
local = next(
|
|
1330
|
+
(
|
|
1331
|
+
item
|
|
1332
|
+
for item in listing.locals
|
|
1333
|
+
if item.slot == slot
|
|
1334
|
+
and item.start_pc == candidate_instruction.pc
|
|
1335
|
+
and _looks_user_named(item.name)
|
|
1336
|
+
),
|
|
1337
|
+
None,
|
|
1338
|
+
)
|
|
1339
|
+
if local is not None:
|
|
1340
|
+
return local if saw_table_write else None
|
|
1341
|
+
if _is_table_initializer_write_for_slot(candidate_instruction, slot):
|
|
1342
|
+
saw_table_write = True
|
|
1343
|
+
continue
|
|
1344
|
+
if candidate_instruction.opcode in CONTROL_OPS:
|
|
1345
|
+
return None
|
|
1346
|
+
if _instruction_writes_register(candidate_instruction, slot):
|
|
1347
|
+
return None
|
|
1348
|
+
return None
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def _is_table_initializer_write_for_slot(instruction, slot: int) -> bool:
|
|
1352
|
+
"""Return whether an instruction can extend one freshly allocated table."""
|
|
1353
|
+
|
|
1354
|
+
if instruction.opcode == "EXTRAARG":
|
|
1355
|
+
return True
|
|
1356
|
+
if instruction.opcode not in {"SETFIELD", "SETI", "SETTABLE", "SETLIST"}:
|
|
1357
|
+
return False
|
|
1358
|
+
return bool(instruction.operands) and int(instruction.operands[0]) == slot
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
def _table_initializer_local_for_slot_at_pc(
|
|
1362
|
+
listing: LuaFunctionListing,
|
|
1363
|
+
slot: int,
|
|
1364
|
+
pc: int,
|
|
1365
|
+
):
|
|
1366
|
+
"""Resolve a table register while its debug-local range has not opened."""
|
|
1367
|
+
|
|
1368
|
+
current = _instruction_at_pc(listing, pc)
|
|
1369
|
+
if current is None or not _is_table_initializer_write_for_slot(current, slot):
|
|
1370
|
+
return None
|
|
1371
|
+
for prior in reversed(listing.instructions):
|
|
1372
|
+
if prior.pc >= pc:
|
|
1373
|
+
continue
|
|
1374
|
+
if prior.opcode != "NEWTABLE" or not prior.operands or int(prior.operands[0]) != slot:
|
|
1375
|
+
continue
|
|
1376
|
+
local = _local_starting_after_table_initializer(listing, slot, prior.pc)
|
|
1377
|
+
if local is not None and prior.pc < pc < local.start_pc:
|
|
1378
|
+
return local
|
|
1379
|
+
return None
|
|
1380
|
+
return None
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
def _instruction_writes_register(instruction, register: int) -> bool:
|
|
1384
|
+
if not instruction.operands or not instruction.operands[0].lstrip("-").isdigit():
|
|
1385
|
+
return False
|
|
1386
|
+
if instruction.opcode in {"CALL", "TAILCALL"}:
|
|
1387
|
+
# A call can write its base and, when it returns multiple values,
|
|
1388
|
+
# following registers as well. Treat an overlapping base as a
|
|
1389
|
+
# clobber; this is conservative and avoids renaming a reused slot.
|
|
1390
|
+
return int(instruction.operands[0]) <= register
|
|
1391
|
+
if instruction.opcode in LUA_REGISTER_DEST_OPS:
|
|
1392
|
+
return int(instruction.operands[0]) == register
|
|
1393
|
+
return instruction.opcode == "TESTSET" and int(instruction.operands[0]) == register
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def _local_starting_after_setlist_read(
|
|
1397
|
+
listing: LuaFunctionListing,
|
|
1398
|
+
slot: int,
|
|
1399
|
+
pc: int,
|
|
1400
|
+
):
|
|
1401
|
+
instruction = _instruction_at_pc(listing, pc)
|
|
1402
|
+
if instruction is None or instruction.opcode != "SETLIST" or not instruction.operands:
|
|
1403
|
+
return None
|
|
1404
|
+
if int(instruction.operands[0]) != slot:
|
|
1405
|
+
return None
|
|
1406
|
+
return _nearest_user_local_start(listing, slot, pc + 1, pc + 1)
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
def _nearest_user_local_start(
|
|
1410
|
+
listing: LuaFunctionListing,
|
|
1411
|
+
slot: int,
|
|
1412
|
+
start_pc: int,
|
|
1413
|
+
end_pc: int,
|
|
1414
|
+
):
|
|
1415
|
+
candidates = [
|
|
1416
|
+
local
|
|
1417
|
+
for local in listing.locals
|
|
1418
|
+
if local.slot == slot and start_pc <= local.start_pc <= end_pc and _looks_user_named(local.name)
|
|
1419
|
+
]
|
|
1420
|
+
if not candidates:
|
|
1421
|
+
return None
|
|
1422
|
+
return min(candidates, key=lambda local: local.start_pc)
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
def _instruction_at_pc(listing: LuaFunctionListing, pc: int):
|
|
1426
|
+
return next((instruction for instruction in listing.instructions if instruction.pc == pc), None)
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
def _next_instruction_reads_register(
|
|
1430
|
+
listing: LuaFunctionListing,
|
|
1431
|
+
pc: int,
|
|
1432
|
+
register: int,
|
|
1433
|
+
) -> bool:
|
|
1434
|
+
next_instruction = next((instruction for instruction in listing.instructions if instruction.pc == pc + 1), None)
|
|
1435
|
+
if next_instruction is None:
|
|
1436
|
+
return False
|
|
1437
|
+
return register in _read_registers_for_instruction(next_instruction)
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
def _read_registers_for_instruction(instruction) -> set[int]:
|
|
1441
|
+
operands = instruction.operands
|
|
1442
|
+
if not operands:
|
|
1443
|
+
return set()
|
|
1444
|
+
opcode = instruction.opcode
|
|
1445
|
+
result: set[int] = set()
|
|
1446
|
+
if opcode in {"MOVE", "UNM", "BNOT", "NOT", "LEN"} and len(operands) >= 2:
|
|
1447
|
+
result.add(int(operands[1]))
|
|
1448
|
+
elif opcode in {"GETTABLE", "GETFIELD", "GETI"} and len(operands) >= 2:
|
|
1449
|
+
result.add(int(operands[1]))
|
|
1450
|
+
if opcode == "GETTABLE" and len(operands) >= 3 and not operands[2].endswith("k"):
|
|
1451
|
+
result.add(int(operands[2]))
|
|
1452
|
+
elif opcode in {"SETTABLE", "SETFIELD", "SETI"}:
|
|
1453
|
+
result.add(int(operands[0]))
|
|
1454
|
+
if opcode == "SETTABLE" and len(operands) >= 2 and not operands[1].endswith("k"):
|
|
1455
|
+
result.add(int(operands[1]))
|
|
1456
|
+
if len(operands) >= 3 and not operands[2].endswith("k"):
|
|
1457
|
+
result.add(int(operands[2]))
|
|
1458
|
+
elif opcode == "SELF" and len(operands) >= 2:
|
|
1459
|
+
result.add(int(operands[1]))
|
|
1460
|
+
elif opcode == "CONCAT":
|
|
1461
|
+
start = int(operands[1]) if len(operands) >= 3 else int(operands[0])
|
|
1462
|
+
end = int(operands[2]) if len(operands) >= 3 else int(operands[0]) + int(operands[1]) - 1
|
|
1463
|
+
result.update(range(start, end + 1))
|
|
1464
|
+
elif opcode in BINARY_OPS or opcode in {
|
|
1465
|
+
"BAND",
|
|
1466
|
+
"BOR",
|
|
1467
|
+
"BXOR",
|
|
1468
|
+
"SHL",
|
|
1469
|
+
"SHR",
|
|
1470
|
+
"POW",
|
|
1471
|
+
}:
|
|
1472
|
+
if len(operands) >= 3:
|
|
1473
|
+
result.update((int(operands[1]), int(operands[2])))
|
|
1474
|
+
elif opcode in IMMEDIATE_BINARY_OPS or opcode in {"SHRI", "SHLI"}:
|
|
1475
|
+
if len(operands) >= 2:
|
|
1476
|
+
result.add(int(operands[1]))
|
|
1477
|
+
elif opcode.endswith("K") and opcode not in {"LOADK", "LOADKX"}:
|
|
1478
|
+
if len(operands) >= 2:
|
|
1479
|
+
result.add(int(operands[1]))
|
|
1480
|
+
elif opcode in {"CALL", "TAILCALL"} and len(operands) >= 2:
|
|
1481
|
+
base = int(operands[0])
|
|
1482
|
+
arg_count = int(operands[1]) - 1
|
|
1483
|
+
result.add(base)
|
|
1484
|
+
if arg_count >= 0:
|
|
1485
|
+
result.update(range(base + 1, base + 1 + arg_count))
|
|
1486
|
+
elif opcode in COMPARISON_OPS and len(operands) >= 2:
|
|
1487
|
+
result.update((int(operands[0]), int(operands[1])))
|
|
1488
|
+
elif opcode in set(IMMEDIATE_COMPARISON_OPS) | {"TEST"} and operands:
|
|
1489
|
+
result.add(int(operands[0]))
|
|
1490
|
+
elif opcode == "RETURN" and len(operands) >= 2:
|
|
1491
|
+
start = int(operands[0])
|
|
1492
|
+
count = int(operands[1]) - 1
|
|
1493
|
+
if count >= 0:
|
|
1494
|
+
result.update(range(start, start + count))
|
|
1495
|
+
elif opcode in {"RETURN1", "RETURNI", "RETURNK"}:
|
|
1496
|
+
result.add(int(operands[0]))
|
|
1497
|
+
return result
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
def _looks_user_named(name: str) -> bool:
|
|
1501
|
+
return not (name.startswith("(") and name.endswith(")"))
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
def _is_root_chunk(listing: LuaFunctionListing) -> bool:
|
|
1505
|
+
return listing.inferred_name == "<chunk>" and listing.line_start == 0
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
def _constant_operand_expr(
|
|
1509
|
+
constants: dict[int, object],
|
|
1510
|
+
operand: str,
|
|
1511
|
+
source: SourceRef,
|
|
1512
|
+
) -> Expr:
|
|
1513
|
+
constant_index = int(operand.removesuffix("k"))
|
|
1514
|
+
return Const(value=constants.get(constant_index), source=source)
|
|
1515
|
+
|
|
1516
|
+
|
|
1517
|
+
def _operand_expr(
|
|
1518
|
+
listing: LuaFunctionListing,
|
|
1519
|
+
constants: dict[int, object],
|
|
1520
|
+
operand: str,
|
|
1521
|
+
pc: int,
|
|
1522
|
+
source: SourceRef,
|
|
1523
|
+
) -> Expr:
|
|
1524
|
+
if operand.endswith("k"):
|
|
1525
|
+
return _constant_operand_expr(constants, operand, source)
|
|
1526
|
+
return Var(name=_read_register_name(listing, int(operand), pc), source=source)
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
def _lua_call_argument_expr(
|
|
1530
|
+
listing: LuaFunctionListing,
|
|
1531
|
+
register: int,
|
|
1532
|
+
pc: int,
|
|
1533
|
+
source: SourceRef,
|
|
1534
|
+
) -> Expr:
|
|
1535
|
+
register_name = _read_register_name(listing, register, pc)
|
|
1536
|
+
return Var(name=register_name, source=source)
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
def _lua_open_call_arg_count(listing: LuaFunctionListing, instruction) -> int:
|
|
1540
|
+
base = int(instruction.operands[0])
|
|
1541
|
+
previous = next(
|
|
1542
|
+
(prior for prior in reversed(listing.instructions) if prior.pc < instruction.pc),
|
|
1543
|
+
None,
|
|
1544
|
+
)
|
|
1545
|
+
if (
|
|
1546
|
+
previous is not None
|
|
1547
|
+
and previous.opcode == "CALL"
|
|
1548
|
+
and len(previous.operands) >= 3
|
|
1549
|
+
and int(previous.operands[0]) == base + 1
|
|
1550
|
+
and int(previous.operands[2]) == 0
|
|
1551
|
+
):
|
|
1552
|
+
return 1
|
|
1553
|
+
highest = base
|
|
1554
|
+
for prior in listing.instructions:
|
|
1555
|
+
if prior.pc >= instruction.pc:
|
|
1556
|
+
break
|
|
1557
|
+
if not prior.operands:
|
|
1558
|
+
continue
|
|
1559
|
+
if not prior.operands[0].lstrip("-").isdigit():
|
|
1560
|
+
continue
|
|
1561
|
+
if prior.opcode not in LUA_REGISTER_DEST_OPS:
|
|
1562
|
+
continue
|
|
1563
|
+
dest = int(prior.operands[0])
|
|
1564
|
+
if dest > highest:
|
|
1565
|
+
highest = dest
|
|
1566
|
+
return max(0, highest - base)
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
LUA_REGISTER_DEST_OPS = frozenset(
|
|
1570
|
+
{
|
|
1571
|
+
"ADDI",
|
|
1572
|
+
"ADDK",
|
|
1573
|
+
"ADD",
|
|
1574
|
+
"BAND",
|
|
1575
|
+
"BANDK",
|
|
1576
|
+
"BOR",
|
|
1577
|
+
"BORK",
|
|
1578
|
+
"BXOR",
|
|
1579
|
+
"BXORK",
|
|
1580
|
+
"CALL",
|
|
1581
|
+
"CONCAT",
|
|
1582
|
+
"CLOSURE",
|
|
1583
|
+
"DIV",
|
|
1584
|
+
"DIVK",
|
|
1585
|
+
"EQ",
|
|
1586
|
+
"EQI",
|
|
1587
|
+
"EQK",
|
|
1588
|
+
"GETFIELD",
|
|
1589
|
+
"GETI",
|
|
1590
|
+
"GETTABLE",
|
|
1591
|
+
"GETTABUP",
|
|
1592
|
+
"GETUPVAL",
|
|
1593
|
+
"IDIV",
|
|
1594
|
+
"IDIVK",
|
|
1595
|
+
"LOADFALSE",
|
|
1596
|
+
"LOADF",
|
|
1597
|
+
"LOADI",
|
|
1598
|
+
"LOADK",
|
|
1599
|
+
"LOADKX",
|
|
1600
|
+
"LOADNIL",
|
|
1601
|
+
"LOADTRUE",
|
|
1602
|
+
"MOD",
|
|
1603
|
+
"MODK",
|
|
1604
|
+
"MUL",
|
|
1605
|
+
"MULK",
|
|
1606
|
+
"NEWTABLE",
|
|
1607
|
+
"POW",
|
|
1608
|
+
"POWK",
|
|
1609
|
+
"SELF",
|
|
1610
|
+
"SHL",
|
|
1611
|
+
"SHLI",
|
|
1612
|
+
"SHR",
|
|
1613
|
+
"SHRI",
|
|
1614
|
+
"SUB",
|
|
1615
|
+
"SUBK",
|
|
1616
|
+
"VARARG",
|
|
1617
|
+
}
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1620
|
+
|
|
1621
|
+
def _lua_raw_instruction_window(
|
|
1622
|
+
instructions: tuple[object, ...],
|
|
1623
|
+
index: int,
|
|
1624
|
+
radius: int = 3,
|
|
1625
|
+
) -> tuple[str, ...]:
|
|
1626
|
+
start = max(0, index - radius)
|
|
1627
|
+
end = min(len(instructions), index + radius + 1)
|
|
1628
|
+
return tuple(
|
|
1629
|
+
f"{instruction.pc}: {instruction.opcode} {' '.join(instruction.operands)}".strip()
|
|
1630
|
+
for instruction in instructions[start:end]
|
|
1631
|
+
)
|