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.
Files changed (61) hide show
  1. {pytecode-0.0.1/pytecode.egg-info → pytecode-0.0.3}/PKG-INFO +29 -19
  2. {pytecode-0.0.1 → pytecode-0.0.3}/README.md +28 -18
  3. {pytecode-0.0.1 → pytecode-0.0.3}/pyproject.toml +2 -2
  4. {pytecode-0.0.1 → pytecode-0.0.3}/pytecode/__init__.py +5 -5
  5. pytecode-0.0.3/pytecode/_internal/__init__.py +1 -0
  6. pytecode-0.0.1/pytecode/analysis.py → pytecode-0.0.3/pytecode/analysis/__init__.py +14 -14
  7. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/analysis}/hierarchy.py +6 -6
  8. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/analysis}/verify.py +59 -45
  9. pytecode-0.0.1/pytecode/jar.py → pytecode-0.0.3/pytecode/archive/__init__.py +5 -5
  10. pytecode-0.0.3/pytecode/classfile/__init__.py +14 -0
  11. pytecode-0.0.1/pytecode/class_reader.py → pytecode-0.0.3/pytecode/classfile/reader.py +4 -4
  12. pytecode-0.0.1/pytecode/class_writer.py → pytecode-0.0.3/pytecode/classfile/writer.py +1 -1
  13. pytecode-0.0.3/pytecode/edit/__init__.py +12 -0
  14. pytecode-0.0.3/pytecode/edit/_attribute_clone.py +546 -0
  15. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/constant_pool_builder.py +471 -69
  16. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/debug_info.py +1 -1
  17. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/labels.py +341 -145
  18. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/model.py +131 -80
  19. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/edit}/operands.py +1 -1
  20. pytecode-0.0.1/pytecode/transforms.py → pytecode-0.0.3/pytecode/transforms/__init__.py +3 -3
  21. {pytecode-0.0.1 → pytecode-0.0.3/pytecode.egg-info}/PKG-INFO +29 -19
  22. {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/SOURCES.txt +24 -20
  23. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_analysis.py +45 -56
  24. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_api_docs.py +6 -6
  25. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_attributes.py +4 -1
  26. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_bytes_utils.py +1 -1
  27. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_cfg_oracle.py +4 -8
  28. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_class_reader.py +7 -4
  29. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_class_writer.py +7 -4
  30. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_constant_pool.py +3 -3
  31. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_constant_pool_builder.py +174 -4
  32. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_debug_info.py +22 -150
  33. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_descriptors.py +2 -2
  34. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_helpers.py +122 -0
  35. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_hierarchy.py +5 -5
  36. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_instructions.py +1 -1
  37. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_jar.py +8 -14
  38. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_labels.py +179 -126
  39. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_model.py +246 -40
  40. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_modified_utf8.py +1 -1
  41. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_operands.py +16 -25
  42. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_transforms.py +3 -3
  43. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_validation.py +2 -2
  44. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_verify.py +31 -31
  45. {pytecode-0.0.1 → pytecode-0.0.3}/LICENSE +0 -0
  46. {pytecode-0.0.1 → pytecode-0.0.3}/MANIFEST.in +0 -0
  47. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/_internal}/bytes_utils.py +0 -0
  48. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/attributes.py +0 -0
  49. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/constant_pool.py +0 -0
  50. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/constants.py +0 -0
  51. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/descriptors.py +0 -0
  52. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/info.py +0 -0
  53. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/instructions.py +0 -0
  54. {pytecode-0.0.1/pytecode → pytecode-0.0.3/pytecode/classfile}/modified_utf8.py +0 -0
  55. {pytecode-0.0.1 → pytecode-0.0.3}/pytecode/py.typed +0 -0
  56. {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/dependency_links.txt +0 -0
  57. {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/requires.txt +0 -0
  58. {pytecode-0.0.1 → pytecode-0.0.3}/pytecode.egg-info/top_level.txt +0 -0
  59. {pytecode-0.0.1 → pytecode-0.0.3}/setup.cfg +0 -0
  60. {pytecode-0.0.1 → pytecode-0.0.3}/tests/test_javap_parser.py +0 -0
  61. {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.1
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\generate_api_docs.py --check
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\generate_api_docs.py
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
- 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.
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 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.
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\generate_api_docs.py --check
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. 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.
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>\output\<jar stem>\parsed\`, and writes class-model-derived rewritten `.class` files plus copied resources under `<jar parent>\output\<jar stem>\rewritten\`.
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 .\run.py .\path\to\input.jar
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\generate_api_docs.py --check
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\generate_api_docs.py
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
- 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.
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 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.
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\generate_api_docs.py --check
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. 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.
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>\output\<jar stem>\parsed\`, and writes class-model-derived rewritten `.class` files plus copied resources under `<jar parent>\output\<jar stem>\rewritten\`.
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 .\run.py .\path\to\input.jar
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.1"
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 .class_reader import ClassReader
18
- from .class_writer import ClassWriter
19
- from .jar import JarFile
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 .hierarchy import JAVA_LANG_OBJECT, common_superclass
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 .constant_pool_builder import ConstantPoolBuilder
18
- from .constants import ClassAccessFlag, MethodAccessFlag
19
- from .info import ClassFile
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.get(index)
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 .debug_info import is_class_debug_info_stale, is_code_debug_info_stale
66
- from .descriptors import is_valid_field_descriptor, is_valid_method_descriptor
67
- from .info import ClassFile, FieldInfo, MethodInfo
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 .labels import (
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
- entry = _cp_entry(cp, insn.index)
1004
- if entry is None:
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(entry, FieldrefInfo):
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 {type(entry).__name__}",
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(entry, (MethodrefInfo, InterfaceMethodrefInfo)):
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 {type(entry).__name__}"
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(entry, MethodrefInfo):
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 {type(entry).__name__}",
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(entry, ClassInfo):
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 {type(entry).__name__}",
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(entry, insn.index, major, loc, dc)
1041
+ _verify_ldc_entry(cp_entry, insn.index, major, loc, dc)
1041
1042
  elif insn.type == InsnInfoType.LDC2_W:
1042
- if not isinstance(entry, (LongInfo, DoubleInfo)):
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 {type(entry).__name__}",
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
- entry = _cp_entry(cp, insn.index)
1052
- if entry is None:
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(entry, insn.index, major, loc, dc)
1056
+ _verify_ldc_entry(ldc_entry, insn.index, major, loc, dc)
1056
1057
 
1057
1058
  elif isinstance(insn, InvokeInterface):
1058
- entry = _cp_entry(cp, insn.index)
1059
- if entry is None:
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
- elif not isinstance(entry, InterfaceMethodrefInfo):
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
- f"INVOKEINTERFACE CP#{insn.index} expected InterfaceMethodrefInfo, got {type(entry).__name__}",
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
- entry = _cp_entry(cp, insn.index)
1076
- if entry is None:
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
- elif not isinstance(entry, InvokeDynamicInfo):
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, got {type(entry).__name__}",
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
- entry = _cp_entry(cp, insn.index)
1093
- if entry is None:
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
- elif not isinstance(entry, ClassInfo):
1101
- dc.add(
1102
- Severity.ERROR,
1103
- Category.CODE,
1104
- f"MULTIANEWARRAY CP#{insn.index} expected ClassInfo, got {type(entry).__name__}",
1105
- loc,
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} {type(entry).__name__} requires version >= 51, got {major}",
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 {type(entry).__name__}",
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 .class_reader import ClassReader
13
- from .debug_info import DebugInfoPolicy, normalize_debug_info_policy
14
- from .hierarchy import ClassResolver
15
- from .model import ClassModel
16
- from .transforms import ClassTransform
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
+ ]