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/transforms.py
ADDED
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
"""Composable transform pipeline and matcher DSL for JVM class-file models.
|
|
2
|
+
|
|
3
|
+
Provides a declarative API for selecting and transforming classes, fields,
|
|
4
|
+
methods, and code within JVM ``.class`` files. Core building blocks are
|
|
5
|
+
:class:`Matcher` (a composable predicate wrapper), the four transform
|
|
6
|
+
protocols (:class:`ClassTransform`, :class:`FieldTransform`,
|
|
7
|
+
:class:`MethodTransform`, :class:`CodeTransform`), and :class:`Pipeline`
|
|
8
|
+
which sequences transforms into a single pass.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Callable, Iterable
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import IntFlag
|
|
17
|
+
from typing import Protocol, cast
|
|
18
|
+
|
|
19
|
+
from .constants import ClassAccessFlag, FieldAccessFlag, MethodAccessFlag
|
|
20
|
+
from .descriptors import VoidType, parse_method_descriptor, to_descriptor
|
|
21
|
+
from .model import ClassModel, CodeModel, FieldModel, MethodModel
|
|
22
|
+
|
|
23
|
+
type Predicate[T] = Callable[[T], bool]
|
|
24
|
+
type ClassPredicate = Predicate[ClassModel]
|
|
25
|
+
type FieldPredicate = Predicate[FieldModel]
|
|
26
|
+
type MethodPredicate = Predicate[MethodModel]
|
|
27
|
+
|
|
28
|
+
_FIELD_VISIBILITY_FLAGS = FieldAccessFlag.PUBLIC | FieldAccessFlag.PRIVATE | FieldAccessFlag.PROTECTED
|
|
29
|
+
_METHOD_VISIBILITY_FLAGS = MethodAccessFlag.PUBLIC | MethodAccessFlag.PRIVATE | MethodAccessFlag.PROTECTED
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class Matcher[T]:
|
|
34
|
+
"""Composable predicate wrapper used by the transform-selection DSL."""
|
|
35
|
+
|
|
36
|
+
_predicate: Predicate[T]
|
|
37
|
+
_description: str
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def of[U](
|
|
41
|
+
predicate: Predicate[U] | Matcher[U],
|
|
42
|
+
description: str | None = None,
|
|
43
|
+
) -> Matcher[U]:
|
|
44
|
+
"""Wrap a predicate as a ``Matcher``.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
predicate: Callable or existing matcher to wrap.
|
|
48
|
+
description: Human-readable label shown in ``repr`` output.
|
|
49
|
+
"""
|
|
50
|
+
return _ensure_matcher(predicate, description)
|
|
51
|
+
|
|
52
|
+
def __call__(self, value: T, /) -> bool:
|
|
53
|
+
"""Evaluate this matcher against *value*."""
|
|
54
|
+
return self._predicate(value)
|
|
55
|
+
|
|
56
|
+
def __and__(self, other: Predicate[T] | Matcher[T], /) -> Matcher[T]:
|
|
57
|
+
"""Combine with *other* using logical AND."""
|
|
58
|
+
rhs = _ensure_matcher(other)
|
|
59
|
+
return Matcher(
|
|
60
|
+
lambda value: self(value) and rhs(value),
|
|
61
|
+
_join_descriptions("&", self._description, rhs._description),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def __rand__(self, other: Predicate[T] | Matcher[T], /) -> Matcher[T]:
|
|
65
|
+
"""Support ``predicate & matcher`` with a plain callable on the left."""
|
|
66
|
+
lhs = _ensure_matcher(other)
|
|
67
|
+
return lhs & self
|
|
68
|
+
|
|
69
|
+
def __or__(self, other: Predicate[T] | Matcher[T], /) -> Matcher[T]:
|
|
70
|
+
"""Combine with *other* using logical OR."""
|
|
71
|
+
rhs = _ensure_matcher(other)
|
|
72
|
+
return Matcher(
|
|
73
|
+
lambda value: self(value) or rhs(value),
|
|
74
|
+
_join_descriptions("|", self._description, rhs._description),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def __ror__(self, other: Predicate[T] | Matcher[T], /) -> Matcher[T]:
|
|
78
|
+
"""Support ``predicate | matcher`` with a plain callable on the left."""
|
|
79
|
+
lhs = _ensure_matcher(other)
|
|
80
|
+
return lhs | self
|
|
81
|
+
|
|
82
|
+
def __invert__(self) -> Matcher[T]:
|
|
83
|
+
"""Return the logical negation of this matcher."""
|
|
84
|
+
return Matcher(
|
|
85
|
+
lambda value: not self(value),
|
|
86
|
+
f"~{_parenthesize_description(self._description)}",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def __repr__(self) -> str:
|
|
90
|
+
"""Return a human-readable description of this matcher."""
|
|
91
|
+
return f"Matcher[{self._description}]"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
type ClassMatcher = Matcher[ClassModel]
|
|
95
|
+
type FieldMatcher = Matcher[FieldModel]
|
|
96
|
+
type MethodMatcher = Matcher[MethodModel]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ClassTransform(Protocol):
|
|
100
|
+
"""In-place transform applied to a ``ClassModel``."""
|
|
101
|
+
|
|
102
|
+
def __call__(self, model: ClassModel, /) -> None:
|
|
103
|
+
"""Apply this transform to a class model."""
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class FieldTransform(Protocol):
|
|
108
|
+
"""In-place transform applied to a ``FieldModel``."""
|
|
109
|
+
|
|
110
|
+
def __call__(self, field: FieldModel, owner: ClassModel, /) -> None:
|
|
111
|
+
"""Apply this transform to a field.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
field: The field to transform in place.
|
|
115
|
+
owner: The class that declares the field.
|
|
116
|
+
"""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class MethodTransform(Protocol):
|
|
121
|
+
"""In-place transform applied to a ``MethodModel``."""
|
|
122
|
+
|
|
123
|
+
def __call__(self, method: MethodModel, owner: ClassModel, /) -> None:
|
|
124
|
+
"""Apply this transform to a method.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
method: The method to transform in place.
|
|
128
|
+
owner: The class that declares the method.
|
|
129
|
+
"""
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class CodeTransform(Protocol):
|
|
134
|
+
"""In-place transform applied to a ``CodeModel``."""
|
|
135
|
+
|
|
136
|
+
def __call__(self, code: CodeModel, method: MethodModel, owner: ClassModel, /) -> None:
|
|
137
|
+
"""Apply this transform to a code attribute.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
code: The code model to transform in place.
|
|
141
|
+
method: The method that owns the code attribute.
|
|
142
|
+
owner: The class that declares the method.
|
|
143
|
+
"""
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True)
|
|
148
|
+
class Pipeline:
|
|
149
|
+
"""Composable sequence of class-level transforms applied in order.
|
|
150
|
+
|
|
151
|
+
Pipelines are themselves callable and can be passed anywhere a
|
|
152
|
+
``ClassTransform`` is accepted, including ``JarFile.rewrite()``.
|
|
153
|
+
|
|
154
|
+
Attributes:
|
|
155
|
+
transforms: The ordered tuple of class transforms in this pipeline.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
transforms: tuple[ClassTransform, ...] = ()
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def of(cls, *transforms: ClassTransform | Pipeline) -> Pipeline:
|
|
162
|
+
"""Build a pipeline from class transforms and/or nested pipelines."""
|
|
163
|
+
|
|
164
|
+
return cls(_flatten_transforms(transforms))
|
|
165
|
+
|
|
166
|
+
def then(self, *transforms: ClassTransform | Pipeline) -> Pipeline:
|
|
167
|
+
"""Return a new pipeline with *transforms* appended."""
|
|
168
|
+
|
|
169
|
+
return Pipeline((*self.transforms, *_flatten_transforms(transforms)))
|
|
170
|
+
|
|
171
|
+
def __call__(self, model: ClassModel, /) -> None:
|
|
172
|
+
"""Apply all transforms in sequence to a class model."""
|
|
173
|
+
for transform in self.transforms:
|
|
174
|
+
_expect_none(
|
|
175
|
+
transform(model),
|
|
176
|
+
"Class transforms must mutate ClassModel in place and return None",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def pipeline(*transforms: ClassTransform | Pipeline) -> Pipeline:
|
|
181
|
+
"""Return a callable ``Pipeline`` from class transforms and/or pipelines."""
|
|
182
|
+
|
|
183
|
+
return Pipeline.of(*transforms)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def on_classes(
|
|
187
|
+
transform: ClassTransform,
|
|
188
|
+
*,
|
|
189
|
+
where: ClassPredicate | None = None,
|
|
190
|
+
) -> ClassTransform:
|
|
191
|
+
"""Conditionally apply a class transform.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
transform: The class-level transform to apply.
|
|
195
|
+
where: Optional predicate that must match for the transform to run.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def lifted(model: ClassModel) -> None:
|
|
199
|
+
if where is not None and not where(model):
|
|
200
|
+
return
|
|
201
|
+
_expect_none(
|
|
202
|
+
transform(model),
|
|
203
|
+
"Class transforms must mutate ClassModel in place and return None",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return lifted
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def on_fields(
|
|
210
|
+
transform: FieldTransform,
|
|
211
|
+
*,
|
|
212
|
+
where: FieldPredicate | None = None,
|
|
213
|
+
owner: ClassPredicate | None = None,
|
|
214
|
+
) -> ClassTransform:
|
|
215
|
+
"""Lift a field transform into a class transform.
|
|
216
|
+
|
|
217
|
+
Iterates over a snapshot of ``ClassModel.fields`` so that collection
|
|
218
|
+
edits do not affect which fields are visited within the current pass.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
transform: The field-level transform to apply.
|
|
222
|
+
where: Optional predicate selecting which fields are visited.
|
|
223
|
+
owner: Optional predicate gating traversal at the class level.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def lifted(model: ClassModel) -> None:
|
|
227
|
+
if owner is not None and not owner(model):
|
|
228
|
+
return
|
|
229
|
+
for field in tuple(model.fields):
|
|
230
|
+
if where is not None and not where(field):
|
|
231
|
+
continue
|
|
232
|
+
_expect_none(
|
|
233
|
+
transform(field, model),
|
|
234
|
+
"Field transforms must mutate FieldModel in place and return None",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return lifted
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def on_methods(
|
|
241
|
+
transform: MethodTransform,
|
|
242
|
+
*,
|
|
243
|
+
where: MethodPredicate | None = None,
|
|
244
|
+
owner: ClassPredicate | None = None,
|
|
245
|
+
) -> ClassTransform:
|
|
246
|
+
"""Lift a method transform into a class transform.
|
|
247
|
+
|
|
248
|
+
Iterates over a snapshot of ``ClassModel.methods`` so that collection
|
|
249
|
+
edits do not affect which methods are visited within the current pass.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
transform: The method-level transform to apply.
|
|
253
|
+
where: Optional predicate selecting which methods are visited.
|
|
254
|
+
owner: Optional predicate gating traversal at the class level.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
def lifted(model: ClassModel) -> None:
|
|
258
|
+
if owner is not None and not owner(model):
|
|
259
|
+
return
|
|
260
|
+
for method in tuple(model.methods):
|
|
261
|
+
if where is not None and not where(method):
|
|
262
|
+
continue
|
|
263
|
+
_expect_none(
|
|
264
|
+
transform(method, model),
|
|
265
|
+
"Method transforms must mutate MethodModel in place and return None",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return lifted
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def on_code(
|
|
272
|
+
transform: CodeTransform,
|
|
273
|
+
*,
|
|
274
|
+
where: MethodPredicate | None = None,
|
|
275
|
+
owner: ClassPredicate | None = None,
|
|
276
|
+
) -> ClassTransform:
|
|
277
|
+
"""Lift a code transform into a class transform.
|
|
278
|
+
|
|
279
|
+
Only methods that currently have a ``CodeModel`` are visited.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
transform: The code-level transform to apply.
|
|
283
|
+
where: Optional predicate selecting which methods' code is visited.
|
|
284
|
+
owner: Optional predicate gating traversal at the class level.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
def lifted(model: ClassModel) -> None:
|
|
288
|
+
if owner is not None and not owner(model):
|
|
289
|
+
return
|
|
290
|
+
for method in tuple(model.methods):
|
|
291
|
+
if where is not None and not where(method):
|
|
292
|
+
continue
|
|
293
|
+
code = method.code
|
|
294
|
+
if code is None:
|
|
295
|
+
continue
|
|
296
|
+
_expect_none(
|
|
297
|
+
transform(code, method, model),
|
|
298
|
+
"Code transforms must mutate CodeModel in place and return None",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return lifted
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def all_of[T](*predicates: Predicate[T] | Matcher[T]) -> Matcher[T]:
|
|
305
|
+
"""Return a matcher that requires every predicate to match."""
|
|
306
|
+
|
|
307
|
+
matchers = tuple(_ensure_matcher(predicate) for predicate in predicates)
|
|
308
|
+
if not matchers:
|
|
309
|
+
return Matcher(lambda _value: True, "all_of()")
|
|
310
|
+
|
|
311
|
+
def combined(value: T) -> bool:
|
|
312
|
+
return all(matcher(value) for matcher in matchers)
|
|
313
|
+
|
|
314
|
+
return Matcher(combined, _combine_variadic_descriptions("&", matchers))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def any_of[T](*predicates: Predicate[T] | Matcher[T]) -> Matcher[T]:
|
|
318
|
+
"""Return a matcher that matches when any predicate matches."""
|
|
319
|
+
|
|
320
|
+
matchers = tuple(_ensure_matcher(predicate) for predicate in predicates)
|
|
321
|
+
if not matchers:
|
|
322
|
+
return Matcher(lambda _value: False, "any_of()")
|
|
323
|
+
|
|
324
|
+
def combined(value: T) -> bool:
|
|
325
|
+
return any(matcher(value) for matcher in matchers)
|
|
326
|
+
|
|
327
|
+
return Matcher(combined, _combine_variadic_descriptions("|", matchers))
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def not_[T](predicate: Predicate[T] | Matcher[T]) -> Matcher[T]:
|
|
331
|
+
"""Return the negation of *predicate*."""
|
|
332
|
+
|
|
333
|
+
return ~_ensure_matcher(predicate)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def class_named(name: str) -> ClassMatcher:
|
|
337
|
+
"""Match classes by internal name."""
|
|
338
|
+
|
|
339
|
+
return _equals_matcher(lambda model: model.name, name, f"class_named({name!r})")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def class_name_matches(pattern: str) -> ClassMatcher:
|
|
343
|
+
"""Match classes by internal-name regex."""
|
|
344
|
+
|
|
345
|
+
return _regex_matcher(
|
|
346
|
+
lambda model: model.name,
|
|
347
|
+
pattern,
|
|
348
|
+
f"class_name_matches({pattern!r})",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def class_access(flags: ClassAccessFlag) -> ClassMatcher:
|
|
353
|
+
"""Match classes containing all requested access flags."""
|
|
354
|
+
|
|
355
|
+
return _all_flags_matcher(
|
|
356
|
+
lambda model: model.access_flags,
|
|
357
|
+
flags,
|
|
358
|
+
f"class_access({flags!r})",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def class_access_any(flags: ClassAccessFlag) -> ClassMatcher:
|
|
363
|
+
"""Match classes containing any requested access flag."""
|
|
364
|
+
|
|
365
|
+
return _any_flags_matcher(
|
|
366
|
+
lambda model: model.access_flags,
|
|
367
|
+
flags,
|
|
368
|
+
f"class_access_any({flags!r})",
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def class_is_public() -> ClassMatcher:
|
|
373
|
+
"""Match public classes."""
|
|
374
|
+
|
|
375
|
+
return Matcher(
|
|
376
|
+
lambda model: ClassAccessFlag.PUBLIC in model.access_flags,
|
|
377
|
+
"class_is_public()",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def class_is_package_private() -> ClassMatcher:
|
|
382
|
+
"""Match package-private classes."""
|
|
383
|
+
|
|
384
|
+
return Matcher(
|
|
385
|
+
lambda model: ClassAccessFlag.PUBLIC not in model.access_flags,
|
|
386
|
+
"class_is_package_private()",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def class_is_final() -> ClassMatcher:
|
|
391
|
+
"""Match final classes."""
|
|
392
|
+
|
|
393
|
+
return _ensure_matcher(class_access(ClassAccessFlag.FINAL), "class_is_final()")
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def class_is_interface() -> ClassMatcher:
|
|
397
|
+
"""Match interface classes."""
|
|
398
|
+
|
|
399
|
+
return _ensure_matcher(class_access(ClassAccessFlag.INTERFACE), "class_is_interface()")
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def class_is_abstract() -> ClassMatcher:
|
|
403
|
+
"""Match abstract classes."""
|
|
404
|
+
|
|
405
|
+
return _ensure_matcher(class_access(ClassAccessFlag.ABSTRACT), "class_is_abstract()")
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def class_is_synthetic() -> ClassMatcher:
|
|
409
|
+
"""Match synthetic classes."""
|
|
410
|
+
|
|
411
|
+
return _ensure_matcher(class_access(ClassAccessFlag.SYNTHETIC), "class_is_synthetic()")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def class_is_annotation() -> ClassMatcher:
|
|
415
|
+
"""Match annotation classes."""
|
|
416
|
+
|
|
417
|
+
return _ensure_matcher(class_access(ClassAccessFlag.ANNOTATION), "class_is_annotation()")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def class_is_enum() -> ClassMatcher:
|
|
421
|
+
"""Match enum classes."""
|
|
422
|
+
|
|
423
|
+
return _ensure_matcher(class_access(ClassAccessFlag.ENUM), "class_is_enum()")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def class_is_module() -> ClassMatcher:
|
|
427
|
+
"""Match module-info classes."""
|
|
428
|
+
|
|
429
|
+
return _ensure_matcher(class_access(ClassAccessFlag.MODULE), "class_is_module()")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def extends(name: str) -> ClassMatcher:
|
|
433
|
+
"""Match classes with the given direct superclass."""
|
|
434
|
+
|
|
435
|
+
return _equals_matcher(lambda model: model.super_name, name, f"extends({name!r})")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def implements(name: str) -> ClassMatcher:
|
|
439
|
+
"""Match classes declaring the given direct interface."""
|
|
440
|
+
|
|
441
|
+
return _contains_matcher(
|
|
442
|
+
lambda model: model.interfaces,
|
|
443
|
+
name,
|
|
444
|
+
f"implements({name!r})",
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def class_version(major: int) -> ClassMatcher:
|
|
449
|
+
"""Match classes with the given major version."""
|
|
450
|
+
|
|
451
|
+
return _equals_matcher(lambda model: model.version[0], major, f"class_version({major})")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def class_version_at_least(major: int) -> ClassMatcher:
|
|
455
|
+
"""Match classes whose major version is at least *major*."""
|
|
456
|
+
|
|
457
|
+
return Matcher(
|
|
458
|
+
lambda model: model.version[0] >= major,
|
|
459
|
+
f"class_version_at_least({major})",
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def class_version_below(major: int) -> ClassMatcher:
|
|
464
|
+
"""Match classes whose major version is below *major*."""
|
|
465
|
+
|
|
466
|
+
return Matcher(
|
|
467
|
+
lambda model: model.version[0] < major,
|
|
468
|
+
f"class_version_below({major})",
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def field_named(name: str) -> FieldMatcher:
|
|
473
|
+
"""Match fields by name."""
|
|
474
|
+
|
|
475
|
+
return _equals_matcher(lambda field: field.name, name, f"field_named({name!r})")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def field_name_matches(pattern: str) -> FieldMatcher:
|
|
479
|
+
"""Match fields by name regex."""
|
|
480
|
+
|
|
481
|
+
return _regex_matcher(
|
|
482
|
+
lambda field: field.name,
|
|
483
|
+
pattern,
|
|
484
|
+
f"field_name_matches({pattern!r})",
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def field_descriptor(descriptor: str) -> FieldMatcher:
|
|
489
|
+
"""Match fields by descriptor."""
|
|
490
|
+
|
|
491
|
+
return _equals_matcher(
|
|
492
|
+
lambda field: field.descriptor,
|
|
493
|
+
descriptor,
|
|
494
|
+
f"field_descriptor({descriptor!r})",
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def field_descriptor_matches(pattern: str) -> FieldMatcher:
|
|
499
|
+
"""Match fields by descriptor regex."""
|
|
500
|
+
|
|
501
|
+
return _regex_matcher(
|
|
502
|
+
lambda field: field.descriptor,
|
|
503
|
+
pattern,
|
|
504
|
+
f"field_descriptor_matches({pattern!r})",
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def field_access(flags: FieldAccessFlag) -> FieldMatcher:
|
|
509
|
+
"""Match fields containing all requested access flags."""
|
|
510
|
+
|
|
511
|
+
return _all_flags_matcher(
|
|
512
|
+
lambda field: field.access_flags,
|
|
513
|
+
flags,
|
|
514
|
+
f"field_access({flags!r})",
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def field_access_any(flags: FieldAccessFlag) -> FieldMatcher:
|
|
519
|
+
"""Match fields containing any requested access flag."""
|
|
520
|
+
|
|
521
|
+
return _any_flags_matcher(
|
|
522
|
+
lambda field: field.access_flags,
|
|
523
|
+
flags,
|
|
524
|
+
f"field_access_any({flags!r})",
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def field_is_public() -> FieldMatcher:
|
|
529
|
+
"""Match public fields."""
|
|
530
|
+
|
|
531
|
+
return _ensure_matcher(field_access(FieldAccessFlag.PUBLIC), "field_is_public()")
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def field_is_private() -> FieldMatcher:
|
|
535
|
+
"""Match private fields."""
|
|
536
|
+
|
|
537
|
+
return _ensure_matcher(field_access(FieldAccessFlag.PRIVATE), "field_is_private()")
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def field_is_protected() -> FieldMatcher:
|
|
541
|
+
"""Match protected fields."""
|
|
542
|
+
|
|
543
|
+
return _ensure_matcher(field_access(FieldAccessFlag.PROTECTED), "field_is_protected()")
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def field_is_package_private() -> FieldMatcher:
|
|
547
|
+
"""Match package-private fields."""
|
|
548
|
+
|
|
549
|
+
return Matcher(
|
|
550
|
+
lambda field: not (field.access_flags & _FIELD_VISIBILITY_FLAGS),
|
|
551
|
+
"field_is_package_private()",
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def field_is_static() -> FieldMatcher:
|
|
556
|
+
"""Match static fields."""
|
|
557
|
+
|
|
558
|
+
return _ensure_matcher(field_access(FieldAccessFlag.STATIC), "field_is_static()")
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def field_is_final() -> FieldMatcher:
|
|
562
|
+
"""Match final fields."""
|
|
563
|
+
|
|
564
|
+
return _ensure_matcher(field_access(FieldAccessFlag.FINAL), "field_is_final()")
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def field_is_volatile() -> FieldMatcher:
|
|
568
|
+
"""Match volatile fields."""
|
|
569
|
+
|
|
570
|
+
return _ensure_matcher(field_access(FieldAccessFlag.VOLATILE), "field_is_volatile()")
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def field_is_transient() -> FieldMatcher:
|
|
574
|
+
"""Match transient fields."""
|
|
575
|
+
|
|
576
|
+
return _ensure_matcher(field_access(FieldAccessFlag.TRANSIENT), "field_is_transient()")
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def field_is_synthetic() -> FieldMatcher:
|
|
580
|
+
"""Match synthetic fields."""
|
|
581
|
+
|
|
582
|
+
return _ensure_matcher(field_access(FieldAccessFlag.SYNTHETIC), "field_is_synthetic()")
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def field_is_enum_constant() -> FieldMatcher:
|
|
586
|
+
"""Match enum-constant fields."""
|
|
587
|
+
|
|
588
|
+
return _ensure_matcher(field_access(FieldAccessFlag.ENUM), "field_is_enum_constant()")
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def method_named(name: str) -> MethodMatcher:
|
|
592
|
+
"""Match methods by name."""
|
|
593
|
+
|
|
594
|
+
return _equals_matcher(lambda method: method.name, name, f"method_named({name!r})")
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def method_name_matches(pattern: str) -> MethodMatcher:
|
|
598
|
+
"""Match methods by name regex."""
|
|
599
|
+
|
|
600
|
+
return _regex_matcher(
|
|
601
|
+
lambda method: method.name,
|
|
602
|
+
pattern,
|
|
603
|
+
f"method_name_matches({pattern!r})",
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def method_descriptor(descriptor: str) -> MethodMatcher:
|
|
608
|
+
"""Match methods by descriptor."""
|
|
609
|
+
|
|
610
|
+
return _equals_matcher(
|
|
611
|
+
lambda method: method.descriptor,
|
|
612
|
+
descriptor,
|
|
613
|
+
f"method_descriptor({descriptor!r})",
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def method_descriptor_matches(pattern: str) -> MethodMatcher:
|
|
618
|
+
"""Match methods by descriptor regex."""
|
|
619
|
+
|
|
620
|
+
return _regex_matcher(
|
|
621
|
+
lambda method: method.descriptor,
|
|
622
|
+
pattern,
|
|
623
|
+
f"method_descriptor_matches({pattern!r})",
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def method_access(flags: MethodAccessFlag) -> MethodMatcher:
|
|
628
|
+
"""Match methods containing all requested access flags."""
|
|
629
|
+
|
|
630
|
+
return _all_flags_matcher(
|
|
631
|
+
lambda method: method.access_flags,
|
|
632
|
+
flags,
|
|
633
|
+
f"method_access({flags!r})",
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def method_access_any(flags: MethodAccessFlag) -> MethodMatcher:
|
|
638
|
+
"""Match methods containing any requested access flag."""
|
|
639
|
+
|
|
640
|
+
return _any_flags_matcher(
|
|
641
|
+
lambda method: method.access_flags,
|
|
642
|
+
flags,
|
|
643
|
+
f"method_access_any({flags!r})",
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def method_is_public() -> MethodMatcher:
|
|
648
|
+
"""Match public methods."""
|
|
649
|
+
|
|
650
|
+
return _ensure_matcher(method_access(MethodAccessFlag.PUBLIC), "method_is_public()")
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def method_is_private() -> MethodMatcher:
|
|
654
|
+
"""Match private methods."""
|
|
655
|
+
|
|
656
|
+
return _ensure_matcher(method_access(MethodAccessFlag.PRIVATE), "method_is_private()")
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def method_is_protected() -> MethodMatcher:
|
|
660
|
+
"""Match protected methods."""
|
|
661
|
+
|
|
662
|
+
return _ensure_matcher(method_access(MethodAccessFlag.PROTECTED), "method_is_protected()")
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def method_is_package_private() -> MethodMatcher:
|
|
666
|
+
"""Match package-private methods."""
|
|
667
|
+
|
|
668
|
+
return Matcher(
|
|
669
|
+
lambda method: not (method.access_flags & _METHOD_VISIBILITY_FLAGS),
|
|
670
|
+
"method_is_package_private()",
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def method_is_static() -> MethodMatcher:
|
|
675
|
+
"""Match static methods."""
|
|
676
|
+
|
|
677
|
+
return _ensure_matcher(method_access(MethodAccessFlag.STATIC), "method_is_static()")
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def method_is_final() -> MethodMatcher:
|
|
681
|
+
"""Match final methods."""
|
|
682
|
+
|
|
683
|
+
return _ensure_matcher(method_access(MethodAccessFlag.FINAL), "method_is_final()")
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def method_is_synchronized() -> MethodMatcher:
|
|
687
|
+
"""Match synchronized methods."""
|
|
688
|
+
|
|
689
|
+
return _ensure_matcher(
|
|
690
|
+
method_access(MethodAccessFlag.SYNCHRONIZED),
|
|
691
|
+
"method_is_synchronized()",
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def method_is_bridge() -> MethodMatcher:
|
|
696
|
+
"""Match bridge methods."""
|
|
697
|
+
|
|
698
|
+
return _ensure_matcher(method_access(MethodAccessFlag.BRIDGE), "method_is_bridge()")
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def method_is_varargs() -> MethodMatcher:
|
|
702
|
+
"""Match varargs methods."""
|
|
703
|
+
|
|
704
|
+
return _ensure_matcher(method_access(MethodAccessFlag.VARARGS), "method_is_varargs()")
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def method_is_native() -> MethodMatcher:
|
|
708
|
+
"""Match native methods."""
|
|
709
|
+
|
|
710
|
+
return _ensure_matcher(method_access(MethodAccessFlag.NATIVE), "method_is_native()")
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def method_is_abstract() -> MethodMatcher:
|
|
714
|
+
"""Match abstract methods."""
|
|
715
|
+
|
|
716
|
+
return _ensure_matcher(method_access(MethodAccessFlag.ABSTRACT), "method_is_abstract()")
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def method_is_strict() -> MethodMatcher:
|
|
720
|
+
"""Match strictfp methods."""
|
|
721
|
+
|
|
722
|
+
return _ensure_matcher(method_access(MethodAccessFlag.STRICT), "method_is_strict()")
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def method_is_synthetic() -> MethodMatcher:
|
|
726
|
+
"""Match synthetic methods."""
|
|
727
|
+
|
|
728
|
+
return _ensure_matcher(method_access(MethodAccessFlag.SYNTHETIC), "method_is_synthetic()")
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def has_code() -> MethodMatcher:
|
|
732
|
+
"""Match methods that currently have a ``CodeModel``."""
|
|
733
|
+
|
|
734
|
+
return Matcher(lambda method: method.code is not None, "has_code()")
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def is_constructor() -> MethodMatcher:
|
|
738
|
+
"""Match methods named ``<init>``."""
|
|
739
|
+
|
|
740
|
+
return _ensure_matcher(method_named("<init>"), "is_constructor()")
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def is_static_initializer() -> MethodMatcher:
|
|
744
|
+
"""Match methods named ``<clinit>``."""
|
|
745
|
+
|
|
746
|
+
return _ensure_matcher(method_named("<clinit>"), "is_static_initializer()")
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def method_returns(descriptor: str) -> MethodMatcher:
|
|
750
|
+
"""Match methods by return-type descriptor."""
|
|
751
|
+
|
|
752
|
+
return Matcher(
|
|
753
|
+
lambda method: _method_return_descriptor(method.descriptor) == descriptor,
|
|
754
|
+
f"method_returns({descriptor!r})",
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def _flatten_transforms(
|
|
759
|
+
transforms: Iterable[ClassTransform | Pipeline],
|
|
760
|
+
) -> tuple[ClassTransform, ...]:
|
|
761
|
+
flattened: list[ClassTransform] = []
|
|
762
|
+
for transform in transforms:
|
|
763
|
+
if isinstance(transform, Pipeline):
|
|
764
|
+
flattened.extend(transform.transforms)
|
|
765
|
+
else:
|
|
766
|
+
flattened.append(transform)
|
|
767
|
+
return tuple(flattened)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def _expect_none(result: object, message: str) -> None:
|
|
771
|
+
if result is not None:
|
|
772
|
+
raise TypeError(message)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _ensure_matcher[T](
|
|
776
|
+
predicate: Predicate[T] | Matcher[T],
|
|
777
|
+
description: str | None = None,
|
|
778
|
+
) -> Matcher[T]:
|
|
779
|
+
if isinstance(predicate, Matcher):
|
|
780
|
+
existing = cast(Matcher[T], predicate)
|
|
781
|
+
if description is None:
|
|
782
|
+
return existing
|
|
783
|
+
return Matcher(existing._predicate, description)
|
|
784
|
+
if not callable(predicate):
|
|
785
|
+
raise TypeError("Matcher predicates must be callable")
|
|
786
|
+
return Matcher(
|
|
787
|
+
predicate,
|
|
788
|
+
_describe_predicate(predicate) if description is None else description,
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _describe_predicate(predicate: object) -> str:
|
|
793
|
+
name = getattr(predicate, "__name__", None)
|
|
794
|
+
if isinstance(name, str):
|
|
795
|
+
return name
|
|
796
|
+
return type(predicate).__name__
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def _parenthesize_description(description: str) -> str:
|
|
800
|
+
if description.startswith("(") and description.endswith(")"):
|
|
801
|
+
return description
|
|
802
|
+
if " & " not in description and " | " not in description:
|
|
803
|
+
return description
|
|
804
|
+
return f"({description})"
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _join_descriptions(operator: str, left: str, right: str) -> str:
|
|
808
|
+
return f"({left} {operator} {right})"
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
def _combine_variadic_descriptions[T](
|
|
812
|
+
operator: str,
|
|
813
|
+
matchers: tuple[Matcher[T], ...],
|
|
814
|
+
) -> str:
|
|
815
|
+
descriptions = tuple(matcher._description for matcher in matchers)
|
|
816
|
+
if len(descriptions) == 1:
|
|
817
|
+
return descriptions[0]
|
|
818
|
+
joiner = f" {operator} "
|
|
819
|
+
return f"({joiner.join(descriptions)})"
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def _equals_matcher[T, U](
|
|
823
|
+
accessor: Callable[[T], U],
|
|
824
|
+
expected: U,
|
|
825
|
+
description: str,
|
|
826
|
+
) -> Matcher[T]:
|
|
827
|
+
return Matcher(lambda value: accessor(value) == expected, description)
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def _regex_matcher[T](
|
|
831
|
+
accessor: Callable[[T], str],
|
|
832
|
+
pattern: str,
|
|
833
|
+
description: str,
|
|
834
|
+
) -> Matcher[T]:
|
|
835
|
+
compiled = re.compile(pattern)
|
|
836
|
+
return Matcher(lambda value: compiled.fullmatch(accessor(value)) is not None, description)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def _contains_matcher[T, U](
|
|
840
|
+
accessor: Callable[[T], Iterable[U]],
|
|
841
|
+
expected: U,
|
|
842
|
+
description: str,
|
|
843
|
+
) -> Matcher[T]:
|
|
844
|
+
return Matcher(lambda value: expected in accessor(value), description)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def _all_flags_matcher[T, F: IntFlag](
|
|
848
|
+
accessor: Callable[[T], F],
|
|
849
|
+
flags: F,
|
|
850
|
+
description: str,
|
|
851
|
+
) -> Matcher[T]:
|
|
852
|
+
return Matcher(lambda value: (accessor(value) & flags) == flags, description)
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def _any_flags_matcher[T, F: IntFlag](
|
|
856
|
+
accessor: Callable[[T], F],
|
|
857
|
+
flags: F,
|
|
858
|
+
description: str,
|
|
859
|
+
) -> Matcher[T]:
|
|
860
|
+
return Matcher(lambda value: bool(accessor(value) & flags), description)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _method_return_descriptor(descriptor: str) -> str | None:
|
|
864
|
+
try:
|
|
865
|
+
parsed = parse_method_descriptor(descriptor)
|
|
866
|
+
except ValueError:
|
|
867
|
+
return None
|
|
868
|
+
|
|
869
|
+
return_type = parsed.return_type
|
|
870
|
+
if isinstance(return_type, VoidType):
|
|
871
|
+
return return_type.value
|
|
872
|
+
return to_descriptor(return_type)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
__all__ = [
|
|
876
|
+
"ClassMatcher",
|
|
877
|
+
"ClassPredicate",
|
|
878
|
+
"ClassTransform",
|
|
879
|
+
"CodeTransform",
|
|
880
|
+
"FieldMatcher",
|
|
881
|
+
"FieldPredicate",
|
|
882
|
+
"FieldTransform",
|
|
883
|
+
"Matcher",
|
|
884
|
+
"MethodMatcher",
|
|
885
|
+
"MethodPredicate",
|
|
886
|
+
"MethodTransform",
|
|
887
|
+
"Pipeline",
|
|
888
|
+
"Predicate",
|
|
889
|
+
"all_of",
|
|
890
|
+
"any_of",
|
|
891
|
+
"class_access",
|
|
892
|
+
"class_access_any",
|
|
893
|
+
"class_is_abstract",
|
|
894
|
+
"class_is_annotation",
|
|
895
|
+
"class_is_enum",
|
|
896
|
+
"class_is_final",
|
|
897
|
+
"class_is_interface",
|
|
898
|
+
"class_is_module",
|
|
899
|
+
"class_is_package_private",
|
|
900
|
+
"class_is_public",
|
|
901
|
+
"class_is_synthetic",
|
|
902
|
+
"class_name_matches",
|
|
903
|
+
"class_named",
|
|
904
|
+
"class_version",
|
|
905
|
+
"class_version_at_least",
|
|
906
|
+
"class_version_below",
|
|
907
|
+
"extends",
|
|
908
|
+
"field_access",
|
|
909
|
+
"field_access_any",
|
|
910
|
+
"field_descriptor",
|
|
911
|
+
"field_descriptor_matches",
|
|
912
|
+
"field_is_enum_constant",
|
|
913
|
+
"field_is_final",
|
|
914
|
+
"field_is_package_private",
|
|
915
|
+
"field_is_private",
|
|
916
|
+
"field_is_protected",
|
|
917
|
+
"field_is_public",
|
|
918
|
+
"field_is_static",
|
|
919
|
+
"field_is_synthetic",
|
|
920
|
+
"field_is_transient",
|
|
921
|
+
"field_is_volatile",
|
|
922
|
+
"field_name_matches",
|
|
923
|
+
"field_named",
|
|
924
|
+
"has_code",
|
|
925
|
+
"implements",
|
|
926
|
+
"is_constructor",
|
|
927
|
+
"is_static_initializer",
|
|
928
|
+
"method_access",
|
|
929
|
+
"method_access_any",
|
|
930
|
+
"method_descriptor",
|
|
931
|
+
"method_descriptor_matches",
|
|
932
|
+
"method_is_abstract",
|
|
933
|
+
"method_is_bridge",
|
|
934
|
+
"method_is_final",
|
|
935
|
+
"method_is_native",
|
|
936
|
+
"method_is_package_private",
|
|
937
|
+
"method_is_private",
|
|
938
|
+
"method_is_protected",
|
|
939
|
+
"method_is_public",
|
|
940
|
+
"method_is_static",
|
|
941
|
+
"method_is_strict",
|
|
942
|
+
"method_is_synchronized",
|
|
943
|
+
"method_is_synthetic",
|
|
944
|
+
"method_is_varargs",
|
|
945
|
+
"method_name_matches",
|
|
946
|
+
"method_named",
|
|
947
|
+
"method_returns",
|
|
948
|
+
"not_",
|
|
949
|
+
"on_classes",
|
|
950
|
+
"on_code",
|
|
951
|
+
"on_fields",
|
|
952
|
+
"on_methods",
|
|
953
|
+
"pipeline",
|
|
954
|
+
]
|