pytecode 0.0.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.
- pytecode/__init__.py +22 -0
- pytecode/analysis.py +2402 -0
- pytecode/attributes.py +868 -0
- pytecode/bytes_utils.py +208 -0
- pytecode/class_reader.py +810 -0
- pytecode/class_writer.py +630 -0
- pytecode/constant_pool.py +196 -0
- pytecode/constant_pool_builder.py +844 -0
- pytecode/constants.py +208 -0
- pytecode/debug_info.py +319 -0
- pytecode/descriptors.py +791 -0
- pytecode/hierarchy.py +561 -0
- pytecode/info.py +123 -0
- pytecode/instructions.py +495 -0
- pytecode/jar.py +271 -0
- pytecode/labels.py +1041 -0
- pytecode/model.py +929 -0
- pytecode/modified_utf8.py +145 -0
- pytecode/operands.py +683 -0
- pytecode/py.typed +0 -0
- pytecode/transforms.py +954 -0
- pytecode/verify.py +1386 -0
- pytecode-0.0.1.dist-info/METADATA +218 -0
- pytecode-0.0.1.dist-info/RECORD +27 -0
- pytecode-0.0.1.dist-info/WHEEL +5 -0
- pytecode-0.0.1.dist-info/licenses/LICENSE +21 -0
- pytecode-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytecode
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python library for parsing, manipulating, and emitting JVM class files
|
|
5
|
+
Author: Trenton Smith
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/smithtrenton/pytecode
|
|
8
|
+
Project-URL: Documentation, https://smithtrenton.github.io/pytecode/
|
|
9
|
+
Project-URL: Repository, https://github.com/smithtrenton/pytecode
|
|
10
|
+
Project-URL: Issues, https://github.com/smithtrenton/pytecode/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/smithtrenton/pytecode/releases
|
|
12
|
+
Keywords: bytecode,classfile,java,jvm
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: basedpyright<2.0.0,>=1.38.3; extra == "dev"
|
|
22
|
+
Requires-Dist: pdoc<17.0.0,>=16.0.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest<10.0.0,>=9.0.2; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff<0.16.0,>=0.15.7; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# pytecode
|
|
28
|
+
|
|
29
|
+
`pytecode` is a Python 3.14+ library for parsing, inspecting, editing, validating, and emitting JVM class files and JAR archives.
|
|
30
|
+
|
|
31
|
+
It is built for Python tooling that needs direct access to Java bytecode: classfile readers and writers, archive rewriters, transformation pipelines, control-flow analysis, descriptor utilities, hierarchy-aware frame computation, and verification-oriented workflows.
|
|
32
|
+
|
|
33
|
+
## Why pytecode?
|
|
34
|
+
|
|
35
|
+
- Parse `.class` files into typed Python dataclasses.
|
|
36
|
+
- Edit classes, fields, methods, and bytecode through a mutable symbolic model.
|
|
37
|
+
- Rewrite JAR files while preserving non-class resources and ZIP metadata.
|
|
38
|
+
- Recompute `max_stack`, `max_locals`, and `StackMapTable` when requested.
|
|
39
|
+
- Validate parsed classfiles and edited models before emission.
|
|
40
|
+
- Work with descriptors, signatures, labels, symbolic operands, constant pools, and debug-info policies.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Install from PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pytecode
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or with `uv`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv add pytecode
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`pytecode` requires Python `3.14+`.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
### Parse and roundtrip a class file
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
65
|
+
from pytecode import ClassReader, ClassWriter
|
|
66
|
+
|
|
67
|
+
reader = ClassReader.from_file("HelloWorld.class")
|
|
68
|
+
classfile = reader.class_info
|
|
69
|
+
|
|
70
|
+
print(classfile.major_version)
|
|
71
|
+
print(classfile.methods_count)
|
|
72
|
+
|
|
73
|
+
Path("HelloWorld-copy.class").write_bytes(ClassWriter.write(classfile))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Lift to the editable model
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from pathlib import Path
|
|
80
|
+
|
|
81
|
+
from pytecode import ClassModel
|
|
82
|
+
|
|
83
|
+
model = ClassModel.from_bytes(Path("HelloWorld.class").read_bytes())
|
|
84
|
+
print(model.name)
|
|
85
|
+
|
|
86
|
+
updated_bytes = model.to_bytes()
|
|
87
|
+
Path("HelloWorld-updated.class").write_bytes(updated_bytes)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use `recompute_frames=True` when an edit changes control flow or stack/local layout.
|
|
91
|
+
|
|
92
|
+
## JAR rewriting example
|
|
93
|
+
|
|
94
|
+
`JarFile.rewrite()` can apply in-place transforms to matching classes and methods:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from pytecode import JarFile
|
|
98
|
+
from pytecode.constants import MethodAccessFlag
|
|
99
|
+
from pytecode.model import ClassModel, MethodModel
|
|
100
|
+
from pytecode.transforms import (
|
|
101
|
+
class_named,
|
|
102
|
+
method_is_public,
|
|
103
|
+
method_is_static,
|
|
104
|
+
method_name_matches,
|
|
105
|
+
on_methods,
|
|
106
|
+
pipeline,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def make_final(method: MethodModel, _owner: ClassModel) -> None:
|
|
111
|
+
method.access_flags |= MethodAccessFlag.FINAL
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
JarFile("input.jar").rewrite(
|
|
115
|
+
"output.jar",
|
|
116
|
+
transform=pipeline(
|
|
117
|
+
on_methods(
|
|
118
|
+
make_final,
|
|
119
|
+
where=method_name_matches(r"main") & method_is_public() & method_is_static(),
|
|
120
|
+
owner=class_named("HelloWorld"),
|
|
121
|
+
)
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Transforms must mutate models in place and return `None`. For code-shape changes, pass `recompute_frames=True`. For an ASM-like lift path that omits debug metadata, pass `skip_debug=True`.
|
|
127
|
+
|
|
128
|
+
## Public surface
|
|
129
|
+
|
|
130
|
+
Top-level exports:
|
|
131
|
+
|
|
132
|
+
- `pytecode.ClassReader` and `pytecode.ClassWriter` for raw classfile parsing and emission.
|
|
133
|
+
- `pytecode.JarFile` for archive reads, mutation, and safe rewrite-to-disk.
|
|
134
|
+
- `pytecode.ClassModel` for mutable editing with symbolic references.
|
|
135
|
+
|
|
136
|
+
Supported submodules:
|
|
137
|
+
|
|
138
|
+
- `pytecode.transforms` for composable class, field, method, and code transforms.
|
|
139
|
+
- `pytecode.labels` for label-aware bytecode editing helpers.
|
|
140
|
+
- `pytecode.operands` for symbolic operand wrappers.
|
|
141
|
+
- `pytecode.analysis` for CFG construction, frame simulation, and recomputation helpers.
|
|
142
|
+
- `pytecode.verify` for structural validation and diagnostics.
|
|
143
|
+
- `pytecode.hierarchy` for type and override resolution helpers.
|
|
144
|
+
- `pytecode.descriptors` for JVM descriptors and generic signatures.
|
|
145
|
+
- `pytecode.constant_pool_builder` for deterministic constant-pool construction.
|
|
146
|
+
- `pytecode.modified_utf8` for JVM Modified UTF-8 encoding and decoding.
|
|
147
|
+
- `pytecode.debug_info` for explicit debug-info preservation and stripping policies.
|
|
148
|
+
|
|
149
|
+
## Documentation
|
|
150
|
+
|
|
151
|
+
- Development docs overview: [docs/OVERVIEW.md](https://github.com/smithtrenton/pytecode/blob/master/docs/OVERVIEW.md)
|
|
152
|
+
- Hosted API reference: <https://smithtrenton.github.io/pytecode/>
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
Create a local environment with development tools:
|
|
157
|
+
|
|
158
|
+
```powershell
|
|
159
|
+
uv sync --extra dev
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Common checks:
|
|
163
|
+
|
|
164
|
+
```powershell
|
|
165
|
+
uv run ruff check .
|
|
166
|
+
uv run ruff format --check .
|
|
167
|
+
uv run basedpyright
|
|
168
|
+
uv run pytest -q
|
|
169
|
+
uv run python tools\generate_api_docs.py --check
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Generate local API reference HTML with:
|
|
173
|
+
|
|
174
|
+
```powershell
|
|
175
|
+
uv run python tools\generate_api_docs.py
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Build source and wheel distributions locally:
|
|
179
|
+
|
|
180
|
+
```powershell
|
|
181
|
+
uv build
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The `oracle`-marked CFG tests lazily cache ASM 9.7.1 test jars under `.pytest_cache\pytecode-oracle` and also honor manually seeded jars in `tests\resources\oracle\lib`. If `java`, `javac`, or the ASM jars are unavailable, that suite skips without failing the rest of the test run.
|
|
185
|
+
|
|
186
|
+
## Release automation
|
|
187
|
+
|
|
188
|
+
PyPI releases are published from GitHub Actions by pushing an immutable `v<version>` tag that matches `project.version` in `pyproject.toml`. The release workflow reruns validation on the tagged commit, builds both `sdist` and `wheel` with `uv build`, and publishes from the protected `pypi` environment via PyPI Trusted Publishing.
|
|
189
|
+
|
|
190
|
+
Release procedure:
|
|
191
|
+
|
|
192
|
+
```powershell
|
|
193
|
+
# 1) bump project.version in pyproject.toml
|
|
194
|
+
uv run ruff check .
|
|
195
|
+
uv run ruff format --check .
|
|
196
|
+
uv run basedpyright
|
|
197
|
+
uv run pytest -q
|
|
198
|
+
uv run python tools\generate_api_docs.py --check
|
|
199
|
+
|
|
200
|
+
git commit -am "Bump version to X.Y.Z"
|
|
201
|
+
git push origin master
|
|
202
|
+
git tag vX.Y.Z
|
|
203
|
+
git push origin vX.Y.Z
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The release workflow rejects tags that do not match `project.version`. Treat release tags as immutable: if a tag or published artifact is wrong, bump to a new version and publish a new tag instead of force-pushing the old one. If the workflow fails before the publish step because of environment approval or a transient PyPI issue, rerun the workflow for the same tag instead of moving the tag.
|
|
207
|
+
|
|
208
|
+
## Repository utilities
|
|
209
|
+
|
|
210
|
+
`run.py` is a manual smoke-test helper that parses a JAR file, writes pretty-printed parsed class structures under `<jar parent>\output\<jar stem>\parsed\`, and writes class-model-derived rewritten `.class` files plus copied resources under `<jar parent>\output\<jar stem>\rewritten\`.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
|
|
214
|
+
```powershell
|
|
215
|
+
uv run python .\run.py .\path\to\input.jar
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The script prints read, parse, lift, write, and rewrite timings plus class and resource counts to stdout.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
pytecode/__init__.py,sha256=0WijlZ3llZ5PBPC9yFTLCi17uprAZa5HBjQPrk8OBIE,930
|
|
2
|
+
pytecode/analysis.py,sha256=H8f2uElVeY_6jVUvcb9BW7pDtmiMKpKdvrT3ej4V1Vg,77641
|
|
3
|
+
pytecode/attributes.py,sha256=M2p_Ni7fOx7XnYQXinOIfFItnbRHHC8Dded0tt1WxZ0,20592
|
|
4
|
+
pytecode/bytes_utils.py,sha256=rmVczsgq-TI3EGPO2N1Go93eO04_G3RIPWxFiEsqoO0,5465
|
|
5
|
+
pytecode/class_reader.py,sha256=7GFldG94gaszgjQ1NEh9UwbVW8qKQ-l438TRNBxCOVs,38670
|
|
6
|
+
pytecode/class_writer.py,sha256=RvOtmUJRciKaJu4QJ_WvfEb3LE6ZCMMryvhPrCAA6Y0,24317
|
|
7
|
+
pytecode/constant_pool.py,sha256=mSww_ZznLTe-PeADLfxFoIuphTLAbuNAr9OB8RPsPDc,4005
|
|
8
|
+
pytecode/constant_pool_builder.py,sha256=zKebqffE8qObqsjK6FXtjCCEbhit-iIxPC40HecZxPU,31450
|
|
9
|
+
pytecode/constants.py,sha256=b_1p13jtCbFixmBNytfX3pzHj7en9rMFnXu-33WTvhk,5409
|
|
10
|
+
pytecode/debug_info.py,sha256=Yst0MNh39r4Z92PZ6foZXnv9NP2Ymyg76zFjb6jwZWs,9903
|
|
11
|
+
pytecode/descriptors.py,sha256=0q6z5GbijYZOf8xMPTPjtCWIGAZK8C7f-F8r2xmFYD0,23556
|
|
12
|
+
pytecode/hierarchy.py,sha256=SseaGtZnN1XKY68iEgrcJsgYLpjLCWVkRll-FyiDR9I,18380
|
|
13
|
+
pytecode/info.py,sha256=CX_JAVcFX3Ps7j1kabPT1RERFpFwETe4vel39r8kU2w,4824
|
|
14
|
+
pytecode/instructions.py,sha256=oK0yrNu60otwc98GifXgUO64fhhKEDvrWTPeUOIAJ4o,12538
|
|
15
|
+
pytecode/jar.py,sha256=lERB0D60WSQIM7aepLtRQt1-Eaj51EXL6jUpnUYz04c,9923
|
|
16
|
+
pytecode/labels.py,sha256=aFXz9s3uwK5UScLZAi3RLG4md1Wf5i_JN2U-Eh065HU,36235
|
|
17
|
+
pytecode/model.py,sha256=OkSHCOhm4z0x1uFS7dfd0-pIiLL0jqUSUAuKOhFpbiU,34583
|
|
18
|
+
pytecode/modified_utf8.py,sha256=bJ_xeJMurV70NmayMrO4ILeyimUGXrot6AsmVf87CVs,4801
|
|
19
|
+
pytecode/operands.py,sha256=k_aTMgwVxRGBdVjY0Tv7JYRIIf_VwKwJh92U3AD_cd8,21320
|
|
20
|
+
pytecode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
pytecode/transforms.py,sha256=FSxRjQHizHRdfRL06WUF-k_JZ8DX7I_ohno9EpXR0C4,27274
|
|
22
|
+
pytecode/verify.py,sha256=Rt-GBoMlX9U4ei6NylVbbFZ15nKptCkgTbOqlwGZkM8,53086
|
|
23
|
+
pytecode-0.0.1.dist-info/licenses/LICENSE,sha256=xcOCdQxGtsJL7y8NYaYJZnoZNcnvMejSkpBKAhvlUe4,1070
|
|
24
|
+
pytecode-0.0.1.dist-info/METADATA,sha256=hl7hQjs3pCETvNiHwuAS7ALw88uI1WcDKa1nhztpC8Q,7382
|
|
25
|
+
pytecode-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
26
|
+
pytecode-0.0.1.dist-info/top_level.txt,sha256=L1TzD0q2abDDznp3W8eWNoHtZK_pH3oXoEHWf7Su1Sg,9
|
|
27
|
+
pytecode-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Trenton Smith
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytecode
|