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
pytecode/hierarchy.py
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"""Class hierarchy resolution helpers for JVM internal names.
|
|
2
|
+
|
|
3
|
+
This module provides a small, pluggable foundation for hierarchy-aware JVM
|
|
4
|
+
analysis. It intentionally stays narrower than full verifier type merging:
|
|
5
|
+
it resolves class/interface metadata, answers subtype queries, walks ancestor
|
|
6
|
+
chains, finds common superclasses, and reports inherited method declarations
|
|
7
|
+
that a class method overrides.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Iterable, Iterator
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import TYPE_CHECKING, Protocol
|
|
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
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from .model import ClassModel
|
|
23
|
+
|
|
24
|
+
JAVA_LANG_OBJECT = "java/lang/Object"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HierarchyError(Exception):
|
|
28
|
+
"""Base class for hierarchy-resolution failures."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class UnresolvedClassError(HierarchyError, LookupError):
|
|
32
|
+
"""Raised when a hierarchy query needs a class the resolver cannot provide."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, class_name: str) -> None:
|
|
35
|
+
"""Initialize with the unresolved class name.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
class_name: JVM internal name that could not be resolved.
|
|
39
|
+
"""
|
|
40
|
+
self.class_name = class_name
|
|
41
|
+
super().__init__(f"Could not resolve class {class_name!r}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HierarchyCycleError(HierarchyError):
|
|
45
|
+
"""Raised when a malformed class graph contains an ancestry cycle."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, cycle: tuple[str, ...]) -> None:
|
|
48
|
+
"""Initialize with the detected cycle.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
cycle: Sequence of internal names forming the cycle.
|
|
52
|
+
"""
|
|
53
|
+
self.cycle = cycle
|
|
54
|
+
joined = " -> ".join(cycle)
|
|
55
|
+
super().__init__(f"Hierarchy cycle detected: {joined}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class ResolvedMethod:
|
|
60
|
+
"""A method declaration resolved out of raw class metadata.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
name: Method name (e.g. ``<init>`` or ``toString``).
|
|
64
|
+
descriptor: JVM method descriptor (e.g. ``(I)V``).
|
|
65
|
+
access_flags: Bitfield of method access and property flags.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
descriptor: str
|
|
70
|
+
access_flags: MethodAccessFlag
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True, slots=True)
|
|
74
|
+
class ResolvedClass:
|
|
75
|
+
"""Resolved hierarchy snapshot for one class or interface.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
name: JVM internal name (e.g. ``java/lang/String``).
|
|
79
|
+
super_name: Internal name of the direct superclass, or ``None`` for ``java/lang/Object``.
|
|
80
|
+
interfaces: Internal names of directly implemented interfaces.
|
|
81
|
+
access_flags: Bitfield of class access and property flags.
|
|
82
|
+
methods: Declared methods extracted from the class.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
name: str
|
|
86
|
+
super_name: str | None
|
|
87
|
+
interfaces: tuple[str, ...]
|
|
88
|
+
access_flags: ClassAccessFlag
|
|
89
|
+
methods: tuple[ResolvedMethod, ...] = ()
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def is_interface(self) -> bool:
|
|
93
|
+
"""Whether this entry represents an interface."""
|
|
94
|
+
|
|
95
|
+
return bool(self.access_flags & ClassAccessFlag.INTERFACE)
|
|
96
|
+
|
|
97
|
+
def find_method(self, name: str, descriptor: str) -> ResolvedMethod | None:
|
|
98
|
+
"""Return the declared method with the given signature, if present.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
name: Method name to match.
|
|
102
|
+
descriptor: JVM method descriptor to match.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The matching method, or ``None`` if not declared in this class.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
for method in self.methods:
|
|
109
|
+
if method.name == name and method.descriptor == descriptor:
|
|
110
|
+
return method
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_classfile(cls, classfile: ClassFile) -> ResolvedClass:
|
|
115
|
+
"""Build a resolved hierarchy snapshot from a parsed ``ClassFile``.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
classfile: A parsed JVM class file.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
A new ``ResolvedClass`` populated from the class file metadata.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
cp = ConstantPoolBuilder.from_pool(classfile.constant_pool)
|
|
125
|
+
methods = tuple(
|
|
126
|
+
ResolvedMethod(
|
|
127
|
+
cp.resolve_utf8(method.name_index),
|
|
128
|
+
cp.resolve_utf8(method.descriptor_index),
|
|
129
|
+
method.access_flags,
|
|
130
|
+
)
|
|
131
|
+
for method in classfile.methods
|
|
132
|
+
)
|
|
133
|
+
return cls(
|
|
134
|
+
name=_resolve_class_name(cp, classfile.this_class),
|
|
135
|
+
super_name=None if classfile.super_class == 0 else _resolve_class_name(cp, classfile.super_class),
|
|
136
|
+
interfaces=tuple(_resolve_class_name(cp, index) for index in classfile.interfaces),
|
|
137
|
+
access_flags=classfile.access_flags,
|
|
138
|
+
methods=methods,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def from_model(cls, model: ClassModel) -> ResolvedClass:
|
|
143
|
+
"""Build a resolved hierarchy snapshot from a ``ClassModel``.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
model: A high-level class model.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
A new ``ResolvedClass`` populated from the model.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
return cls(
|
|
153
|
+
name=model.name,
|
|
154
|
+
super_name=model.super_name,
|
|
155
|
+
interfaces=tuple(model.interfaces),
|
|
156
|
+
access_flags=model.access_flags,
|
|
157
|
+
methods=tuple(
|
|
158
|
+
ResolvedMethod(method.name, method.descriptor, method.access_flags) for method in model.methods
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass(frozen=True, slots=True)
|
|
164
|
+
class InheritedMethod:
|
|
165
|
+
"""A matching inherited method declaration found in a supertype.
|
|
166
|
+
|
|
167
|
+
Attributes:
|
|
168
|
+
owner: Internal name of the class that declares the method.
|
|
169
|
+
name: The method name.
|
|
170
|
+
descriptor: The JVM method descriptor.
|
|
171
|
+
access_flags: Bitfield of method access and property flags.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
owner: str
|
|
175
|
+
name: str
|
|
176
|
+
descriptor: str
|
|
177
|
+
access_flags: MethodAccessFlag
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ClassResolver(Protocol):
|
|
181
|
+
"""Protocol for supplying resolved class metadata by internal name."""
|
|
182
|
+
|
|
183
|
+
def resolve_class(self, class_name: str) -> ResolvedClass | None:
|
|
184
|
+
"""Return resolved metadata for *class_name*, or ``None`` if unavailable.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
class_name: JVM internal class name to resolve.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Resolved class metadata, or ``None`` if the class cannot be found.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class MappingClassResolver:
|
|
195
|
+
"""Simple in-memory ``ClassResolver`` backed by resolved class snapshots."""
|
|
196
|
+
|
|
197
|
+
__slots__ = ("_classes",)
|
|
198
|
+
|
|
199
|
+
def __init__(self, classes: Iterable[ResolvedClass]) -> None:
|
|
200
|
+
"""Initialize from an iterable of resolved class snapshots.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
classes: Resolved class entries to index by internal name.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
ValueError: If duplicate class names are found.
|
|
207
|
+
"""
|
|
208
|
+
mapping: dict[str, ResolvedClass] = {}
|
|
209
|
+
for resolved in classes:
|
|
210
|
+
if resolved.name in mapping:
|
|
211
|
+
raise ValueError(f"Duplicate resolved class {resolved.name!r}")
|
|
212
|
+
mapping[resolved.name] = resolved
|
|
213
|
+
self._classes = mapping
|
|
214
|
+
|
|
215
|
+
def resolve_class(self, class_name: str) -> ResolvedClass | None:
|
|
216
|
+
"""Resolve *class_name* from the in-memory mapping.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
class_name: JVM internal class name to look up.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Resolved class metadata, or ``None`` if not in the mapping.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
return self._classes.get(class_name)
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_classfiles(cls, classfiles: Iterable[ClassFile]) -> MappingClassResolver:
|
|
229
|
+
"""Build a resolver from parsed ``ClassFile`` objects.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
classfiles: Parsed JVM class files to index.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A new resolver backed by the given class files.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
return cls(ResolvedClass.from_classfile(classfile) for classfile in classfiles)
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def from_models(cls, models: Iterable[ClassModel]) -> MappingClassResolver:
|
|
242
|
+
"""Build a resolver from ``ClassModel`` objects.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
models: High-level class models to index.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
A new resolver backed by the given models.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
return cls(ResolvedClass.from_model(model) for model in models)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
_IMPLICIT_OBJECT = ResolvedClass(
|
|
255
|
+
name=JAVA_LANG_OBJECT,
|
|
256
|
+
super_name=None,
|
|
257
|
+
interfaces=(),
|
|
258
|
+
access_flags=ClassAccessFlag.PUBLIC | ClassAccessFlag.SUPER,
|
|
259
|
+
methods=(),
|
|
260
|
+
)
|
|
261
|
+
_NON_OVERRIDABLE_METHOD_FLAGS = MethodAccessFlag.PRIVATE | MethodAccessFlag.STATIC | MethodAccessFlag.FINAL
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def iter_superclasses(
|
|
265
|
+
resolver: ClassResolver,
|
|
266
|
+
class_name: str,
|
|
267
|
+
*,
|
|
268
|
+
include_self: bool = False,
|
|
269
|
+
) -> Iterator[ResolvedClass]:
|
|
270
|
+
"""Yield the linear superclass chain for *class_name*.
|
|
271
|
+
|
|
272
|
+
The returned chain always terminates at ``java/lang/Object``. If the
|
|
273
|
+
resolver does not explicitly provide that root class, an implicit stub is
|
|
274
|
+
used so ordinary user-defined class hierarchies can still terminate cleanly.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
resolver: Provider of class metadata.
|
|
278
|
+
class_name: JVM internal name of the starting class.
|
|
279
|
+
include_self: If ``True``, include the starting class itself.
|
|
280
|
+
|
|
281
|
+
Yields:
|
|
282
|
+
Each superclass from the immediate parent up to ``java/lang/Object``.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
UnresolvedClassError: If a class in the chain cannot be resolved.
|
|
286
|
+
HierarchyCycleError: If a cycle is detected in the superclass chain.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
current_name = class_name if include_self else _resolve_required(resolver, class_name).super_name
|
|
290
|
+
seen: set[str] = set()
|
|
291
|
+
path: list[str] = [class_name]
|
|
292
|
+
|
|
293
|
+
while current_name is not None:
|
|
294
|
+
if current_name in seen:
|
|
295
|
+
cycle_start = path.index(current_name) if current_name in path else 0
|
|
296
|
+
cycle = tuple(path[cycle_start:] + [current_name])
|
|
297
|
+
raise HierarchyCycleError(cycle)
|
|
298
|
+
|
|
299
|
+
seen.add(current_name)
|
|
300
|
+
current = _resolve_required(resolver, current_name)
|
|
301
|
+
yield current
|
|
302
|
+
if path[-1] != current.name:
|
|
303
|
+
path.append(current.name)
|
|
304
|
+
current_name = current.super_name
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def iter_supertypes(
|
|
308
|
+
resolver: ClassResolver,
|
|
309
|
+
class_name: str,
|
|
310
|
+
*,
|
|
311
|
+
include_self: bool = False,
|
|
312
|
+
) -> Iterator[ResolvedClass]:
|
|
313
|
+
"""Yield all reachable supertypes of *class_name*.
|
|
314
|
+
|
|
315
|
+
Traversal is deterministic: the direct superclass chain is explored before
|
|
316
|
+
interface edges, and diamond/interface duplicates are yielded only once.
|
|
317
|
+
Malformed ancestry cycles still raise ``HierarchyCycleError``.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
resolver: Provider of class metadata.
|
|
321
|
+
class_name: JVM internal name of the starting class.
|
|
322
|
+
include_self: If ``True``, include the starting class itself.
|
|
323
|
+
|
|
324
|
+
Yields:
|
|
325
|
+
Each unique supertype in depth-first order, superclasses before interfaces.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
UnresolvedClassError: If a class in the graph cannot be resolved.
|
|
329
|
+
HierarchyCycleError: If a cycle is detected in the type graph.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
root = _resolve_required(resolver, class_name)
|
|
333
|
+
seen: set[str] = set()
|
|
334
|
+
|
|
335
|
+
def visit(name: str, stack: tuple[str, ...]) -> Iterator[ResolvedClass]:
|
|
336
|
+
if name in stack:
|
|
337
|
+
cycle = stack[stack.index(name) :] + (name,)
|
|
338
|
+
raise HierarchyCycleError(cycle)
|
|
339
|
+
if name in seen:
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
resolved = _resolve_required(resolver, name)
|
|
343
|
+
seen.add(name)
|
|
344
|
+
yield resolved
|
|
345
|
+
|
|
346
|
+
next_stack = stack + (name,)
|
|
347
|
+
if resolved.super_name is not None:
|
|
348
|
+
yield from visit(resolved.super_name, next_stack)
|
|
349
|
+
for interface_name in resolved.interfaces:
|
|
350
|
+
yield from visit(interface_name, next_stack)
|
|
351
|
+
|
|
352
|
+
if include_self:
|
|
353
|
+
yield from visit(root.name, ())
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
if root.super_name is not None:
|
|
357
|
+
yield from visit(root.super_name, (root.name,))
|
|
358
|
+
for interface_name in root.interfaces:
|
|
359
|
+
yield from visit(interface_name, (root.name,))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def is_subtype(resolver: ClassResolver, class_name: str, super_name: str) -> bool:
|
|
363
|
+
"""Return whether *class_name* is assignable to *super_name* by ancestry.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
resolver: Provider of class metadata.
|
|
367
|
+
class_name: JVM internal name of the candidate subtype.
|
|
368
|
+
super_name: JVM internal name of the candidate supertype.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
``True`` if *class_name* equals or extends/implements *super_name*.
|
|
372
|
+
|
|
373
|
+
Raises:
|
|
374
|
+
UnresolvedClassError: If any class in the hierarchy cannot be resolved.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
_resolve_required(resolver, super_name)
|
|
378
|
+
return any(resolved.name == super_name for resolved in iter_supertypes(resolver, class_name, include_self=True))
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def common_superclass(resolver: ClassResolver, left: str, right: str) -> str:
|
|
382
|
+
"""Return the nearest shared superclass for two internal class names.
|
|
383
|
+
|
|
384
|
+
Follows superclass edges only (cf. JVM spec §4.10.1.2 type merging).
|
|
385
|
+
Interface relationships collapse to ``java/lang/Object`` unless both
|
|
386
|
+
inputs share the same class name.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
resolver: Provider of class metadata.
|
|
390
|
+
left: JVM internal name of the first class.
|
|
391
|
+
right: JVM internal name of the second class.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Internal name of the nearest common superclass.
|
|
395
|
+
|
|
396
|
+
Raises:
|
|
397
|
+
UnresolvedClassError: If any class in either chain cannot be resolved.
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
left_chain = {resolved.name for resolved in iter_superclasses(resolver, left, include_self=True)}
|
|
401
|
+
for resolved in iter_superclasses(resolver, right, include_self=True):
|
|
402
|
+
if resolved.name in left_chain:
|
|
403
|
+
return resolved.name
|
|
404
|
+
return JAVA_LANG_OBJECT
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def find_overridden_methods(
|
|
408
|
+
resolver: ClassResolver,
|
|
409
|
+
class_name: str,
|
|
410
|
+
method: ResolvedMethod,
|
|
411
|
+
) -> tuple[InheritedMethod, ...]:
|
|
412
|
+
"""Return inherited declarations overridden by *method* in *class_name*.
|
|
413
|
+
|
|
414
|
+
The check is intentionally classfile-oriented (cf. JVM spec §5.4.5):
|
|
415
|
+
|
|
416
|
+
- Constructors and class initializers never override.
|
|
417
|
+
- Declaring methods that are ``private`` or ``static`` never override.
|
|
418
|
+
- Inherited declarations that are ``private``, ``static``, or ``final``
|
|
419
|
+
are excluded.
|
|
420
|
+
- Package-private declarations only match within the same runtime package.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
resolver: Provider of class metadata.
|
|
424
|
+
class_name: JVM internal name of the declaring class.
|
|
425
|
+
method: The method whose overridden ancestors to find.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Matching inherited method declarations, possibly empty.
|
|
429
|
+
|
|
430
|
+
Raises:
|
|
431
|
+
UnresolvedClassError: If any class in the hierarchy cannot be resolved.
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
_resolve_required(resolver, class_name)
|
|
435
|
+
if method.name in ("<init>", "<clinit>"):
|
|
436
|
+
return ()
|
|
437
|
+
if method.access_flags & (MethodAccessFlag.PRIVATE | MethodAccessFlag.STATIC):
|
|
438
|
+
return ()
|
|
439
|
+
|
|
440
|
+
matches: list[InheritedMethod] = []
|
|
441
|
+
for supertype in iter_supertypes(resolver, class_name):
|
|
442
|
+
inherited = supertype.find_method(method.name, method.descriptor)
|
|
443
|
+
if inherited is None:
|
|
444
|
+
continue
|
|
445
|
+
if not _can_override(class_name, supertype.name, inherited):
|
|
446
|
+
continue
|
|
447
|
+
matches.append(
|
|
448
|
+
InheritedMethod(
|
|
449
|
+
owner=supertype.name,
|
|
450
|
+
name=inherited.name,
|
|
451
|
+
descriptor=inherited.descriptor,
|
|
452
|
+
access_flags=inherited.access_flags,
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
return tuple(matches)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _resolve_required(resolver: ClassResolver, class_name: str) -> ResolvedClass:
|
|
459
|
+
"""Resolve *class_name* or raise ``UnresolvedClassError``.
|
|
460
|
+
|
|
461
|
+
Falls back to an implicit ``java/lang/Object`` stub when the resolver
|
|
462
|
+
does not provide it.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
resolver: Provider of class metadata.
|
|
466
|
+
class_name: JVM internal name to resolve.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
The resolved class metadata.
|
|
470
|
+
|
|
471
|
+
Raises:
|
|
472
|
+
UnresolvedClassError: If the class cannot be resolved and is not ``java/lang/Object``.
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
resolved = resolver.resolve_class(class_name)
|
|
476
|
+
if resolved is not None:
|
|
477
|
+
return resolved
|
|
478
|
+
if class_name == JAVA_LANG_OBJECT:
|
|
479
|
+
return _IMPLICIT_OBJECT
|
|
480
|
+
raise UnresolvedClassError(class_name)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _resolve_class_name(cp: ConstantPoolBuilder, index: int) -> str:
|
|
484
|
+
"""Resolve a ``CONSTANT_Class`` index to its internal name.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
cp: Constant pool to read from.
|
|
488
|
+
index: Constant pool index pointing to a ``CONSTANT_Class_info`` entry (JVM spec §4.4.1).
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
The UTF-8 internal name referenced by the class-info entry.
|
|
492
|
+
|
|
493
|
+
Raises:
|
|
494
|
+
ValueError: If the entry at *index* is not a ``ClassInfo``.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
entry = cp.get(index)
|
|
498
|
+
if not isinstance(entry, ClassInfo):
|
|
499
|
+
raise ValueError(f"CP index {index} is not a CONSTANT_Class: {type(entry).__name__}")
|
|
500
|
+
return cp.resolve_utf8(entry.name_index)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _package_name(class_name: str) -> str:
|
|
504
|
+
"""Return the internal package prefix for *class_name*.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
class_name: JVM internal name (e.g. ``java/lang/String``).
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
The package portion (e.g. ``java/lang``), or an empty string for the default package.
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
package, _, _ = class_name.rpartition("/")
|
|
514
|
+
return package
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _can_override(
|
|
518
|
+
declaring_owner: str,
|
|
519
|
+
inherited_owner: str,
|
|
520
|
+
inherited_method: ResolvedMethod,
|
|
521
|
+
) -> bool:
|
|
522
|
+
"""Return whether *inherited_method* is overridable from *declaring_owner*.
|
|
523
|
+
|
|
524
|
+
Applies JVM override rules (JVM spec §5.4.5): constructors, ``private``,
|
|
525
|
+
``static``, and ``final`` methods cannot be overridden, and package-private
|
|
526
|
+
access requires the same runtime package.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
declaring_owner: Internal name of the overriding class.
|
|
530
|
+
inherited_owner: Internal name of the class declaring the inherited method.
|
|
531
|
+
inherited_method: The candidate inherited method declaration.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
``True`` if the inherited method can be overridden from the declaring class.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
if inherited_method.name in ("<init>", "<clinit>"):
|
|
538
|
+
return False
|
|
539
|
+
if inherited_method.access_flags & _NON_OVERRIDABLE_METHOD_FLAGS:
|
|
540
|
+
return False
|
|
541
|
+
if inherited_method.access_flags & (MethodAccessFlag.PUBLIC | MethodAccessFlag.PROTECTED):
|
|
542
|
+
return True
|
|
543
|
+
return _package_name(declaring_owner) == _package_name(inherited_owner)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
__all__ = [
|
|
547
|
+
"ClassResolver",
|
|
548
|
+
"HierarchyCycleError",
|
|
549
|
+
"HierarchyError",
|
|
550
|
+
"InheritedMethod",
|
|
551
|
+
"JAVA_LANG_OBJECT",
|
|
552
|
+
"MappingClassResolver",
|
|
553
|
+
"ResolvedClass",
|
|
554
|
+
"ResolvedMethod",
|
|
555
|
+
"UnresolvedClassError",
|
|
556
|
+
"common_superclass",
|
|
557
|
+
"find_overridden_methods",
|
|
558
|
+
"is_subtype",
|
|
559
|
+
"iter_superclasses",
|
|
560
|
+
"iter_supertypes",
|
|
561
|
+
]
|
pytecode/info.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Data structures for parsed JVM class file components.
|
|
2
|
+
|
|
3
|
+
Provides dataclass representations of the top-level structures defined in the
|
|
4
|
+
JVM specification: the ``ClassFile`` structure (§4.1), ``field_info`` (§4.5),
|
|
5
|
+
and ``method_info`` (§4.6).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from .attributes import AttributeInfo
|
|
13
|
+
from .constant_pool import ConstantPoolInfo
|
|
14
|
+
from .constants import ClassAccessFlag, FieldAccessFlag, MethodAccessFlag
|
|
15
|
+
|
|
16
|
+
__all__ = ["ClassFile", "FieldInfo", "MethodInfo"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class FieldInfo:
|
|
21
|
+
"""Parsed ``field_info`` structure (JVM spec §4.5).
|
|
22
|
+
|
|
23
|
+
Represents a single field declared in a class or interface.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
access_flags: Mask of ``FieldAccessFlag`` values denoting access
|
|
27
|
+
permissions and properties of the field.
|
|
28
|
+
name_index: Index into the constant pool for the field's simple name.
|
|
29
|
+
descriptor_index: Index into the constant pool for the field's
|
|
30
|
+
descriptor string.
|
|
31
|
+
attributes_count: Number of additional attributes for this field.
|
|
32
|
+
attributes: Variable-length list of ``AttributeInfo`` structures
|
|
33
|
+
giving additional information about the field.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
access_flags: FieldAccessFlag
|
|
37
|
+
name_index: int
|
|
38
|
+
descriptor_index: int
|
|
39
|
+
attributes_count: int
|
|
40
|
+
attributes: list[AttributeInfo]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class MethodInfo:
|
|
45
|
+
"""Parsed ``method_info`` structure (JVM spec §4.6).
|
|
46
|
+
|
|
47
|
+
Represents a single method declared in a class or interface, including
|
|
48
|
+
instance methods, class methods, instance initialisation methods, and
|
|
49
|
+
the class/interface initialisation method.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
access_flags: Mask of ``MethodAccessFlag`` values denoting access
|
|
53
|
+
permissions and properties of the method.
|
|
54
|
+
name_index: Index into the constant pool for the method's simple name.
|
|
55
|
+
descriptor_index: Index into the constant pool for the method's
|
|
56
|
+
descriptor string.
|
|
57
|
+
attributes_count: Number of additional attributes for this method.
|
|
58
|
+
attributes: Variable-length list of ``AttributeInfo`` structures
|
|
59
|
+
giving additional information about the method (e.g. bytecode).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
access_flags: MethodAccessFlag
|
|
63
|
+
name_index: int
|
|
64
|
+
descriptor_index: int
|
|
65
|
+
attributes_count: int
|
|
66
|
+
attributes: list[AttributeInfo]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class ClassFile:
|
|
71
|
+
"""Parsed ``ClassFile`` structure (JVM spec §4.1).
|
|
72
|
+
|
|
73
|
+
Top-level representation of a ``.class`` file produced by the parser.
|
|
74
|
+
Every field corresponds directly to an item in the ``ClassFile`` table
|
|
75
|
+
defined by the specification.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
magic: The magic number identifying the class file format
|
|
79
|
+
(``0xCAFEBABE``).
|
|
80
|
+
minor_version: Minor version number of the class file.
|
|
81
|
+
major_version: Major version number of the class file.
|
|
82
|
+
constant_pool_count: Number of entries in the constant pool table
|
|
83
|
+
plus one.
|
|
84
|
+
constant_pool: Table of ``ConstantPoolInfo`` entries (indexed from 1).
|
|
85
|
+
Entries may be ``None`` for the unused slots that follow
|
|
86
|
+
``CONSTANT_Long`` and ``CONSTANT_Double`` entries.
|
|
87
|
+
access_flags: Mask of ``ClassAccessFlag`` values denoting access
|
|
88
|
+
permissions and properties of this class or interface.
|
|
89
|
+
this_class: Constant pool index of a ``CONSTANT_Class_info`` entry
|
|
90
|
+
representing the class defined by this file.
|
|
91
|
+
super_class: Constant pool index of a ``CONSTANT_Class_info`` entry
|
|
92
|
+
representing the direct superclass, or ``0`` for
|
|
93
|
+
``java.lang.Object``.
|
|
94
|
+
interfaces_count: Number of direct superinterfaces.
|
|
95
|
+
interfaces: List of constant pool indices, each referencing a
|
|
96
|
+
``CONSTANT_Class_info`` entry for a direct superinterface.
|
|
97
|
+
fields_count: Number of ``FieldInfo`` structures in *fields*.
|
|
98
|
+
fields: List of ``FieldInfo`` structures representing all fields
|
|
99
|
+
declared by this class or interface.
|
|
100
|
+
methods_count: Number of ``MethodInfo`` structures in *methods*.
|
|
101
|
+
methods: List of ``MethodInfo`` structures representing all methods
|
|
102
|
+
declared by this class or interface.
|
|
103
|
+
attributes_count: Number of attributes in the *attributes* table.
|
|
104
|
+
attributes: List of ``AttributeInfo`` structures giving additional
|
|
105
|
+
class file attributes.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
magic: int
|
|
109
|
+
minor_version: int
|
|
110
|
+
major_version: int
|
|
111
|
+
constant_pool_count: int
|
|
112
|
+
constant_pool: list[ConstantPoolInfo | None]
|
|
113
|
+
access_flags: ClassAccessFlag
|
|
114
|
+
this_class: int
|
|
115
|
+
super_class: int
|
|
116
|
+
interfaces_count: int
|
|
117
|
+
interfaces: list[int]
|
|
118
|
+
fields_count: int
|
|
119
|
+
fields: list[FieldInfo]
|
|
120
|
+
methods_count: int
|
|
121
|
+
methods: list[MethodInfo]
|
|
122
|
+
attributes_count: int
|
|
123
|
+
attributes: list[AttributeInfo]
|