unidecompiler-plugin-python-pyc 0.1.1__tar.gz → 0.1.2__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.
Files changed (17) hide show
  1. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/PKG-INFO +2 -2
  2. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/pyproject.toml +2 -2
  3. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc/lifter.py +2 -0
  4. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc/pyc.py +65 -4
  5. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc.egg-info/PKG-INFO +2 -2
  6. unidecompiler_plugin_python_pyc-0.1.2/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt +1 -0
  7. unidecompiler_plugin_python_pyc-0.1.1/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt +0 -1
  8. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/README.md +0 -0
  9. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/setup.cfg +0 -0
  10. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc/__init__.py +0 -0
  11. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc/plugin.py +0 -0
  12. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc/simulation.py +0 -0
  13. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc/support.py +0 -0
  14. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc.egg-info/SOURCES.txt +0 -0
  15. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc.egg-info/dependency_links.txt +0 -0
  16. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/src/unidecompiler_plugin_python_pyc.egg-info/entry_points.txt +0 -0
  17. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.2}/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.1
3
+ Version: 0.1.2
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.1
12
+ Requires-Dist: unidecompiler<0.2.0,>=0.1.2
13
13
 
14
14
  # unidecompiler-plugin-python-pyc
15
15
 
@@ -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.1"
7
+ version = "0.1.2"
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.1,<0.2.0"]
13
+ dependencies = ["unidecompiler>=0.1.2,<0.2.0"]
14
14
 
15
15
  [project.urls]
16
16
  Homepage = "https://github.com/Wker666/unidecompiler"
@@ -79,6 +79,7 @@ from unidecompiler.core.vm_function import (
79
79
  from unidecompiler.core.vm_bytecode import VMBytecodeStep
80
80
  from unidecompiler.core.vm_hints import VMHint
81
81
  from unidecompiler.core.vm_operands import VMDecodedInstruction, VMOperand
82
+ from unidecompiler.provenance import ByteRange
82
83
  from unidecompiler.core.vm_region import (
83
84
  VMLinearState,
84
85
  VMRegionCallbacks,
@@ -811,6 +812,7 @@ def _python_decoded_instruction(instruction, source: SourceRef) -> VMDecodedInst
811
812
  source=source,
812
813
  operands=tuple(operands),
813
814
  raw=_raw_instruction_line(instruction),
815
+ artifact_range=(None if instruction.artifact_offset is None or instruction.size is None else ByteRange(instruction.artifact_offset, instruction.size)),
814
816
  )
815
817
 
816
818
 
@@ -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 dis.get_instructions(code)
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.1
3
+ Version: 0.1.2
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.1
12
+ Requires-Dist: unidecompiler<0.2.0,>=0.1.2
13
13
 
14
14
  # unidecompiler-plugin-python-pyc
15
15