pytecode 0.0.1__tar.gz → 0.0.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.
- {pytecode-0.0.1/pytecode.egg-info → pytecode-0.0.3}/PKG-INFO +29 -19
- {pytecode-0.0.1 → pytecode-0.0.3}/README.md +28 -18
- {pytecode-0.0.1 → pytecode-0.0.3}/pyproject.toml +2 -2
- {pytecode-0.0.1 → pytecode-0.0.3}/pytecode/__init__.py +5 -5
- pytecode-0.0.3/pytecode/_internal/__init__.py +1 -0
- pytecode-0.0.1/pytecode/analysis.py → pytecode-0.0.3/pytecode/analysis/__init__.py +14 -14
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/analysis}/hierarchy.py +6 -6
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/analysis}/verify.py +59 -45
- pytecode-0.0.1/pytecode/jar.py → pytecode-0.0.3/pytecode/archive/__init__.py +5 -5
- pytecode-0.0.3/pytecode/classfile/__init__.py +14 -0
- pytecode-0.0.1/pytecode/class_reader.py → pytecode-0.0.3/pytecode/classfile/reader.py +4 -4
- pytecode-0.0.1/pytecode/class_writer.py → pytecode-0.0.3/pytecode/classfile/writer.py +1 -1
- pytecode-0.0.3/pytecode/edit/__init__.py +12 -0
- pytecode-0.0.3/pytecode/edit/_attribute_clone.py +546 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/constant_pool_builder.py +471 -69
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/debug_info.py +1 -1
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/labels.py +341 -145
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/model.py +131 -80
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/operands.py +1 -1
- pytecode-0.0.1/pytecode/transforms.py → pytecode-0.0.3/pytecode/transforms/__init__.py +3 -3
- {pytecode-0.0.1 → pytecode-0.0.3/pytecode.egg-info}/PKG-INFO +29 -19
- {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/SOURCES.txt +24 -20
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_analysis.py +45 -56
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_api_docs.py +6 -6
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_attributes.py +4 -1
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_bytes_utils.py +1 -1
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_cfg_oracle.py +4 -8
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_class_reader.py +7 -4
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_class_writer.py +7 -4
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_constant_pool.py +3 -3
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_constant_pool_builder.py +174 -4
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_debug_info.py +22 -150
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_descriptors.py +2 -2
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_helpers.py +122 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_hierarchy.py +5 -5
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_instructions.py +1 -1
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_jar.py +8 -14
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_labels.py +179 -126
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_model.py +246 -40
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_modified_utf8.py +1 -1
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_operands.py +16 -25
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_transforms.py +3 -3
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_validation.py +2 -2
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_verify.py +31 -31
- {pytecode-0.0.1 → pytecode-0.0.3}/LICENSE +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/MANIFEST.in +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/_internal}/bytes_utils.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/attributes.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/constant_pool.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/constants.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/descriptors.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/info.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/instructions.py +0 -0
- {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/modified_utf8.py +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/pytecode/py.typed +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/dependency_links.txt +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/requires.txt +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/top_level.txt +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/setup.cfg +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_javap_parser.py +0 -0
- {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_validate_release_tag.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytecode
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Python library for parsing, manipulating, and emitting JVM class files
|
|
5
5
|
Author: Trenton Smith
|
|
6
6
|
License-Expression: MIT
|
|
@@ -95,8 +95,8 @@ Use `recompute_frames=True` when an edit changes control flow or stack/local lay
|
|
|
95
95
|
|
|
96
96
|
```python
|
|
97
97
|
from pytecode import JarFile
|
|
98
|
-
from pytecode.constants import MethodAccessFlag
|
|
99
|
-
from pytecode.model import ClassModel, MethodModel
|
|
98
|
+
from pytecode.classfile.constants import MethodAccessFlag
|
|
99
|
+
from pytecode.edit.model import ClassModel, MethodModel
|
|
100
100
|
from pytecode.transforms import (
|
|
101
101
|
class_named,
|
|
102
102
|
method_is_public,
|
|
@@ -136,15 +136,15 @@ Top-level exports:
|
|
|
136
136
|
Supported submodules:
|
|
137
137
|
|
|
138
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.
|
|
139
|
+
- `pytecode.edit.labels` for label-aware bytecode editing helpers.
|
|
140
|
+
- `pytecode.edit.operands` for symbolic operand wrappers.
|
|
141
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.
|
|
142
|
+
- `pytecode.analysis.verify` for structural validation and diagnostics.
|
|
143
|
+
- `pytecode.analysis.hierarchy` for type and override resolution helpers.
|
|
144
|
+
- `pytecode.classfile.descriptors` for JVM descriptors and generic signatures.
|
|
145
|
+
- `pytecode.edit.constant_pool_builder` for deterministic constant-pool construction.
|
|
146
|
+
- `pytecode.classfile.modified_utf8` for JVM Modified UTF-8 encoding and decoding.
|
|
147
|
+
- `pytecode.edit.debug_info` for explicit debug-info preservation and stripping policies.
|
|
148
148
|
|
|
149
149
|
## Documentation
|
|
150
150
|
|
|
@@ -166,13 +166,13 @@ uv run ruff check .
|
|
|
166
166
|
uv run ruff format --check .
|
|
167
167
|
uv run basedpyright
|
|
168
168
|
uv run pytest -q
|
|
169
|
-
uv run python tools
|
|
169
|
+
uv run python tools/generate_api_docs.py --check
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
Generate local API reference HTML with:
|
|
173
173
|
|
|
174
174
|
```powershell
|
|
175
|
-
uv run python tools
|
|
175
|
+
uv run python tools/generate_api_docs.py
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
Build source and wheel distributions locally:
|
|
@@ -181,11 +181,21 @@ Build source and wheel distributions locally:
|
|
|
181
181
|
uv build
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
Profile isolated JAR-processing stages without `run.py`'s output overhead:
|
|
185
|
+
|
|
186
|
+
```powershell
|
|
187
|
+
uv run python tools/profile_jar_pipeline.py path/to/jar.jar
|
|
188
|
+
uv run python tools/profile_jar_pipeline.py path/to/jar.jar --stages class-parse model-lift model-lower
|
|
189
|
+
uv run python tools/profile_jar_pipeline.py path/to/dir/with/jars --stages model-lift model-lower --summary-json output/profiles/common-libs/summary.json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
When making runtime-performance changes, prefer checking both a focused jar such as `225.jar` and the wider common-jar corpus so regressions and wins are not judged from a single artifact. A single jar defaults to all stages; directories and multi-jar runs default to `model-lift` and `model-lower`.
|
|
193
|
+
|
|
194
|
+
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
195
|
|
|
186
196
|
## Release automation
|
|
187
197
|
|
|
188
|
-
PyPI releases are published from GitHub Actions by pushing an immutable `v<version>` tag that matches `project.version` in `pyproject.toml`. The
|
|
198
|
+
PyPI releases are published from GitHub Actions by pushing an immutable `v<version>` tag that matches `project.version` in `pyproject.toml`. The same workflow can also be started manually for an existing tag by supplying a `tag` input. In both cases, the workflow checks out the tagged commit, reruns validation, builds both `sdist` and `wheel` with `uv build`, publishes from the protected `pypi` environment via PyPI Trusted Publishing, and then creates or updates a GitHub Release for the same tag with the built distributions attached.
|
|
189
199
|
|
|
190
200
|
Release procedure:
|
|
191
201
|
|
|
@@ -195,7 +205,7 @@ uv run ruff check .
|
|
|
195
205
|
uv run ruff format --check .
|
|
196
206
|
uv run basedpyright
|
|
197
207
|
uv run pytest -q
|
|
198
|
-
uv run python tools
|
|
208
|
+
uv run python tools/generate_api_docs.py --check
|
|
199
209
|
|
|
200
210
|
git commit -am "Bump version to X.Y.Z"
|
|
201
211
|
git push origin master
|
|
@@ -203,16 +213,16 @@ git tag vX.Y.Z
|
|
|
203
213
|
git push origin vX.Y.Z
|
|
204
214
|
```
|
|
205
215
|
|
|
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.
|
|
216
|
+
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. For an existing tag, you can rerun the workflow directly or start it manually from Actions by providing the tag name. The workflow is safe to rerun for the same tag: PyPI uploads skip files that already exist, and the GitHub Release step updates the existing release assets in place if the release was already created.
|
|
207
217
|
|
|
208
218
|
## Repository utilities
|
|
209
219
|
|
|
210
|
-
`run.py` is a manual smoke-test helper that parses a JAR file, writes pretty-printed parsed class structures under `<jar parent
|
|
220
|
+
`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
221
|
|
|
212
222
|
Example:
|
|
213
223
|
|
|
214
224
|
```powershell
|
|
215
|
-
uv run python
|
|
225
|
+
uv run python ./run.py ./path/to/input.jar
|
|
216
226
|
```
|
|
217
227
|
|
|
218
228
|
The script prints read, parse, lift, write, and rewrite timings plus class and resource counts to stdout.
|
|
@@ -69,8 +69,8 @@ Use `recompute_frames=True` when an edit changes control flow or stack/local lay
|
|
|
69
69
|
|
|
70
70
|
```python
|
|
71
71
|
from pytecode import JarFile
|
|
72
|
-
from pytecode.constants import MethodAccessFlag
|
|
73
|
-
from pytecode.model import ClassModel, MethodModel
|
|
72
|
+
from pytecode.classfile.constants import MethodAccessFlag
|
|
73
|
+
from pytecode.edit.model import ClassModel, MethodModel
|
|
74
74
|
from pytecode.transforms import (
|
|
75
75
|
class_named,
|
|
76
76
|
method_is_public,
|
|
@@ -110,15 +110,15 @@ Top-level exports:
|
|
|
110
110
|
Supported submodules:
|
|
111
111
|
|
|
112
112
|
- `pytecode.transforms` for composable class, field, method, and code transforms.
|
|
113
|
-
- `pytecode.labels` for label-aware bytecode editing helpers.
|
|
114
|
-
- `pytecode.operands` for symbolic operand wrappers.
|
|
113
|
+
- `pytecode.edit.labels` for label-aware bytecode editing helpers.
|
|
114
|
+
- `pytecode.edit.operands` for symbolic operand wrappers.
|
|
115
115
|
- `pytecode.analysis` for CFG construction, frame simulation, and recomputation helpers.
|
|
116
|
-
- `pytecode.verify` for structural validation and diagnostics.
|
|
117
|
-
- `pytecode.hierarchy` for type and override resolution helpers.
|
|
118
|
-
- `pytecode.descriptors` for JVM descriptors and generic signatures.
|
|
119
|
-
- `pytecode.constant_pool_builder` for deterministic constant-pool construction.
|
|
120
|
-
- `pytecode.modified_utf8` for JVM Modified UTF-8 encoding and decoding.
|
|
121
|
-
- `pytecode.debug_info` for explicit debug-info preservation and stripping policies.
|
|
116
|
+
- `pytecode.analysis.verify` for structural validation and diagnostics.
|
|
117
|
+
- `pytecode.analysis.hierarchy` for type and override resolution helpers.
|
|
118
|
+
- `pytecode.classfile.descriptors` for JVM descriptors and generic signatures.
|
|
119
|
+
- `pytecode.edit.constant_pool_builder` for deterministic constant-pool construction.
|
|
120
|
+
- `pytecode.classfile.modified_utf8` for JVM Modified UTF-8 encoding and decoding.
|
|
121
|
+
- `pytecode.edit.debug_info` for explicit debug-info preservation and stripping policies.
|
|
122
122
|
|
|
123
123
|
## Documentation
|
|
124
124
|
|
|
@@ -140,13 +140,13 @@ uv run ruff check .
|
|
|
140
140
|
uv run ruff format --check .
|
|
141
141
|
uv run basedpyright
|
|
142
142
|
uv run pytest -q
|
|
143
|
-
uv run python tools
|
|
143
|
+
uv run python tools/generate_api_docs.py --check
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
Generate local API reference HTML with:
|
|
147
147
|
|
|
148
148
|
```powershell
|
|
149
|
-
uv run python tools
|
|
149
|
+
uv run python tools/generate_api_docs.py
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
Build source and wheel distributions locally:
|
|
@@ -155,11 +155,21 @@ Build source and wheel distributions locally:
|
|
|
155
155
|
uv build
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
Profile isolated JAR-processing stages without `run.py`'s output overhead:
|
|
159
|
+
|
|
160
|
+
```powershell
|
|
161
|
+
uv run python tools/profile_jar_pipeline.py path/to/jar.jar
|
|
162
|
+
uv run python tools/profile_jar_pipeline.py path/to/jar.jar --stages class-parse model-lift model-lower
|
|
163
|
+
uv run python tools/profile_jar_pipeline.py path/to/dir/with/jars --stages model-lift model-lower --summary-json output/profiles/common-libs/summary.json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
When making runtime-performance changes, prefer checking both a focused jar such as `225.jar` and the wider common-jar corpus so regressions and wins are not judged from a single artifact. A single jar defaults to all stages; directories and multi-jar runs default to `model-lift` and `model-lower`.
|
|
167
|
+
|
|
168
|
+
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.
|
|
159
169
|
|
|
160
170
|
## Release automation
|
|
161
171
|
|
|
162
|
-
PyPI releases are published from GitHub Actions by pushing an immutable `v<version>` tag that matches `project.version` in `pyproject.toml`. The
|
|
172
|
+
PyPI releases are published from GitHub Actions by pushing an immutable `v<version>` tag that matches `project.version` in `pyproject.toml`. The same workflow can also be started manually for an existing tag by supplying a `tag` input. In both cases, the workflow checks out the tagged commit, reruns validation, builds both `sdist` and `wheel` with `uv build`, publishes from the protected `pypi` environment via PyPI Trusted Publishing, and then creates or updates a GitHub Release for the same tag with the built distributions attached.
|
|
163
173
|
|
|
164
174
|
Release procedure:
|
|
165
175
|
|
|
@@ -169,7 +179,7 @@ uv run ruff check .
|
|
|
169
179
|
uv run ruff format --check .
|
|
170
180
|
uv run basedpyright
|
|
171
181
|
uv run pytest -q
|
|
172
|
-
uv run python tools
|
|
182
|
+
uv run python tools/generate_api_docs.py --check
|
|
173
183
|
|
|
174
184
|
git commit -am "Bump version to X.Y.Z"
|
|
175
185
|
git push origin master
|
|
@@ -177,16 +187,16 @@ git tag vX.Y.Z
|
|
|
177
187
|
git push origin vX.Y.Z
|
|
178
188
|
```
|
|
179
189
|
|
|
180
|
-
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.
|
|
190
|
+
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. For an existing tag, you can rerun the workflow directly or start it manually from Actions by providing the tag name. The workflow is safe to rerun for the same tag: PyPI uploads skip files that already exist, and the GitHub Release step updates the existing release assets in place if the release was already created.
|
|
181
191
|
|
|
182
192
|
## Repository utilities
|
|
183
193
|
|
|
184
|
-
`run.py` is a manual smoke-test helper that parses a JAR file, writes pretty-printed parsed class structures under `<jar parent
|
|
194
|
+
`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/`.
|
|
185
195
|
|
|
186
196
|
Example:
|
|
187
197
|
|
|
188
198
|
```powershell
|
|
189
|
-
uv run python
|
|
199
|
+
uv run python ./run.py ./path/to/input.jar
|
|
190
200
|
```
|
|
191
201
|
|
|
192
202
|
The script prints read, parse, lift, write, and rewrite timings plus class and resource counts to stdout.
|
|
@@ -6,14 +6,14 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
include-package-data = true
|
|
7
7
|
|
|
8
8
|
[tool.setuptools.packages.find]
|
|
9
|
-
include = ["pytecode"]
|
|
9
|
+
include = ["pytecode*"]
|
|
10
10
|
|
|
11
11
|
[tool.setuptools.package-data]
|
|
12
12
|
pytecode = ["py.typed"]
|
|
13
13
|
|
|
14
14
|
[project]
|
|
15
15
|
name = "pytecode"
|
|
16
|
-
version = "0.0.
|
|
16
|
+
version = "0.0.3"
|
|
17
17
|
description = "Python library for parsing, manipulating, and emitting JVM class files"
|
|
18
18
|
readme = "README.md"
|
|
19
19
|
authors = [{ name = "Trenton Smith" }]
|
|
@@ -11,12 +11,12 @@ Provides four top-level entry points:
|
|
|
11
11
|
Additional submodules expose transforms, descriptors, analysis, hierarchy
|
|
12
12
|
resolution, validation, operands, labels, debug-info helpers, and the
|
|
13
13
|
underlying data types. See ``pytecode.transforms``, ``pytecode.analysis``,
|
|
14
|
-
``pytecode.verify``, and the other documented submodules for details.
|
|
14
|
+
``pytecode.analysis.verify``, and the other documented submodules for details.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
from .
|
|
18
|
-
from .
|
|
19
|
-
from .
|
|
20
|
-
from .model import ClassModel
|
|
17
|
+
from .archive import JarFile
|
|
18
|
+
from .classfile.reader import ClassReader
|
|
19
|
+
from .classfile.writer import ClassWriter
|
|
20
|
+
from .edit.model import ClassModel
|
|
21
21
|
|
|
22
22
|
__all__ = ["ClassModel", "ClassReader", "ClassWriter", "JarFile"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal implementation helpers for pytecode."""
|
|
@@ -27,7 +27,7 @@ from collections.abc import Sequence
|
|
|
27
27
|
from dataclasses import dataclass
|
|
28
28
|
from typing import TYPE_CHECKING
|
|
29
29
|
|
|
30
|
-
from .attributes import (
|
|
30
|
+
from ..classfile.attributes import (
|
|
31
31
|
AppendFrameInfo,
|
|
32
32
|
ChopFrameInfo,
|
|
33
33
|
DoubleVariableInfo,
|
|
@@ -48,11 +48,11 @@ from .attributes import (
|
|
|
48
48
|
UninitializedVariableInfo,
|
|
49
49
|
VerificationTypeInfo,
|
|
50
50
|
)
|
|
51
|
-
from .constants import VerificationType
|
|
52
|
-
from .descriptors import (
|
|
51
|
+
from ..classfile.constants import VerificationType
|
|
52
|
+
from ..classfile.descriptors import (
|
|
53
53
|
ArrayType as DescArrayType,
|
|
54
54
|
)
|
|
55
|
-
from .descriptors import (
|
|
55
|
+
from ..classfile.descriptors import (
|
|
56
56
|
BaseType,
|
|
57
57
|
FieldDescriptor,
|
|
58
58
|
ObjectType,
|
|
@@ -60,15 +60,14 @@ from .descriptors import (
|
|
|
60
60
|
parse_field_descriptor,
|
|
61
61
|
parse_method_descriptor,
|
|
62
62
|
)
|
|
63
|
-
from .
|
|
64
|
-
from .instructions import (
|
|
63
|
+
from ..classfile.instructions import (
|
|
65
64
|
ArrayType as InsnArrayType,
|
|
66
65
|
)
|
|
67
|
-
from .instructions import (
|
|
66
|
+
from ..classfile.instructions import (
|
|
68
67
|
InsnInfo,
|
|
69
68
|
InsnInfoType,
|
|
70
69
|
)
|
|
71
|
-
from .labels import (
|
|
70
|
+
from ..edit.labels import (
|
|
72
71
|
BranchInsn,
|
|
73
72
|
CodeItem,
|
|
74
73
|
ExceptionHandler,
|
|
@@ -76,7 +75,7 @@ from .labels import (
|
|
|
76
75
|
LookupSwitchInsn,
|
|
77
76
|
TableSwitchInsn,
|
|
78
77
|
)
|
|
79
|
-
from .operands import (
|
|
78
|
+
from ..edit.operands import (
|
|
80
79
|
FieldInsn,
|
|
81
80
|
IIncInsn,
|
|
82
81
|
InterfaceMethodInsn,
|
|
@@ -95,11 +94,12 @@ from .operands import (
|
|
|
95
94
|
TypeInsn,
|
|
96
95
|
VarInsn,
|
|
97
96
|
)
|
|
97
|
+
from .hierarchy import JAVA_LANG_OBJECT, common_superclass
|
|
98
98
|
|
|
99
99
|
if TYPE_CHECKING:
|
|
100
|
-
from .constant_pool_builder import ConstantPoolBuilder
|
|
100
|
+
from ..edit.constant_pool_builder import ConstantPoolBuilder
|
|
101
|
+
from ..edit.model import CodeModel, MethodModel
|
|
101
102
|
from .hierarchy import ClassResolver
|
|
102
|
-
from .model import CodeModel, MethodModel
|
|
103
103
|
|
|
104
104
|
# ===================================================================
|
|
105
105
|
# Analysis errors
|
|
@@ -435,7 +435,7 @@ def initial_frame(method: MethodModel, class_name: str) -> FrameState:
|
|
|
435
435
|
Returns:
|
|
436
436
|
A ``FrameState`` representing the method entry point.
|
|
437
437
|
"""
|
|
438
|
-
from .constants import MethodAccessFlag
|
|
438
|
+
from ..classfile.constants import MethodAccessFlag
|
|
439
439
|
|
|
440
440
|
md = parse_method_descriptor(method.descriptor)
|
|
441
441
|
locals_list: list[VType] = []
|
|
@@ -1936,7 +1936,7 @@ def _simulate_raw_insn(insn: InsnInfo, state: FrameState) -> FrameState:
|
|
|
1936
1936
|
# --- NEWARRAY ---
|
|
1937
1937
|
if opcode == _T.NEWARRAY:
|
|
1938
1938
|
state, _ = state.pop(1) # pop count
|
|
1939
|
-
from .instructions import NewArray as NewArrayInsn
|
|
1939
|
+
from ..classfile.instructions import NewArray as NewArrayInsn
|
|
1940
1940
|
|
|
1941
1941
|
if isinstance(insn, NewArrayInsn):
|
|
1942
1942
|
array_desc = _NEWARRAY_TYPE_MAP.get(insn.atype, "[I")
|
|
@@ -2279,7 +2279,7 @@ def compute_frames(
|
|
|
2279
2279
|
analysis_code = _prepare_analysis_code(code)
|
|
2280
2280
|
analysis_label_offsets = label_offsets
|
|
2281
2281
|
if analysis_code is not code:
|
|
2282
|
-
from .labels import resolve_labels
|
|
2282
|
+
from ..edit.labels import resolve_labels
|
|
2283
2283
|
|
|
2284
2284
|
analysis_label_offsets = resolve_labels(list(analysis_code.instructions), cp).label_offsets
|
|
2285
2285
|
|
|
@@ -13,13 +13,13 @@ from collections.abc import Iterable, Iterator
|
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from typing import TYPE_CHECKING, Protocol
|
|
15
15
|
|
|
16
|
-
from .constant_pool import ClassInfo
|
|
17
|
-
from .
|
|
18
|
-
from .
|
|
19
|
-
from .
|
|
16
|
+
from ..classfile.constant_pool import ClassInfo
|
|
17
|
+
from ..classfile.constants import ClassAccessFlag, MethodAccessFlag
|
|
18
|
+
from ..classfile.info import ClassFile
|
|
19
|
+
from ..edit.constant_pool_builder import ConstantPoolBuilder
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
|
-
from .model import ClassModel
|
|
22
|
+
from ..edit.model import ClassModel
|
|
23
23
|
|
|
24
24
|
JAVA_LANG_OBJECT = "java/lang/Object"
|
|
25
25
|
|
|
@@ -494,7 +494,7 @@ def _resolve_class_name(cp: ConstantPoolBuilder, index: int) -> str:
|
|
|
494
494
|
ValueError: If the entry at *index* is not a ``ClassInfo``.
|
|
495
495
|
"""
|
|
496
496
|
|
|
497
|
-
entry = cp.
|
|
497
|
+
entry = cp.peek(index)
|
|
498
498
|
if not isinstance(entry, ClassInfo):
|
|
499
499
|
raise ValueError(f"CP index {index} is not a CONSTANT_Class: {type(entry).__name__}")
|
|
500
500
|
return cp.resolve_utf8(entry.name_index)
|
|
@@ -15,7 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
from dataclasses import dataclass, field
|
|
16
16
|
from enum import Enum
|
|
17
17
|
|
|
18
|
-
from .attributes import (
|
|
18
|
+
from ..classfile.attributes import (
|
|
19
19
|
AnnotationDefaultAttr,
|
|
20
20
|
AttributeInfo,
|
|
21
21
|
BootstrapMethodsAttr,
|
|
@@ -41,7 +41,7 @@ from .attributes import (
|
|
|
41
41
|
SourceDebugExtensionAttr,
|
|
42
42
|
StackMapTableAttr,
|
|
43
43
|
)
|
|
44
|
-
from .constant_pool import (
|
|
44
|
+
from ..classfile.constant_pool import (
|
|
45
45
|
ClassInfo,
|
|
46
46
|
ConstantPoolInfo,
|
|
47
47
|
DoubleInfo,
|
|
@@ -61,11 +61,10 @@ from .constant_pool import (
|
|
|
61
61
|
StringInfo,
|
|
62
62
|
Utf8Info,
|
|
63
63
|
)
|
|
64
|
-
from .constants import MAGIC, ClassAccessFlag, FieldAccessFlag, MethodAccessFlag
|
|
65
|
-
from .
|
|
66
|
-
from .
|
|
67
|
-
from .
|
|
68
|
-
from .instructions import (
|
|
64
|
+
from ..classfile.constants import MAGIC, ClassAccessFlag, FieldAccessFlag, MethodAccessFlag
|
|
65
|
+
from ..classfile.descriptors import is_valid_field_descriptor, is_valid_method_descriptor
|
|
66
|
+
from ..classfile.info import ClassFile, FieldInfo, MethodInfo
|
|
67
|
+
from ..classfile.instructions import (
|
|
69
68
|
Branch,
|
|
70
69
|
BranchW,
|
|
71
70
|
ConstPoolIndex,
|
|
@@ -77,14 +76,15 @@ from .instructions import (
|
|
|
77
76
|
MultiANewArray,
|
|
78
77
|
TableSwitch,
|
|
79
78
|
)
|
|
80
|
-
from .
|
|
79
|
+
from ..classfile.modified_utf8 import decode_modified_utf8
|
|
80
|
+
from ..edit.debug_info import is_class_debug_info_stale, is_code_debug_info_stale
|
|
81
|
+
from ..edit.labels import (
|
|
81
82
|
BranchInsn,
|
|
82
83
|
Label,
|
|
83
84
|
LookupSwitchInsn,
|
|
84
85
|
TableSwitchInsn,
|
|
85
86
|
)
|
|
86
|
-
from .model import ClassModel, CodeModel, FieldModel, MethodModel
|
|
87
|
-
from .modified_utf8 import decode_modified_utf8
|
|
87
|
+
from ..edit.model import ClassModel, CodeModel, FieldModel, MethodModel
|
|
88
88
|
|
|
89
89
|
__all__ = [
|
|
90
90
|
"Category",
|
|
@@ -1000,110 +1000,123 @@ def _verify_code_cp_refs(
|
|
|
1000
1000
|
)
|
|
1001
1001
|
|
|
1002
1002
|
if isinstance(insn, ConstPoolIndex):
|
|
1003
|
-
|
|
1004
|
-
if
|
|
1003
|
+
cp_entry = _cp_entry(cp, insn.index)
|
|
1004
|
+
if cp_entry is None:
|
|
1005
1005
|
dc.add(Severity.ERROR, Category.CODE, f"{insn.type.name} references invalid CP index {insn.index}", loc)
|
|
1006
1006
|
continue
|
|
1007
|
+
cp_entry_type = type(cp_entry)
|
|
1007
1008
|
|
|
1008
1009
|
if insn.type in _FIELD_OPS:
|
|
1009
|
-
if not isinstance(
|
|
1010
|
+
if not isinstance(cp_entry, FieldrefInfo):
|
|
1010
1011
|
dc.add(
|
|
1011
1012
|
Severity.ERROR,
|
|
1012
1013
|
Category.CODE,
|
|
1013
|
-
f"{insn.type.name} CP#{insn.index} expected FieldrefInfo, got {
|
|
1014
|
+
f"{insn.type.name} CP#{insn.index} expected FieldrefInfo, got {cp_entry_type.__name__}",
|
|
1014
1015
|
loc,
|
|
1015
1016
|
)
|
|
1016
1017
|
elif insn.type in _METHOD_OPS:
|
|
1017
1018
|
if major >= 52:
|
|
1018
|
-
if not isinstance(
|
|
1019
|
+
if not isinstance(cp_entry, (MethodrefInfo, InterfaceMethodrefInfo)):
|
|
1019
1020
|
msg = (
|
|
1020
1021
|
f"{insn.type.name} CP#{insn.index} expected "
|
|
1021
|
-
f"Methodref/InterfaceMethodref, got {
|
|
1022
|
+
f"Methodref/InterfaceMethodref, got {cp_entry_type.__name__}"
|
|
1022
1023
|
)
|
|
1023
1024
|
dc.add(Severity.ERROR, Category.CODE, msg, loc)
|
|
1024
|
-
elif not isinstance(
|
|
1025
|
+
elif not isinstance(cp_entry, MethodrefInfo):
|
|
1025
1026
|
dc.add(
|
|
1026
1027
|
Severity.ERROR,
|
|
1027
1028
|
Category.CODE,
|
|
1028
|
-
f"{insn.type.name} CP#{insn.index} expected MethodrefInfo, got {
|
|
1029
|
+
f"{insn.type.name} CP#{insn.index} expected MethodrefInfo, got {cp_entry_type.__name__}",
|
|
1029
1030
|
loc,
|
|
1030
1031
|
)
|
|
1031
1032
|
elif insn.type in _CLASS_OPS:
|
|
1032
|
-
if not isinstance(
|
|
1033
|
+
if not isinstance(cp_entry, ClassInfo):
|
|
1033
1034
|
dc.add(
|
|
1034
1035
|
Severity.ERROR,
|
|
1035
1036
|
Category.CODE,
|
|
1036
|
-
f"{insn.type.name} CP#{insn.index} expected ClassInfo, got {
|
|
1037
|
+
f"{insn.type.name} CP#{insn.index} expected ClassInfo, got {cp_entry_type.__name__}",
|
|
1037
1038
|
loc,
|
|
1038
1039
|
)
|
|
1039
1040
|
elif insn.type == InsnInfoType.LDC_W:
|
|
1040
|
-
_verify_ldc_entry(
|
|
1041
|
+
_verify_ldc_entry(cp_entry, insn.index, major, loc, dc)
|
|
1041
1042
|
elif insn.type == InsnInfoType.LDC2_W:
|
|
1042
|
-
if not isinstance(
|
|
1043
|
+
if not isinstance(cp_entry, (LongInfo, DoubleInfo)):
|
|
1043
1044
|
dc.add(
|
|
1044
1045
|
Severity.ERROR,
|
|
1045
1046
|
Category.CODE,
|
|
1046
|
-
f"LDC2_W CP#{insn.index} expected Long/Double, got {
|
|
1047
|
+
f"LDC2_W CP#{insn.index} expected Long/Double, got {cp_entry_type.__name__}",
|
|
1047
1048
|
loc,
|
|
1048
1049
|
)
|
|
1049
1050
|
|
|
1050
1051
|
elif isinstance(insn, LocalIndex) and insn.type == InsnInfoType.LDC:
|
|
1051
|
-
|
|
1052
|
-
if
|
|
1052
|
+
ldc_entry = _cp_entry(cp, insn.index)
|
|
1053
|
+
if ldc_entry is None:
|
|
1053
1054
|
dc.add(Severity.ERROR, Category.CODE, f"LDC references invalid CP index {insn.index}", loc)
|
|
1054
1055
|
else:
|
|
1055
|
-
_verify_ldc_entry(
|
|
1056
|
+
_verify_ldc_entry(ldc_entry, insn.index, major, loc, dc)
|
|
1056
1057
|
|
|
1057
1058
|
elif isinstance(insn, InvokeInterface):
|
|
1058
|
-
|
|
1059
|
-
if
|
|
1059
|
+
invoke_interface_entry = _cp_entry(cp, insn.index)
|
|
1060
|
+
if invoke_interface_entry is None:
|
|
1060
1061
|
dc.add(
|
|
1061
1062
|
Severity.ERROR,
|
|
1062
1063
|
Category.CODE,
|
|
1063
1064
|
f"INVOKEINTERFACE references invalid CP index {insn.index}",
|
|
1064
1065
|
loc,
|
|
1065
1066
|
)
|
|
1066
|
-
|
|
1067
|
+
else:
|
|
1068
|
+
invoke_interface_entry_type = type(invoke_interface_entry)
|
|
1069
|
+
if isinstance(invoke_interface_entry, InterfaceMethodrefInfo):
|
|
1070
|
+
continue
|
|
1067
1071
|
dc.add(
|
|
1068
1072
|
Severity.ERROR,
|
|
1069
1073
|
Category.CODE,
|
|
1070
|
-
|
|
1074
|
+
"INVOKEINTERFACE "
|
|
1075
|
+
f"CP#{insn.index} expected InterfaceMethodrefInfo, got "
|
|
1076
|
+
f"{invoke_interface_entry_type.__name__}",
|
|
1071
1077
|
loc,
|
|
1072
1078
|
)
|
|
1073
1079
|
|
|
1074
1080
|
elif isinstance(insn, InvokeDynamic):
|
|
1075
|
-
|
|
1076
|
-
if
|
|
1081
|
+
invoke_dynamic_entry = _cp_entry(cp, insn.index)
|
|
1082
|
+
if invoke_dynamic_entry is None:
|
|
1077
1083
|
dc.add(
|
|
1078
1084
|
Severity.ERROR,
|
|
1079
1085
|
Category.CODE,
|
|
1080
1086
|
f"INVOKEDYNAMIC references invalid CP index {insn.index}",
|
|
1081
1087
|
loc,
|
|
1082
1088
|
)
|
|
1083
|
-
|
|
1089
|
+
else:
|
|
1090
|
+
invoke_dynamic_entry_type = type(invoke_dynamic_entry)
|
|
1091
|
+
if isinstance(invoke_dynamic_entry, InvokeDynamicInfo):
|
|
1092
|
+
continue
|
|
1084
1093
|
dc.add(
|
|
1085
1094
|
Severity.ERROR,
|
|
1086
1095
|
Category.CODE,
|
|
1087
|
-
f"INVOKEDYNAMIC CP#{insn.index} expected InvokeDynamicInfo,
|
|
1096
|
+
f"INVOKEDYNAMIC CP#{insn.index} expected InvokeDynamicInfo, "
|
|
1097
|
+
f"got {invoke_dynamic_entry_type.__name__}",
|
|
1088
1098
|
loc,
|
|
1089
1099
|
)
|
|
1090
1100
|
|
|
1091
1101
|
elif isinstance(insn, MultiANewArray):
|
|
1092
|
-
|
|
1093
|
-
if
|
|
1102
|
+
multi_anew_array_entry = _cp_entry(cp, insn.index)
|
|
1103
|
+
if multi_anew_array_entry is None:
|
|
1094
1104
|
dc.add(
|
|
1095
1105
|
Severity.ERROR,
|
|
1096
1106
|
Category.CODE,
|
|
1097
1107
|
f"MULTIANEWARRAY references invalid CP index {insn.index}",
|
|
1098
1108
|
loc,
|
|
1099
1109
|
)
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1110
|
+
else:
|
|
1111
|
+
multi_anew_array_entry_type = type(multi_anew_array_entry)
|
|
1112
|
+
if not isinstance(multi_anew_array_entry, ClassInfo):
|
|
1113
|
+
dc.add(
|
|
1114
|
+
Severity.ERROR,
|
|
1115
|
+
Category.CODE,
|
|
1116
|
+
f"MULTIANEWARRAY CP#{insn.index} expected ClassInfo, "
|
|
1117
|
+
f"got {multi_anew_array_entry_type.__name__}",
|
|
1118
|
+
loc,
|
|
1119
|
+
)
|
|
1107
1120
|
if insn.dimensions < 1:
|
|
1108
1121
|
dc.add(
|
|
1109
1122
|
Severity.ERROR,
|
|
@@ -1115,6 +1128,7 @@ def _verify_code_cp_refs(
|
|
|
1115
1128
|
|
|
1116
1129
|
def _verify_ldc_entry(entry: ConstantPoolInfo, idx: int, major: int, loc: Location, dc: _Collector) -> None:
|
|
1117
1130
|
"""Validate that an LDC/LDC_W entry is a valid loadable type."""
|
|
1131
|
+
entry_type = type(entry)
|
|
1118
1132
|
if isinstance(entry, (IntegerInfo, FloatInfo, StringInfo)):
|
|
1119
1133
|
return
|
|
1120
1134
|
if isinstance(entry, ClassInfo):
|
|
@@ -1131,7 +1145,7 @@ def _verify_ldc_entry(entry: ConstantPoolInfo, idx: int, major: int, loc: Locati
|
|
|
1131
1145
|
dc.add(
|
|
1132
1146
|
Severity.ERROR,
|
|
1133
1147
|
Category.CODE,
|
|
1134
|
-
f"LDC CP#{idx} {
|
|
1148
|
+
f"LDC CP#{idx} {entry_type.__name__} requires version >= 51, got {major}",
|
|
1135
1149
|
loc,
|
|
1136
1150
|
)
|
|
1137
1151
|
return
|
|
@@ -1147,7 +1161,7 @@ def _verify_ldc_entry(entry: ConstantPoolInfo, idx: int, major: int, loc: Locati
|
|
|
1147
1161
|
dc.add(
|
|
1148
1162
|
Severity.ERROR,
|
|
1149
1163
|
Category.CODE,
|
|
1150
|
-
f"LDC CP#{idx} has non-loadable type {
|
|
1164
|
+
f"LDC CP#{idx} has non-loadable type {entry_type.__name__}",
|
|
1151
1165
|
loc,
|
|
1152
1166
|
)
|
|
1153
1167
|
|
|
@@ -9,11 +9,11 @@ import zipfile
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path, PurePosixPath
|
|
11
11
|
|
|
12
|
-
from .
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
15
|
-
from .model import ClassModel
|
|
16
|
-
from
|
|
12
|
+
from ..analysis.hierarchy import ClassResolver
|
|
13
|
+
from ..classfile.reader import ClassReader
|
|
14
|
+
from ..edit.debug_info import DebugInfoPolicy, normalize_debug_info_policy
|
|
15
|
+
from ..edit.model import ClassModel
|
|
16
|
+
from ..transforms import ClassTransform
|
|
17
17
|
|
|
18
18
|
__all__ = ["JarFile", "JarInfo"]
|
|
19
19
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Parsed JVM classfile structures, parsing, and emission."""
|
|
2
|
+
|
|
3
|
+
from .info import ClassFile, FieldInfo, MethodInfo
|
|
4
|
+
from .reader import ClassReader, MalformedClassException
|
|
5
|
+
from .writer import ClassWriter
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ClassFile",
|
|
9
|
+
"ClassReader",
|
|
10
|
+
"ClassWriter",
|
|
11
|
+
"FieldInfo",
|
|
12
|
+
"MalformedClassException",
|
|
13
|
+
"MethodInfo",
|
|
14
|
+
]
|