unidecompiler-plugin-python-pyc 0.1.1__tar.gz → 0.1.3__tar.gz
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_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/PKG-INFO +2 -2
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/pyproject.toml +2 -2
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/lifter.py +83 -126
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/pyc.py +65 -4
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/PKG-INFO +2 -2
- unidecompiler_plugin_python_pyc-0.1.3/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt +1 -0
- unidecompiler_plugin_python_pyc-0.1.1/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt +0 -1
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/README.md +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/setup.cfg +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/__init__.py +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/plugin.py +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/simulation.py +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/support.py +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/SOURCES.txt +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/dependency_links.txt +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/entry_points.txt +0 -0
- {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unidecompiler-plugin-python-pyc
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Python bytecode frontend plugin for unidecompiler
|
|
5
5
|
Author-email: Wker <1670133844@qq.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/Wker666/unidecompiler
|
|
|
9
9
|
Project-URL: Issues, https://github.com/Wker666/unidecompiler/issues
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: unidecompiler<0.2.0,>=0.1.
|
|
12
|
+
Requires-Dist: unidecompiler<0.2.0,>=0.1.6
|
|
13
13
|
|
|
14
14
|
# unidecompiler-plugin-python-pyc
|
|
15
15
|
|
{unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/pyproject.toml
RENAMED
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unidecompiler-plugin-python-pyc"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Python bytecode frontend plugin for unidecompiler"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-or-later"
|
|
11
11
|
authors = [{ name = "Wker", email = "1670133844@qq.com" }]
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
|
-
dependencies = ["unidecompiler>=0.1.
|
|
13
|
+
dependencies = ["unidecompiler>=0.1.6,<0.2.0"]
|
|
14
14
|
|
|
15
15
|
[project.urls]
|
|
16
16
|
Homepage = "https://github.com/Wker666/unidecompiler"
|
|
@@ -32,11 +32,13 @@ from unidecompiler.core.effects import (
|
|
|
32
32
|
Iterate,
|
|
33
33
|
MakeFunctionValue,
|
|
34
34
|
MergeMap,
|
|
35
|
+
OfferImplicitCallArguments,
|
|
35
36
|
Pop,
|
|
36
37
|
Push,
|
|
37
38
|
RaiseTop,
|
|
38
39
|
RaiseWithCause,
|
|
39
40
|
ReraiseTop,
|
|
41
|
+
ResumeValue,
|
|
40
42
|
ReturnTop,
|
|
41
43
|
ReturnVoid,
|
|
42
44
|
StoreAttr,
|
|
@@ -55,7 +57,6 @@ from unidecompiler.core.effects import (
|
|
|
55
57
|
)
|
|
56
58
|
from unidecompiler.core.vm_module import assemble_vm_module
|
|
57
59
|
from unidecompiler.core.ir import (
|
|
58
|
-
Assign,
|
|
59
60
|
BinaryOp,
|
|
60
61
|
CapturedVar,
|
|
61
62
|
Const,
|
|
@@ -79,18 +80,17 @@ from unidecompiler.core.vm_function import (
|
|
|
79
80
|
from unidecompiler.core.vm_bytecode import VMBytecodeStep
|
|
80
81
|
from unidecompiler.core.vm_hints import VMHint
|
|
81
82
|
from unidecompiler.core.vm_operands import VMDecodedInstruction, VMOperand
|
|
83
|
+
from unidecompiler.provenance import ByteRange
|
|
82
84
|
from unidecompiler.core.vm_region import (
|
|
83
85
|
VMLinearState,
|
|
84
86
|
VMRegionCallbacks,
|
|
87
|
+
VMRegionSlice,
|
|
85
88
|
VMRegionOpcodeClasses,
|
|
86
89
|
VMRegionProfile,
|
|
87
90
|
VMStatefulCallbacks,
|
|
88
91
|
build_hint_region_profile,
|
|
89
92
|
lift_control_region as lift_vm_control_region,
|
|
90
93
|
)
|
|
91
|
-
from unidecompiler.core.vm_structures import (
|
|
92
|
-
vm_unsupported,
|
|
93
|
-
)
|
|
94
94
|
from unidecompiler_plugin_python_pyc.pyc import PycCodeObject, PycExceptionRegion, PycModule
|
|
95
95
|
|
|
96
96
|
|
|
@@ -121,7 +121,6 @@ BINARY_SYMBOLS = {
|
|
|
121
121
|
COMPLEX_OPS = {
|
|
122
122
|
}
|
|
123
123
|
IGNORED_OPS = {
|
|
124
|
-
"RESUME",
|
|
125
124
|
"CACHE",
|
|
126
125
|
"NOP",
|
|
127
126
|
"EXTENDED_ARG",
|
|
@@ -129,7 +128,7 @@ IGNORED_OPS = {
|
|
|
129
128
|
"COPY_FREE_VARS",
|
|
130
129
|
}
|
|
131
130
|
PYTHON_REGION_OPCODE_CLASSES = VMRegionOpcodeClasses(
|
|
132
|
-
noise=frozenset({"END_FOR", "POP_TOP", "NOP"
|
|
131
|
+
noise=frozenset({"END_FOR", "POP_TOP", "NOP"}),
|
|
133
132
|
control=frozenset(
|
|
134
133
|
{
|
|
135
134
|
"POP_JUMP_IF_FALSE",
|
|
@@ -282,7 +281,10 @@ def _build_set(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
|
282
281
|
|
|
283
282
|
|
|
284
283
|
def _get_iter(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
285
|
-
return (
|
|
284
|
+
return (
|
|
285
|
+
Iterate(source=source),
|
|
286
|
+
OfferImplicitCallArguments(source=source, max_explicit_arg_count=0),
|
|
287
|
+
)
|
|
286
288
|
|
|
287
289
|
|
|
288
290
|
def _unpack_sequence(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
@@ -359,7 +361,10 @@ def _binary_slice(_context, _instruction, source: SourceRef) -> tuple[Effect, ..
|
|
|
359
361
|
|
|
360
362
|
|
|
361
363
|
def _store_slice(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
362
|
-
return (
|
|
364
|
+
return (
|
|
365
|
+
BuildCall(source=source, arg_count=2, callee=Global(name="slice", source=source)),
|
|
366
|
+
StoreItemEffect(source=source, order="value-obj-key"),
|
|
367
|
+
)
|
|
363
368
|
|
|
364
369
|
|
|
365
370
|
def _list_extend(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
@@ -442,8 +447,8 @@ def _map_add(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
|
442
447
|
return (StoreItemAtDepth(source=source, depth=_instruction_count(instruction)),)
|
|
443
448
|
|
|
444
449
|
|
|
445
|
-
def _set_add(_context,
|
|
446
|
-
return (SetAdd(source=source),)
|
|
450
|
+
def _set_add(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
451
|
+
return (SetAdd(source=source, depth=_instruction_count(instruction)),)
|
|
447
452
|
|
|
448
453
|
|
|
449
454
|
def _store_local(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
@@ -483,6 +488,13 @@ def _yield_value(_context, instruction, source: SourceRef) -> tuple[Effect, ...]
|
|
|
483
488
|
return (YieldTop(source=source, default=Const(value=None, source=source)),)
|
|
484
489
|
|
|
485
490
|
|
|
491
|
+
def _resume(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
492
|
+
# CPython's resume mode 5 continues a ``yield`` expression, whose value
|
|
493
|
+
# is supplied by the caller. Await/send modes manage their result in the
|
|
494
|
+
# surrounding protocol and must not manufacture an extra stack value.
|
|
495
|
+
return (ResumeValue(source=source),) if _instruction_count(instruction) == 5 else ()
|
|
496
|
+
|
|
497
|
+
|
|
486
498
|
def _send(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
|
|
487
499
|
return (Pop(source=source, count=1, allow_missing=True),)
|
|
488
500
|
|
|
@@ -604,6 +616,7 @@ PYTHON_EFFECT_TABLE = VMEffectTable(
|
|
|
604
616
|
"BUILD_TUPLE": _build_tuple,
|
|
605
617
|
"BUILD_SET": _build_set,
|
|
606
618
|
"SET_ADD": _set_add,
|
|
619
|
+
"RESUME": _resume,
|
|
607
620
|
"GET_ITER": _get_iter,
|
|
608
621
|
"GET_YIELD_FROM_ITER": _get_iter,
|
|
609
622
|
"UNPACK_SEQUENCE": _unpack_sequence,
|
|
@@ -683,8 +696,8 @@ def lift_code_object(code: PycCodeObject) -> FunctionIR:
|
|
|
683
696
|
_python_function_spec(code),
|
|
684
697
|
steps,
|
|
685
698
|
profile=_python_region_profile(steps, instructions),
|
|
686
|
-
|
|
687
|
-
|
|
699
|
+
callbacks_factory=lambda normalized_steps: _python_region_callbacks(code, normalized_steps),
|
|
700
|
+
stateful_callbacks_factory=lambda normalized_steps: _python_stateful_callbacks(code, normalized_steps),
|
|
688
701
|
initial_locals=_python_initial_locals(code),
|
|
689
702
|
raw_window=lambda index: _raw_instruction_window(instructions, index),
|
|
690
703
|
)
|
|
@@ -811,6 +824,7 @@ def _python_decoded_instruction(instruction, source: SourceRef) -> VMDecodedInst
|
|
|
811
824
|
source=source,
|
|
812
825
|
operands=tuple(operands),
|
|
813
826
|
raw=_raw_instruction_line(instruction),
|
|
827
|
+
artifact_range=(None if instruction.artifact_offset is None or instruction.size is None else ByteRange(instruction.artifact_offset, instruction.size)),
|
|
814
828
|
)
|
|
815
829
|
|
|
816
830
|
|
|
@@ -882,21 +896,40 @@ def _python_initial_locals(code: PycCodeObject) -> dict[str, Expr]:
|
|
|
882
896
|
|
|
883
897
|
def _python_region_callbacks(
|
|
884
898
|
code: PycCodeObject,
|
|
885
|
-
|
|
899
|
+
prepared_steps: tuple[VMBytecodeStep, ...],
|
|
886
900
|
) -> VMRegionCallbacks[VMBytecodeStep]:
|
|
901
|
+
def lift_slice(slice_start, slice_end, stack):
|
|
902
|
+
slice_steps = prepared_steps[slice_start:slice_end]
|
|
903
|
+
result = lift_steps(
|
|
904
|
+
slice_steps,
|
|
905
|
+
initial_locals=_python_initial_locals(code),
|
|
906
|
+
initial_stack=stack,
|
|
907
|
+
)
|
|
908
|
+
if result.state.diagnostics:
|
|
909
|
+
return VMRegionSlice(stopped_at=slice_start)
|
|
910
|
+
output: list[object] = list(result.state.statements)
|
|
911
|
+
if result.stopped_at is not None and result.state.terminator is None:
|
|
912
|
+
return VMRegionSlice(
|
|
913
|
+
statements=tuple(output),
|
|
914
|
+
stopped_at=slice_start + slice_steps.index(result.stopped_at),
|
|
915
|
+
)
|
|
916
|
+
if result.state.terminator is not None:
|
|
917
|
+
output.append(result.state.terminator)
|
|
918
|
+
return VMRegionSlice(statements=tuple(output))
|
|
919
|
+
|
|
920
|
+
def lift_expr(slice_start, slice_end, stack):
|
|
921
|
+
result = lift_steps(
|
|
922
|
+
prepared_steps[slice_start:slice_end],
|
|
923
|
+
initial_locals=_python_initial_locals(code),
|
|
924
|
+
initial_stack=stack,
|
|
925
|
+
)
|
|
926
|
+
if result.state.diagnostics or not result.state.stack:
|
|
927
|
+
return None
|
|
928
|
+
return result.state.stack[-1]
|
|
929
|
+
|
|
887
930
|
return VMRegionCallbacks(
|
|
888
|
-
lift_slice=
|
|
889
|
-
|
|
890
|
-
instructions[slice_start:slice_end],
|
|
891
|
-
[],
|
|
892
|
-
stack,
|
|
893
|
-
),
|
|
894
|
-
lift_expr=lambda slice_start, slice_end, stack: _lift_stack_expr(
|
|
895
|
-
code,
|
|
896
|
-
instructions[slice_start:slice_end],
|
|
897
|
-
[],
|
|
898
|
-
stack,
|
|
899
|
-
),
|
|
931
|
+
lift_slice=lift_slice,
|
|
932
|
+
lift_expr=lift_expr,
|
|
900
933
|
lift_iter_loop=lambda _get_iter_index, _iterable: None,
|
|
901
934
|
lift_async_iter_loop=lambda _prefix_start, _get_aiter_index, _region_end: None,
|
|
902
935
|
lift_comprehension=lambda _prefix_start, _get_iter_index, _region_end, _iterable: None,
|
|
@@ -905,45 +938,38 @@ def _python_region_callbacks(
|
|
|
905
938
|
|
|
906
939
|
def _python_stateful_callbacks(
|
|
907
940
|
code: PycCodeObject,
|
|
908
|
-
|
|
941
|
+
prepared_steps: tuple[VMBytecodeStep, ...],
|
|
909
942
|
) -> VMStatefulCallbacks[VMBytecodeStep]:
|
|
943
|
+
def lift_linear(start, end, locals_, stack):
|
|
944
|
+
slice_steps = prepared_steps[start:end]
|
|
945
|
+
result = lift_steps(
|
|
946
|
+
slice_steps,
|
|
947
|
+
initial_locals=locals_,
|
|
948
|
+
initial_stack=stack,
|
|
949
|
+
)
|
|
950
|
+
stopped_at_terminal_end = (
|
|
951
|
+
result.stopped_at is slice_steps[-1]
|
|
952
|
+
if slice_steps and result.state.terminator is not None
|
|
953
|
+
else False
|
|
954
|
+
)
|
|
955
|
+
if result.state.diagnostics or (
|
|
956
|
+
result.stopped_at is not None and not stopped_at_terminal_end
|
|
957
|
+
):
|
|
958
|
+
return None
|
|
959
|
+
return VMLinearState(
|
|
960
|
+
locals=result.state.locals,
|
|
961
|
+
stack=tuple(result.state.stack),
|
|
962
|
+
statements=tuple(result.state.statements),
|
|
963
|
+
terminator=result.state.terminator,
|
|
964
|
+
)
|
|
965
|
+
|
|
910
966
|
return VMStatefulCallbacks(
|
|
911
967
|
initial_locals=lambda: _python_initial_locals(code),
|
|
912
|
-
lift_linear=
|
|
913
|
-
instructions[start:end],
|
|
914
|
-
locals,
|
|
915
|
-
stack,
|
|
916
|
-
),
|
|
968
|
+
lift_linear=lift_linear,
|
|
917
969
|
branch_condition=_python_branch_condition,
|
|
918
970
|
)
|
|
919
971
|
|
|
920
972
|
|
|
921
|
-
def _python_linear_state(
|
|
922
|
-
instructions: tuple[object, ...],
|
|
923
|
-
initial_locals: dict[str, Expr],
|
|
924
|
-
initial_stack: tuple[Expr, ...],
|
|
925
|
-
) -> VMLinearState | None:
|
|
926
|
-
result = _run_python_stack_slice(
|
|
927
|
-
_PycLinearLocals(tuple(initial_locals)),
|
|
928
|
-
instructions,
|
|
929
|
-
dict(initial_locals),
|
|
930
|
-
initial_stack,
|
|
931
|
-
)
|
|
932
|
-
stopped_at_terminal_end = (
|
|
933
|
-
getattr(result.stopped_at, "source", None) == _python_source(instructions[-1])
|
|
934
|
-
if instructions and result.state.terminator is not None
|
|
935
|
-
else False
|
|
936
|
-
)
|
|
937
|
-
if result.state.diagnostics or (result.stopped_at is not None and not stopped_at_terminal_end):
|
|
938
|
-
return None
|
|
939
|
-
return VMLinearState(
|
|
940
|
-
locals=result.state.locals,
|
|
941
|
-
stack=tuple(result.state.stack),
|
|
942
|
-
statements=tuple(result.state.statements),
|
|
943
|
-
terminator=result.state.terminator,
|
|
944
|
-
)
|
|
945
|
-
|
|
946
|
-
|
|
947
973
|
def _python_branch_condition(branch: VMBytecodeStep, stack: tuple[Expr, ...]) -> Expr | None:
|
|
948
974
|
if not stack:
|
|
949
975
|
return None
|
|
@@ -960,11 +986,6 @@ def _python_branch_condition(branch: VMBytecodeStep, stack: tuple[Expr, ...]) ->
|
|
|
960
986
|
return condition
|
|
961
987
|
|
|
962
988
|
|
|
963
|
-
class _PycLinearLocals:
|
|
964
|
-
def __init__(self, varnames: tuple[str, ...]) -> None:
|
|
965
|
-
self.varnames = varnames
|
|
966
|
-
|
|
967
|
-
|
|
968
989
|
def _python_region_profile(
|
|
969
990
|
steps: tuple[VMBytecodeStep, ...],
|
|
970
991
|
instructions: tuple[object, ...],
|
|
@@ -997,70 +1018,6 @@ def _await_region_end(instructions: tuple[object, ...], start: int, end: int) ->
|
|
|
997
1018
|
return None
|
|
998
1019
|
|
|
999
1020
|
|
|
1000
|
-
def _lift_instruction_slice(
|
|
1001
|
-
code: PycCodeObject,
|
|
1002
|
-
instructions: tuple[object, ...],
|
|
1003
|
-
assignments: list[Assign],
|
|
1004
|
-
preloaded_stack: tuple[Expr, ...] = (),
|
|
1005
|
-
) -> tuple[object, ...]:
|
|
1006
|
-
if not instructions:
|
|
1007
|
-
return ()
|
|
1008
|
-
initial_locals = {
|
|
1009
|
-
name: Var(name=name, source=SourceRef(frontend="python-pyc", detail=f"local:{name}"))
|
|
1010
|
-
for name in code.varnames
|
|
1011
|
-
}
|
|
1012
|
-
for assignment in assignments:
|
|
1013
|
-
initial_locals[assignment.target.name] = assignment.target
|
|
1014
|
-
result = _run_python_stack_slice(code, instructions, initial_locals, preloaded_stack)
|
|
1015
|
-
if result.state.diagnostics:
|
|
1016
|
-
return ()
|
|
1017
|
-
output: list[object] = list(result.state.statements)
|
|
1018
|
-
if result.stopped_at is not None and result.state.terminator is None:
|
|
1019
|
-
try:
|
|
1020
|
-
stopped_index = instructions.index(result.stopped_at)
|
|
1021
|
-
except ValueError:
|
|
1022
|
-
stopped_index = 0
|
|
1023
|
-
output.append(
|
|
1024
|
-
vm_unsupported(
|
|
1025
|
-
source=result.stopped_at.source,
|
|
1026
|
-
message="unsupported region",
|
|
1027
|
-
detail=f"stopped at {result.stopped_at.opcode}",
|
|
1028
|
-
raw=(result.stopped_at.raw,) if result.stopped_at.raw else _raw_instruction_window(instructions, stopped_index),
|
|
1029
|
-
)
|
|
1030
|
-
)
|
|
1031
|
-
if result.state.terminator is not None:
|
|
1032
|
-
output.append(result.state.terminator)
|
|
1033
|
-
return tuple(output)
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
def _lift_stack_expr(
|
|
1037
|
-
code: PycCodeObject,
|
|
1038
|
-
instructions: tuple[object, ...],
|
|
1039
|
-
assignments: list[Assign],
|
|
1040
|
-
preloaded_stack: tuple[Expr, ...] = (),
|
|
1041
|
-
) -> Expr | None:
|
|
1042
|
-
initial_locals = {
|
|
1043
|
-
name: Var(name=name, source=SourceRef(frontend="python-pyc", detail=f"local:{name}"))
|
|
1044
|
-
for name in code.varnames
|
|
1045
|
-
}
|
|
1046
|
-
for assignment in assignments:
|
|
1047
|
-
initial_locals[assignment.target.name] = assignment.target
|
|
1048
|
-
result = _run_python_stack_slice(code, instructions, initial_locals, preloaded_stack)
|
|
1049
|
-
if result.state.diagnostics or not result.state.stack:
|
|
1050
|
-
return None
|
|
1051
|
-
return result.state.stack[-1]
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
def _run_python_stack_slice(
|
|
1055
|
-
code: PycCodeObject,
|
|
1056
|
-
instructions: tuple[object, ...],
|
|
1057
|
-
initial_locals: dict[str, Expr],
|
|
1058
|
-
initial_stack: tuple[Expr, ...] = (),
|
|
1059
|
-
):
|
|
1060
|
-
steps = _python_bytecode_steps(instructions)
|
|
1061
|
-
return lift_steps(steps, initial_locals=initial_locals, initial_stack=initial_stack)
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
1021
|
def _instruction_count(instruction) -> int:
|
|
1065
1022
|
return instruction.arg if isinstance(instruction.arg, int) else 0
|
|
1066
1023
|
|
|
@@ -24,6 +24,8 @@ class PycInstruction:
|
|
|
24
24
|
argval: object
|
|
25
25
|
argrepr: str
|
|
26
26
|
starts_line: int | None
|
|
27
|
+
artifact_offset: int | None = None
|
|
28
|
+
size: int | None = None
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
@dataclass(frozen=True)
|
|
@@ -51,6 +53,7 @@ class PycCodeObject:
|
|
|
51
53
|
instructions: tuple[PycInstruction, ...]
|
|
52
54
|
exception_regions: tuple[PycExceptionRegion, ...]
|
|
53
55
|
children: tuple["PycCodeObject", ...]
|
|
56
|
+
artifact_code_offset: int | None = None
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
@dataclass(frozen=True)
|
|
@@ -85,17 +88,19 @@ def decode_pyc(data: bytes, filename: str | None = None) -> PycModule:
|
|
|
85
88
|
return PycModule(
|
|
86
89
|
magic=data[:4],
|
|
87
90
|
flags=flags,
|
|
88
|
-
code=_decode_code_object(code),
|
|
91
|
+
code=_decode_code_object(code, _marshal_code_offsets(data, code)),
|
|
89
92
|
filename=filename,
|
|
90
93
|
)
|
|
91
94
|
|
|
92
95
|
|
|
93
|
-
def _decode_code_object(code: types.CodeType) -> PycCodeObject:
|
|
96
|
+
def _decode_code_object(code: types.CodeType, code_offsets: dict[int, int]) -> PycCodeObject:
|
|
94
97
|
children = tuple(
|
|
95
|
-
_decode_code_object(const)
|
|
98
|
+
_decode_code_object(const, code_offsets)
|
|
96
99
|
for const in code.co_consts
|
|
97
100
|
if isinstance(const, types.CodeType)
|
|
98
101
|
)
|
|
102
|
+
instructions = tuple(dis.get_instructions(code))
|
|
103
|
+
code_start = code_offsets.get(id(code))
|
|
99
104
|
return PycCodeObject(
|
|
100
105
|
name=code.co_name,
|
|
101
106
|
argcount=code.co_argcount,
|
|
@@ -119,8 +124,10 @@ def _decode_code_object(code: types.CodeType) -> PycCodeObject:
|
|
|
119
124
|
argval=instruction.argval,
|
|
120
125
|
argrepr=instruction.argrepr,
|
|
121
126
|
starts_line=instruction.starts_line,
|
|
127
|
+
artifact_offset=None if code_start is None else code_start + instruction.offset,
|
|
128
|
+
size=None if code_start is None else _instruction_size(instructions, index, len(code.co_code)),
|
|
122
129
|
)
|
|
123
|
-
for instruction in
|
|
130
|
+
for index, instruction in enumerate(instructions)
|
|
124
131
|
),
|
|
125
132
|
exception_regions=tuple(
|
|
126
133
|
PycExceptionRegion(
|
|
@@ -133,4 +140,58 @@ def _decode_code_object(code: types.CodeType) -> PycCodeObject:
|
|
|
133
140
|
for entry in dis.Bytecode(code).exception_entries
|
|
134
141
|
),
|
|
135
142
|
children=children,
|
|
143
|
+
artifact_code_offset=code_start,
|
|
136
144
|
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _instruction_size(instructions: tuple[dis.Instruction, ...], index: int, code_size: int) -> int:
|
|
148
|
+
next_offset = instructions[index + 1].offset if index + 1 < len(instructions) else code_size
|
|
149
|
+
return next_offset - instructions[index].offset
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _marshal_code_offsets(data: bytes, root: types.CodeType) -> dict[int, int]:
|
|
153
|
+
"""Find only unambiguous marshal byte-string records for ``co_code`` values.
|
|
154
|
+
|
|
155
|
+
CPython serializes code bytes as a marshal ``TYPE_STRING`` record. The
|
|
156
|
+
frontend verifies both that record's tag and its length, then accepts a
|
|
157
|
+
location only when that code byte sequence has exactly one candidate. A
|
|
158
|
+
repeated or otherwise ambiguous value is deliberately left unmapped.
|
|
159
|
+
"""
|
|
160
|
+
code_objects = tuple(_walk_code_objects(root))
|
|
161
|
+
candidates: dict[bytes, list[int]] = {}
|
|
162
|
+
for code in code_objects:
|
|
163
|
+
payload = code.co_code
|
|
164
|
+
if payload in candidates:
|
|
165
|
+
continue
|
|
166
|
+
candidates[payload] = _marshal_string_payload_offsets(data, payload)
|
|
167
|
+
assigned: set[int] = set()
|
|
168
|
+
offsets: dict[int, int] = {}
|
|
169
|
+
for code in code_objects:
|
|
170
|
+
matches = [offset for offset in candidates[code.co_code] if offset not in assigned]
|
|
171
|
+
if len(matches) == 1:
|
|
172
|
+
offsets[id(code)] = matches[0]
|
|
173
|
+
assigned.add(matches[0])
|
|
174
|
+
return offsets
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _walk_code_objects(code: types.CodeType):
|
|
178
|
+
yield code
|
|
179
|
+
for value in code.co_consts:
|
|
180
|
+
if isinstance(value, types.CodeType):
|
|
181
|
+
yield from _walk_code_objects(value)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _marshal_string_payload_offsets(data: bytes, payload: bytes) -> list[int]:
|
|
185
|
+
if not payload:
|
|
186
|
+
return []
|
|
187
|
+
result: list[int] = []
|
|
188
|
+
size = len(payload)
|
|
189
|
+
for header in range(PYC_MIN_HEADER_SIZE, len(data) - size - 4):
|
|
190
|
+
if (data[header] & 0x7F) != ord("s"):
|
|
191
|
+
continue
|
|
192
|
+
if int.from_bytes(data[header + 1 : header + 5], "little", signed=True) != size:
|
|
193
|
+
continue
|
|
194
|
+
start = header + 5
|
|
195
|
+
if data[start : start + size] == payload:
|
|
196
|
+
result.append(start)
|
|
197
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unidecompiler-plugin-python-pyc
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Python bytecode frontend plugin for unidecompiler
|
|
5
5
|
Author-email: Wker <1670133844@qq.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/Wker666/unidecompiler
|
|
|
9
9
|
Project-URL: Issues, https://github.com/Wker666/unidecompiler/issues
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: unidecompiler<0.2.0,>=0.1.
|
|
12
|
+
Requires-Dist: unidecompiler<0.2.0,>=0.1.6
|
|
13
13
|
|
|
14
14
|
# unidecompiler-plugin-python-pyc
|
|
15
15
|
|
unidecompiler_plugin_python_pyc-0.1.3/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
unidecompiler<0.2.0,>=0.1.6
|
unidecompiler_plugin_python_pyc-0.1.1/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
unidecompiler<0.2.0,>=0.1.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|