unidecompiler-plugin-dotnet-cli 0.1.0__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.
@@ -0,0 +1,686 @@
1
+ from __future__ import annotations
2
+
3
+ from unidecompiler.core.effects import (
4
+ Binary,
5
+ BuildArrayCall,
6
+ BuildCall,
7
+ CallTopAs,
8
+ DuplicateTop,
9
+ InvokeMember,
10
+ LoadIndirect,
11
+ LoadAttr,
12
+ LoadItem,
13
+ LoadItemAddress,
14
+ LoadLocal,
15
+ Pop,
16
+ Push,
17
+ RaiseTop,
18
+ ReturnTop,
19
+ StoreAttr,
20
+ StoreIndirect,
21
+ StoreItemEffect,
22
+ StoreLocal,
23
+ StoreStaticMember,
24
+ UnknownOpcode,
25
+ )
26
+ from unidecompiler.core.ir import BinaryOp, Const, Expr, Global, IndirectRef, SourceRef, Var
27
+ from unidecompiler.core.vm_bytecode import VMBytecodeStep
28
+ from unidecompiler.core.vm_effect_table import VMEffectRule, VMEffectTable
29
+ from unidecompiler.core.vm_function import VMFunctionSpec, lift_steps, lift_vm_step_function, recover_vm_function
30
+ from unidecompiler.core.vm_hints import VMHint
31
+ from unidecompiler.core.vm_module import assemble_vm_module
32
+ from unidecompiler.core.vm_operands import VMDecodedInstruction, VMOperand
33
+ from unidecompiler.core.vm_region import (
34
+ VMLinearState,
35
+ VMRegionOpcodeClasses,
36
+ VMRegionProfile,
37
+ VMStatefulCallbacks,
38
+ build_hint_region_profile,
39
+ )
40
+ from unidecompiler_plugin_dotnet_cli.assembly import (
41
+ DotNetAssembly,
42
+ DotNetInstruction,
43
+ DotNetMethodListing,
44
+ )
45
+
46
+
47
+ DOTNET_FRONTEND_ID = "dotnet-cli"
48
+
49
+ BINARY_OPS = {
50
+ "add": "+",
51
+ "add.ovf": "+",
52
+ "add.ovf.un": "+",
53
+ "sub": "-",
54
+ "sub.ovf": "-",
55
+ "sub.ovf.un": "-",
56
+ "mul": "*",
57
+ "mul.ovf": "*",
58
+ "mul.ovf.un": "*",
59
+ "div": "/",
60
+ "div.un": "/",
61
+ "rem": "%",
62
+ "rem.un": "%",
63
+ "and": "&",
64
+ "or": "|",
65
+ "xor": "^",
66
+ "shl": "<<",
67
+ "shr": ">>",
68
+ "shr.un": ">>",
69
+ }
70
+
71
+ COMPARE_OPS = {
72
+ "ceq": "==",
73
+ "cgt": ">",
74
+ "cgt.un": ">",
75
+ "clt": "<",
76
+ "clt.un": "<",
77
+ }
78
+
79
+ CONTROL_OPS = {
80
+ "br",
81
+ "br.s",
82
+ "brfalse",
83
+ "brfalse.s",
84
+ "brtrue",
85
+ "brtrue.s",
86
+ "beq",
87
+ "beq.s",
88
+ "bge",
89
+ "bge.s",
90
+ "bge.un",
91
+ "bge.un.s",
92
+ "bgt",
93
+ "bgt.s",
94
+ "bgt.un",
95
+ "bgt.un.s",
96
+ "ble",
97
+ "ble.s",
98
+ "ble.un",
99
+ "ble.un.s",
100
+ "blt",
101
+ "blt.s",
102
+ "blt.un",
103
+ "blt.un.s",
104
+ "bne.un",
105
+ "bne.un.s",
106
+ "leave",
107
+ "leave.s",
108
+ "switch",
109
+ }
110
+
111
+ DOTNET_CONDITIONAL_OPS = {
112
+ "brfalse": "truthy",
113
+ "brfalse.s": "truthy",
114
+ "brtrue": "falsey",
115
+ "brtrue.s": "falsey",
116
+ "beq": "!=",
117
+ "beq.s": "!=",
118
+ "bne.un": "==",
119
+ "bne.un.s": "==",
120
+ "bge": "<",
121
+ "bge.s": "<",
122
+ "bge.un": "<",
123
+ "bge.un.s": "<",
124
+ "bgt": "<=",
125
+ "bgt.s": "<=",
126
+ "bgt.un": "<=",
127
+ "bgt.un.s": "<=",
128
+ "ble": ">",
129
+ "ble.s": ">",
130
+ "ble.un": ">",
131
+ "ble.un.s": ">",
132
+ "blt": ">=",
133
+ "blt.s": ">=",
134
+ "blt.un": ">=",
135
+ "blt.un.s": ">=",
136
+ }
137
+
138
+ DOTNET_REGION_OPCODE_CLASSES = VMRegionOpcodeClasses(
139
+ control=frozenset(CONTROL_OPS),
140
+ jumps=frozenset({"br", "br.s", "leave", "leave.s"}),
141
+ forward_jumps=frozenset({"br", "br.s", "leave", "leave.s"}),
142
+ backward_jumps=frozenset({"br", "br.s", "leave", "leave.s"}),
143
+ conditional_jumps=frozenset(DOTNET_CONDITIONAL_OPS),
144
+ )
145
+
146
+ IGNORED_OPS = {
147
+ "nop",
148
+ "constrained.",
149
+ "readonly.",
150
+ "tail.",
151
+ "volatile.",
152
+ "unaligned.",
153
+ "prefix",
154
+ "castclass",
155
+ "box",
156
+ "unbox",
157
+ "unbox.any",
158
+ "conv.i",
159
+ "conv.i1",
160
+ "conv.i2",
161
+ "conv.i4",
162
+ "conv.i8",
163
+ "conv.u",
164
+ "conv.u1",
165
+ "conv.u2",
166
+ "conv.u4",
167
+ "conv.u8",
168
+ "conv.r4",
169
+ "conv.r8",
170
+ "conv.r.un",
171
+ "conv.ovf.i",
172
+ "conv.ovf.i.un",
173
+ "conv.ovf.i1.un",
174
+ "conv.ovf.i2.un",
175
+ "conv.ovf.i4.un",
176
+ "conv.ovf.i8.un",
177
+ "conv.ovf.u",
178
+ "conv.ovf.u.un",
179
+ "conv.ovf.u1.un",
180
+ "conv.ovf.u2.un",
181
+ "conv.ovf.u4.un",
182
+ "conv.ovf.u8.un",
183
+ "endfinally",
184
+ "endfilter",
185
+ "rethrow",
186
+ *CONTROL_OPS,
187
+ }
188
+
189
+
190
+ def _dotnet_no_effect(_method: DotNetMethodListing, _instruction: DotNetInstruction, _source: SourceRef) -> tuple:
191
+ return ()
192
+
193
+
194
+ def _dotnet_unknown_opcode_effect(
195
+ _method: DotNetMethodListing,
196
+ instruction: DotNetInstruction,
197
+ source: SourceRef,
198
+ ) -> tuple:
199
+ return (UnknownOpcode(source=source, opcode=instruction.opcode, raw=_dotnet_raw_instruction_line(instruction)),)
200
+
201
+
202
+ def _dotnet_binary(op: str):
203
+ def factory(_method: DotNetMethodListing, _instruction: DotNetInstruction, source: SourceRef) -> tuple:
204
+ return (Binary(source=source, op=op, semantics="static"),)
205
+
206
+ return factory
207
+
208
+
209
+ def _dotnet_compare(op: str):
210
+ def factory(_method: DotNetMethodListing, _instruction: DotNetInstruction, source: SourceRef) -> tuple:
211
+ return (Binary(source=source, op=op, semantics="static"),)
212
+
213
+ return factory
214
+
215
+
216
+ def _dotnet_constant_effect(_method: DotNetMethodListing, instruction: DotNetInstruction, source: SourceRef) -> tuple | None:
217
+ value = _constant_value(instruction)
218
+ if value is None:
219
+ return None
220
+ return (Push(source=source, value=Const(value=value, source=source)),)
221
+
222
+
223
+ def _dotnet_load_local_effect(method: DotNetMethodListing, instruction: DotNetInstruction, source: SourceRef) -> tuple | None:
224
+ index = _load_index(instruction.opcode, instruction.operands, "ldloc", include_address=True)
225
+ if index is None:
226
+ return None
227
+ name = _local_name(method, index)
228
+ return (LoadLocal(source=source, name=name, fallback=Var(name=name, source=source)),)
229
+
230
+
231
+ def _dotnet_store_local_effect(method: DotNetMethodListing, instruction: DotNetInstruction, source: SourceRef) -> tuple | None:
232
+ index = _load_index(instruction.opcode, instruction.operands, "stloc")
233
+ if index is None:
234
+ return None
235
+ name = _local_name(method, index)
236
+ return (StoreLocal(source=source, name=name, target=Var(name=name, source=source)),)
237
+
238
+
239
+ def _dotnet_load_arg_effect(method: DotNetMethodListing, instruction: DotNetInstruction, source: SourceRef) -> tuple | None:
240
+ index = _load_index(instruction.opcode, instruction.operands, "ldarg", include_address=True)
241
+ if index is None:
242
+ return None
243
+ name = _argument_name(method, index)
244
+ if instruction.opcode.startswith("ldarga"):
245
+ return (Push(source=source, value=IndirectRef(source=source, target=Var(name=name, source=source))),)
246
+ return (LoadLocal(source=source, name=name, fallback=Var(name=name, source=source)),)
247
+
248
+
249
+ def _dotnet_store_arg_effect(method: DotNetMethodListing, instruction: DotNetInstruction, source: SourceRef) -> tuple | None:
250
+ index = _load_index(instruction.opcode, instruction.operands, "starg")
251
+ if index is None:
252
+ return None
253
+ name = _argument_name(method, index)
254
+ return (StoreLocal(source=source, name=name, target=IndirectRef(source=source, target=Var(name=name, source=source))),)
255
+
256
+
257
+ def _dotnet_call(_method: DotNetMethodListing, instruction: DotNetInstruction, source: SourceRef) -> tuple:
258
+ if instruction.opcode in {"call", "callvirt", "newobj", "jmp"} and not instruction.member_name:
259
+ return _dotnet_unknown_opcode_effect(_method, instruction, source)
260
+ arg_count = instruction.arg_count or 0
261
+ returns = 0 if instruction.returns_void else "unknown"
262
+ if instruction.opcode == "newobj":
263
+ return (
264
+ BuildCall(
265
+ source=source,
266
+ callee=Global(name=instruction.member_name or _member_token_name(instruction.operands), source=source),
267
+ arg_count=arg_count,
268
+ returns="unknown",
269
+ ),
270
+ )
271
+ if instruction.is_static is False or instruction.opcode == "callvirt":
272
+ return (
273
+ InvokeMember(
274
+ source=source,
275
+ owner=instruction.owner_name or "",
276
+ member=instruction.member_name or _member_token_name(instruction.operands),
277
+ arg_count=arg_count,
278
+ static=False,
279
+ returns=returns,
280
+ ),
281
+ )
282
+ if instruction.owner_name and instruction.member_name:
283
+ return (
284
+ InvokeMember(
285
+ source=source,
286
+ owner=instruction.owner_name,
287
+ member=instruction.member_name,
288
+ arg_count=arg_count,
289
+ static=True,
290
+ returns=returns,
291
+ ),
292
+ )
293
+ return (
294
+ BuildCall(
295
+ source=source,
296
+ callee=Global(name=_member_token_name(instruction.operands), source=source),
297
+ arg_count=arg_count,
298
+ returns=returns,
299
+ ),
300
+ )
301
+
302
+
303
+ DOTNET_EFFECT_TABLE = VMEffectTable(
304
+ opcode_attr="opcode",
305
+ ignored=frozenset(IGNORED_OPS),
306
+ exact={
307
+ **{opcode: _dotnet_binary(op) for opcode, op in BINARY_OPS.items()},
308
+ **{opcode: _dotnet_compare(op) for opcode, op in COMPARE_OPS.items()},
309
+ "ldnull": lambda _method, _instruction, source: (Push(source=source, value=Const(value=None, source=source)),),
310
+ "dup": lambda _method, instruction, source: (DuplicateTop(source=source, materialized_name=f"local_stack_{instruction.offset}"),),
311
+ "pop": lambda _method, _instruction, source: (Pop(source=source, count=1, emit_calls=True),),
312
+ "neg": lambda _method, _instruction, source: (CallTopAs(source=source, callee_name="neg"),),
313
+ "not": lambda _method, _instruction, source: (CallTopAs(source=source, callee_name="bitnot"),),
314
+ "call": _dotnet_call,
315
+ "callvirt": _dotnet_call,
316
+ "newobj": _dotnet_call,
317
+ "jmp": _dotnet_call,
318
+ "ldftn": lambda _method, instruction, source: (Push(source=source, value=Global(name=_member_token_name(instruction.operands), source=source)),),
319
+ "ldvirtftn": lambda _method, instruction, source: (Push(source=source, value=Global(name=_member_token_name(instruction.operands), source=source)),),
320
+ "initobj": lambda _method, _instruction, source: (Pop(source=source, count=1, allow_missing=True),),
321
+ "arglist": lambda _method, _instruction, source: (Push(source=source, value=Global(name="arglist", source=source)),),
322
+ "ckfinite": lambda _method, _instruction, source: (CallTopAs(source=source, callee_name="check_finite"),),
323
+ "cpblk": lambda _method, _instruction, source: (Pop(source=source, count=3, allow_missing=True),),
324
+ "cpobj": lambda _method, _instruction, source: (Pop(source=source, count=2, allow_missing=True),),
325
+ "initblk": lambda _method, _instruction, source: (Pop(source=source, count=3, allow_missing=True),),
326
+ "localloc": lambda _method, _instruction, source: (BuildCall(source=source, callee=Global(name="localloc", source=source), arg_count=1),),
327
+ "mkrefany": lambda _method, instruction, source: (BuildCall(source=source, callee=Global(name=f"mkrefany<{instruction.operands or 'type'}>", source=source), arg_count=1),),
328
+ "refanytype": lambda _method, _instruction, source: (CallTopAs(source=source, callee_name="refanytype"),),
329
+ "refanyval": lambda _method, instruction, source: (BuildCall(source=source, callee=Global(name=f"refanyval<{instruction.operands or 'type'}>", source=source), arg_count=1),),
330
+ "sizeof": lambda _method, instruction, source: (Push(source=source, value=Global(name=f"sizeof<{instruction.operands or 'type'}>", source=source)),),
331
+ "ldstr": lambda _method, instruction, source: (Push(source=source, value=Const(value=instruction.operands, source=source)),),
332
+ "ldtoken": lambda _method, instruction, source: (Push(source=source, value=Global(name=instruction.operands, source=source)),),
333
+ "isinst": lambda _method, instruction, source: (BuildCall(source=source, callee=Global(name="instanceof", source=source), arg_count=1, returns=1),),
334
+ "ldobj": lambda _method, instruction, source: (Pop(source=source, count=1, allow_missing=True), Push(source=source, value=Global(name=_member_token_name(instruction.operands), source=source))),
335
+ "ldfld": lambda _method, instruction, source: (LoadAttr(source=source, attr=_member_name(instruction)),),
336
+ "ldflda": lambda _method, instruction, source: (LoadAttr(source=source, attr=_member_name(instruction)),),
337
+ "stfld": lambda _method, instruction, source: (StoreAttr(source=source, attr=_member_name(instruction)),),
338
+ "ldsfld": lambda _method, instruction, source: (Push(source=source, value=Global(name=_member_token_name(instruction.operands), source=source)),),
339
+ "ldsflda": lambda _method, instruction, source: (Push(source=source, value=Global(name=_member_token_name(instruction.operands), source=source)),),
340
+ "stsfld": lambda _method, instruction, source: (
341
+ StoreStaticMember(
342
+ source=source,
343
+ owner=_member_owner(instruction),
344
+ field_name=_member_name(instruction),
345
+ ),
346
+ ),
347
+ "ldlen": lambda _method, _instruction, source: (LoadAttr(source=source, attr="length"),),
348
+ "newarr": lambda _method, instruction, source: (BuildArrayCall(source=source, kind=instruction.operands or "array"),),
349
+ "throw": lambda _method, _instruction, source: (RaiseTop(source=source),),
350
+ "ret": lambda _method, _instruction, source: (ReturnTop(source=source, empty_is_void=True),),
351
+ },
352
+ rules=(
353
+ VMEffectRule(matches=lambda opcode, instruction: _constant_value(instruction) is not None, factory=_dotnet_constant_effect),
354
+ VMEffectRule(matches=lambda opcode, instruction: _load_index(opcode, instruction.operands, "ldloc", include_address=True) is not None, factory=_dotnet_load_local_effect),
355
+ VMEffectRule(matches=lambda opcode, instruction: _load_index(opcode, instruction.operands, "stloc") is not None, factory=_dotnet_store_local_effect),
356
+ VMEffectRule(matches=lambda opcode, instruction: _load_index(opcode, instruction.operands, "ldarg", include_address=True) is not None, factory=_dotnet_load_arg_effect),
357
+ VMEffectRule(matches=lambda opcode, instruction: _load_index(opcode, instruction.operands, "starg") is not None, factory=_dotnet_store_arg_effect),
358
+ VMEffectRule(matches=lambda opcode, _instruction: opcode.startswith("ldind."), factory=lambda _method, _instruction, source: (LoadIndirect(source=source),)),
359
+ VMEffectRule(matches=lambda opcode, _instruction: opcode.startswith("stind."), factory=lambda _method, _instruction, source: (StoreIndirect(source=source),)),
360
+ VMEffectRule(matches=lambda opcode, _instruction: opcode.startswith("ldelema"), factory=lambda _method, _instruction, source: (LoadItemAddress(source=source),)),
361
+ VMEffectRule(matches=lambda opcode, _instruction: opcode.startswith("ldelem"), factory=lambda _method, _instruction, source: (LoadItem(source=source),)),
362
+ VMEffectRule(matches=lambda opcode, _instruction: opcode.startswith("stelem"), factory=lambda _method, _instruction, source: (StoreItemEffect(source=source),)),
363
+ ),
364
+ fallback=_dotnet_unknown_opcode_effect,
365
+ )
366
+
367
+
368
+ def lift_dotnet_assembly(assembly: DotNetAssembly, metadata: dict) -> "ModuleIR":
369
+ return assemble_vm_module(
370
+ name=assembly.name,
371
+ source_language="dotnet",
372
+ metadata={"frontend": metadata, "bytecode_format": "cli-assembly"},
373
+ functions=tuple(_recover_dotnet_method(method) for method in assembly.methods),
374
+ )
375
+
376
+
377
+ def _recover_dotnet_method(method: DotNetMethodListing) -> "FunctionIR":
378
+ spec = VMFunctionSpec(
379
+ name=method.name,
380
+ params=tuple(_parameter_name(method, index) for index in range(method.param_count)),
381
+ frontend=DOTNET_FRONTEND_ID,
382
+ instruction_count=len(method.instructions),
383
+ metadata={"token": f"0x{method.token:08x}", "rva": method.rva},
384
+ )
385
+ return recover_vm_function(
386
+ spec,
387
+ lambda: lift_dotnet_method(method),
388
+ raw=tuple(_dotnet_raw_instruction_line(instruction) for instruction in method.instructions),
389
+ )
390
+
391
+
392
+ def lift_dotnet_method(method: DotNetMethodListing) -> "FunctionIR":
393
+ steps = _dotnet_bytecode_steps(method)
394
+ return lift_vm_step_function(
395
+ VMFunctionSpec(
396
+ name=method.name,
397
+ params=tuple(_parameter_name(method, index) for index in range(method.param_count)),
398
+ frontend=DOTNET_FRONTEND_ID,
399
+ instruction_count=len(method.instructions),
400
+ metadata={"token": f"0x{method.token:08x}", "rva": method.rva},
401
+ ),
402
+ steps,
403
+ profile=_dotnet_region_profile(steps, method.instructions),
404
+ stateful_callbacks=VMStatefulCallbacks(
405
+ initial_locals=lambda: _initial_locals(method),
406
+ lift_linear=lambda start, end, locals, stack: _dotnet_linear_state(method, method.instructions, start, end, locals, stack),
407
+ branch_condition=lambda branch, stack: _dotnet_branch_condition(_dotnet_instruction_for_step(method.instructions, branch), stack),
408
+ branch_stack_width=lambda branch: _dotnet_branch_stack_width(_dotnet_instruction_for_step(method.instructions, branch)),
409
+ ),
410
+ initial_locals=_initial_locals(method),
411
+ raw_window=lambda index: _dotnet_raw_instruction_window(method.instructions, index),
412
+ )
413
+
414
+
415
+ def _dotnet_region_profile(
416
+ steps: tuple[VMBytecodeStep, ...],
417
+ instructions: tuple[DotNetInstruction, ...],
418
+ ) -> VMRegionProfile[VMBytecodeStep]:
419
+ return build_hint_region_profile(
420
+ steps,
421
+ frontend=DOTNET_FRONTEND_ID,
422
+ opcode_classes=DOTNET_REGION_OPCODE_CLASSES,
423
+ raw_window=lambda index: _dotnet_raw_instruction_window(instructions, index),
424
+ )
425
+
426
+
427
+ def _dotnet_linear_state(
428
+ method: DotNetMethodListing,
429
+ instructions: tuple[DotNetInstruction, ...],
430
+ start: int,
431
+ end: int,
432
+ initial_locals: dict[str, Expr],
433
+ initial_stack: tuple[Expr, ...],
434
+ ) -> VMLinearState | None:
435
+ steps = tuple(_dotnet_bytecode_step(method, instruction) for instruction in instructions[start:end])
436
+ result = lift_steps(steps, initial_locals=initial_locals, initial_stack=initial_stack)
437
+ if result.state.diagnostics:
438
+ return None
439
+ if result.stopped_at is not None and result.state.terminator is None:
440
+ return None
441
+ if result.state.terminator is not None and result.stopped_at != steps[-1]:
442
+ return None
443
+ return VMLinearState(
444
+ locals=result.state.locals,
445
+ stack=tuple(result.state.stack),
446
+ statements=tuple(result.state.statements),
447
+ terminator=result.state.terminator,
448
+ )
449
+
450
+
451
+ def _dotnet_instruction_for_step(
452
+ instructions: tuple[DotNetInstruction, ...],
453
+ step: VMBytecodeStep,
454
+ ) -> DotNetInstruction:
455
+ for instruction in instructions:
456
+ if instruction.offset == step.source.offset:
457
+ return instruction
458
+ return instructions[0]
459
+
460
+
461
+ def _dotnet_branch_stack_width(instruction: DotNetInstruction) -> int:
462
+ if instruction.opcode == "switch":
463
+ return 1
464
+ op = DOTNET_CONDITIONAL_OPS.get(instruction.opcode)
465
+ return 2 if op in {"!=", "==", "<", "<=", ">", ">="} else 1
466
+
467
+
468
+ def _dotnet_branch_condition(instruction: DotNetInstruction, stack: tuple[Expr, ...]) -> Expr:
469
+ source = SourceRef(frontend=DOTNET_FRONTEND_ID, offset=instruction.offset)
470
+ op = DOTNET_CONDITIONAL_OPS[instruction.opcode]
471
+ if op == "truthy":
472
+ return BinaryOp(source=source, op="!=", left=stack[0], right=Const(value=0, source=source), semantics="static")
473
+ if op == "falsey":
474
+ return BinaryOp(source=source, op="==", left=stack[0], right=Const(value=0, source=source), semantics="static")
475
+ return BinaryOp(source=source, op=op, left=stack[0], right=stack[1], semantics="static")
476
+
477
+
478
+ def _dotnet_bytecode_step(method: DotNetMethodListing, instruction: DotNetInstruction) -> VMBytecodeStep:
479
+ source = SourceRef(frontend=DOTNET_FRONTEND_ID, offset=instruction.offset)
480
+ decoded = _dotnet_decoded_instruction(instruction, source)
481
+ return VMBytecodeStep(
482
+ opcode=decoded.opcode,
483
+ source=source,
484
+ effects=_dotnet_instruction_effects(method, instruction, source),
485
+ raw=decoded.raw,
486
+ decoded=decoded,
487
+ hints=_dotnet_instruction_hints(instruction, source),
488
+ )
489
+
490
+
491
+ def _dotnet_bytecode_steps(method: DotNetMethodListing) -> tuple[VMBytecodeStep, ...]:
492
+ steps: list[VMBytecodeStep] = []
493
+ instructions = method.instructions
494
+ for index, instruction in enumerate(instructions):
495
+ step = _dotnet_bytecode_step(method, instruction)
496
+ if _is_materialized_condition_branch(instructions, index):
497
+ step = VMBytecodeStep(
498
+ opcode=step.opcode,
499
+ source=step.source,
500
+ effects=step.effects,
501
+ raw=step.raw,
502
+ decoded=step.decoded,
503
+ hints=(*step.hints, VMHint(kind="materialized-condition", source=step.source, label=instruction.opcode)),
504
+ )
505
+ steps.append(step)
506
+ return tuple(steps)
507
+
508
+
509
+ def _is_materialized_condition_branch(instructions: tuple[DotNetInstruction, ...], index: int) -> bool:
510
+ if instructions[index].opcode not in {"brtrue", "brtrue.s", "brfalse", "brfalse.s"} or index < 2:
511
+ return False
512
+ return instructions[index - 1].opcode.startswith("ldloc") and instructions[index - 2].opcode.startswith("stloc")
513
+
514
+
515
+ def _dotnet_decoded_instruction(instruction: DotNetInstruction, source: SourceRef) -> VMDecodedInstruction:
516
+ operands = ()
517
+ if instruction.operands:
518
+ operands = (
519
+ VMOperand(
520
+ role=_dotnet_operand_role(instruction.opcode),
521
+ value=instruction.operands,
522
+ text=instruction.operands,
523
+ ),
524
+ )
525
+ return VMDecodedInstruction(
526
+ opcode=instruction.opcode,
527
+ source=source,
528
+ operands=operands,
529
+ raw=_dotnet_raw_instruction_line(instruction),
530
+ )
531
+
532
+
533
+ def _dotnet_instruction_effects(
534
+ method: DotNetMethodListing,
535
+ instruction: DotNetInstruction,
536
+ source: SourceRef,
537
+ ) -> tuple[object, ...] | None:
538
+ return DOTNET_EFFECT_TABLE.effects_for(method, instruction, source)
539
+
540
+
541
+ def _dotnet_operand_role(opcode: str):
542
+ if opcode in CONTROL_OPS or opcode == "switch":
543
+ return "target"
544
+ if "arg" in opcode or "loc" in opcode:
545
+ return "local"
546
+ if opcode.startswith(("call", "newobj", "ldfld", "stfld", "ldsfld", "stsfld")):
547
+ return "member"
548
+ if opcode.startswith("ldc") or opcode == "ldstr":
549
+ return "constant"
550
+ return "raw"
551
+
552
+
553
+ def _dotnet_instruction_hints(instruction: DotNetInstruction, source: SourceRef) -> tuple[VMHint, ...]:
554
+ if instruction.opcode not in CONTROL_OPS:
555
+ return ()
556
+ if instruction.opcode == "switch":
557
+ targets = _branch_targets(instruction)
558
+ default_target = _dotnet_switch_default_target(instruction)
559
+ hints = [VMHint(kind="default-target", source=source, target=default_target, label=instruction.opcode, flow="multiway")]
560
+ hints.extend(
561
+ VMHint(kind="case-target", source=source, target=target, value=index, label=instruction.opcode, flow="multiway")
562
+ for index, target in enumerate(targets)
563
+ )
564
+ return tuple(hints)
565
+ targets = _branch_targets(instruction)
566
+ flow = "unconditional" if instruction.opcode in {"br", "br.s", "leave", "leave.s"} else "conditional"
567
+ return tuple(
568
+ VMHint(
569
+ kind="loop-backedge" if target <= instruction.offset else "branch-target",
570
+ source=source,
571
+ target=target,
572
+ label=instruction.opcode,
573
+ flow=flow,
574
+ )
575
+ for target in targets
576
+ )
577
+
578
+
579
+ def _branch_targets(instruction: DotNetInstruction) -> tuple[int, ...]:
580
+ targets: list[int] = []
581
+ for raw in instruction.operands.split(","):
582
+ try:
583
+ targets.append(int(raw.strip()))
584
+ except ValueError:
585
+ continue
586
+ return tuple(targets)
587
+
588
+
589
+ def _dotnet_switch_default_target(instruction: DotNetInstruction) -> int | None:
590
+ if instruction.operand_kind != "InlineSwitch":
591
+ return None
592
+ count = len(_branch_targets(instruction))
593
+ return instruction.offset + 1 + 4 + (count * 4)
594
+
595
+
596
+ def _constant_value(instruction: DotNetInstruction):
597
+ opcode = instruction.opcode
598
+ if opcode == "ldc.i4.m1":
599
+ return -1
600
+ if opcode in {"ldc.i4.s", "ldc.i4", "ldc.i8"}:
601
+ try:
602
+ return int(instruction.operands)
603
+ except ValueError:
604
+ return None
605
+ if opcode.startswith("ldc.i4."):
606
+ try:
607
+ return int(opcode.removeprefix("ldc.i4."))
608
+ except ValueError:
609
+ return None
610
+ if opcode in {"ldc.r4", "ldc.r8"}:
611
+ try:
612
+ return float(instruction.operands)
613
+ except ValueError:
614
+ return None
615
+ return None
616
+
617
+
618
+ def _load_index(opcode: str, operands: str, family: str, *, include_address: bool = False) -> int | None:
619
+ if opcode == family or opcode == f"{family}.s":
620
+ try:
621
+ return int(operands.split()[0])
622
+ except (IndexError, ValueError):
623
+ return None
624
+ if include_address and (opcode == f"{family}a" or opcode == f"{family}a.s"):
625
+ try:
626
+ return int(operands.split()[0])
627
+ except (IndexError, ValueError):
628
+ return None
629
+ prefix = f"{family}."
630
+ if opcode.startswith(prefix):
631
+ suffix = opcode.removeprefix(prefix)
632
+ if suffix.isdigit():
633
+ return int(suffix)
634
+ return None
635
+
636
+
637
+ def _argument_name(method: DotNetMethodListing, index: int) -> str:
638
+ if not method.is_static and index == 0:
639
+ return "this"
640
+ parameter_index = index if method.is_static else index - 1
641
+ return _parameter_name(method, parameter_index) if 0 <= parameter_index < method.param_count else f"arg{index}"
642
+
643
+
644
+ def _parameter_name(_method: DotNetMethodListing, index: int) -> str:
645
+ return f"arg{index}"
646
+
647
+
648
+ def _local_name(_method: DotNetMethodListing, index: int) -> str:
649
+ return f"local{index}"
650
+
651
+
652
+ def _initial_locals(method: DotNetMethodListing) -> dict[str, Expr]:
653
+ locals_: dict[str, Expr] = {}
654
+ if not method.is_static:
655
+ locals_["this"] = Var(name="this", source=SourceRef(frontend=DOTNET_FRONTEND_ID, detail="arg:0"))
656
+ for index in range(method.param_count):
657
+ name = _parameter_name(method, index)
658
+ locals_[name] = Var(name=name, source=SourceRef(frontend=DOTNET_FRONTEND_ID, detail=f"arg:{index}"))
659
+ return locals_
660
+
661
+
662
+ def _member_token_name(operand: str) -> str:
663
+ return operand or "<metadata-token>"
664
+
665
+
666
+ def _member_name(instruction: DotNetInstruction) -> str:
667
+ return instruction.member_name or _member_token_name(instruction.operands)
668
+
669
+
670
+ def _member_owner(instruction: DotNetInstruction) -> str:
671
+ return instruction.owner_name or "<static>"
672
+
673
+
674
+ def _dotnet_raw_instruction_window(
675
+ instructions: tuple[DotNetInstruction, ...],
676
+ index: int,
677
+ radius: int = 3,
678
+ ) -> tuple[str, ...]:
679
+ start = max(0, index - radius)
680
+ end = min(len(instructions), index + radius + 1)
681
+ return tuple(_dotnet_raw_instruction_line(instruction) for instruction in instructions[start:end])
682
+
683
+
684
+ def _dotnet_raw_instruction_line(instruction: DotNetInstruction) -> str:
685
+ operands = f" {instruction.operands}" if instruction.operands else ""
686
+ return f"IL_{instruction.offset:04x}: {instruction.opcode}{operands}"