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/constants.py ADDED
@@ -0,0 +1,208 @@
1
+ """Constants for JVM class file parsing (JVMS §4)."""
2
+
3
+ from enum import Enum, IntEnum, IntFlag
4
+
5
+ __all__ = [
6
+ "ClassAccessFlag",
7
+ "FieldAccessFlag",
8
+ "MAGIC",
9
+ "MethodAccessFlag",
10
+ "MethodParameterAccessFlag",
11
+ "ModuleAccessFlag",
12
+ "ModuleExportsAccessFlag",
13
+ "ModuleOpensAccessFlag",
14
+ "ModuleRequiresAccessFlag",
15
+ "NestedClassAccessFlag",
16
+ "TargetInfoType",
17
+ "TargetType",
18
+ "TypePathKind",
19
+ "VerificationType",
20
+ ]
21
+
22
+ MAGIC = 0xCAFEBABE
23
+ """The magic number identifying a valid Java class file (§4.1)."""
24
+
25
+
26
+ class ModuleAccessFlag(IntFlag):
27
+ """Access flags for modules (§4.7.25, Table 4.7.25-A)."""
28
+
29
+ OPEN = 0x0020
30
+ SYNTHETIC = 0x1000
31
+ MANDATED = 0x8000
32
+
33
+
34
+ class ModuleRequiresAccessFlag(IntFlag):
35
+ """Access flags for module requires directives (§4.7.25, Table 4.7.25-B)."""
36
+
37
+ TRANSITIVE = 0x0020
38
+ STATIC_PHASE = 0x0040
39
+ SYNTHETIC = 0x1000
40
+ MANDATED = 0x8000
41
+
42
+
43
+ class ModuleExportsAccessFlag(IntFlag):
44
+ """Access flags for module exports directives (§4.7.25, Table 4.7.25-C)."""
45
+
46
+ SYNTHETIC = 0x1000
47
+ MANDATED = 0x8000
48
+
49
+
50
+ class ModuleOpensAccessFlag(IntFlag):
51
+ """Access flags for module opens directives (§4.7.25, Table 4.7.25-D)."""
52
+
53
+ SYNTHETIC = 0x1000
54
+ MANDATED = 0x8000
55
+
56
+
57
+ class ClassAccessFlag(IntFlag):
58
+ """Access flags for classes (§4.1, Table 4.1-B)."""
59
+
60
+ PUBLIC = 0x0001
61
+ FINAL = 0x0010
62
+ SUPER = 0x0020
63
+ INTERFACE = 0x0200
64
+ ABSTRACT = 0x0400
65
+ SYNTHETIC = 0x1000
66
+ ANNOTATION = 0x2000
67
+ ENUM = 0x4000
68
+ MODULE = 0x8000
69
+
70
+
71
+ class NestedClassAccessFlag(IntFlag):
72
+ """Access flags for nested classes (§4.7.6, Table 4.7.6-A)."""
73
+
74
+ PUBLIC = 0x0001
75
+ PRIVATE = 0x0002
76
+ PROTECTED = 0x0004
77
+ STATIC = 0x0008
78
+ FINAL = 0x0010
79
+ INTERFACE = 0x0200
80
+ ABSTRACT = 0x0400
81
+ SYNTHETIC = 0x1000
82
+ ANNOTATION = 0x2000
83
+ ENUM = 0x4000
84
+
85
+
86
+ class MethodAccessFlag(IntFlag):
87
+ """Access flags for methods (§4.6, Table 4.6-A)."""
88
+
89
+ PUBLIC = 0x0001
90
+ PRIVATE = 0x0002
91
+ PROTECTED = 0x0004
92
+ STATIC = 0x0008
93
+ FINAL = 0x0010
94
+ SYNCHRONIZED = 0x0020
95
+ BRIDGE = 0x0040
96
+ VARARGS = 0x0080
97
+ NATIVE = 0x0100
98
+ ABSTRACT = 0x0400
99
+ STRICT = 0x0800
100
+ SYNTHETIC = 0x1000
101
+
102
+
103
+ class MethodParameterAccessFlag(IntFlag):
104
+ """Access flags for method parameters (§4.7.24, Table 4.7.24-A)."""
105
+
106
+ FINAL = 0x0010
107
+ SYNTHETIC = 0x1000
108
+ MANDATED = 0x8000
109
+
110
+
111
+ class FieldAccessFlag(IntFlag):
112
+ """Access flags for fields (§4.5, Table 4.5-A)."""
113
+
114
+ PUBLIC = 0x0001
115
+ PRIVATE = 0x0002
116
+ PROTECTED = 0x0004
117
+ STATIC = 0x0008
118
+ FINAL = 0x0010
119
+ VOLATILE = 0x0040
120
+ TRANSIENT = 0x0080
121
+ SYNTHETIC = 0x1000
122
+ ENUM = 0x4000
123
+
124
+
125
+ class TargetType(IntEnum):
126
+ """Target type values for type annotations (§4.7.20, Table 4.7.20-A/B)."""
127
+
128
+ TYPE_PARAMETER_GENERIC_CLASS_OR_INTERFACE = 0x00
129
+ TYPE_PARAMETER_GENERIC_METHOD_OR_CONSTRUCTOR = 0x01
130
+ SUPERTYPE = 0x10
131
+ TYPE_PARAMETER_BOUND_GENERIC_CLASS_OR_INTERFACE = 0x11
132
+ TYPE_PARAMETER_BOUND_GENERIC_METHOD_OR_CONSTRUCTOR = 0x12
133
+ TYPE_IN_FIELD_OR_RECORD = 0x13
134
+ RETURN_OR_OBJECT_TYPE = 0x14
135
+ RECEIVER_TYPE_METHOD_OR_CONSTRUCTOR = 0x15
136
+ FORMAL_PARAMETER_METHOD_CONSTRUCTOR_OR_LAMBDA = 0x16
137
+ TYPE_THROWS = 0x17
138
+ TYPE_LOCAL_VARIABLE = 0x40
139
+ TYPE_RESOURCE_VARIABLE = 0x41
140
+ TYPE_EXCEPTION_PARAMETER = 0x42
141
+ TYPE_INSTANCEOF = 0x43
142
+ TYPE_NEW = 0x44
143
+ TYPE_METHOD_NEW = 0x45
144
+ TYPE_METHOD_IDENTIFIER = 0x46
145
+ TYPE_CAST = 0x47
146
+ TYPE_GENERIC_CONSTRUCTOR = 0x48
147
+ TYPE_GENERIC_METHOD = 0x49
148
+ TYPE_GENERIC_CONSTRUCTOR_NEW = 0x4A
149
+ TYPE_GENERIC_METHOD_IDENTIFIER = 0x4B
150
+
151
+
152
+ class TargetInfoType(Enum):
153
+ """Mapping from target_info union discriminants to target types (§4.7.20.1)."""
154
+
155
+ TYPE_PARAMETER = (
156
+ TargetType.TYPE_PARAMETER_GENERIC_CLASS_OR_INTERFACE,
157
+ TargetType.TYPE_PARAMETER_GENERIC_METHOD_OR_CONSTRUCTOR,
158
+ )
159
+ SUPERTYPE = (TargetType.SUPERTYPE,)
160
+ TYPE_PARAMETER_BOUND = (
161
+ TargetType.TYPE_PARAMETER_BOUND_GENERIC_CLASS_OR_INTERFACE,
162
+ TargetType.TYPE_PARAMETER_BOUND_GENERIC_METHOD_OR_CONSTRUCTOR,
163
+ )
164
+ EMPTY = (
165
+ TargetType.TYPE_IN_FIELD_OR_RECORD,
166
+ TargetType.RETURN_OR_OBJECT_TYPE,
167
+ TargetType.RECEIVER_TYPE_METHOD_OR_CONSTRUCTOR,
168
+ )
169
+ FORMAL_PARAMETER = (TargetType.FORMAL_PARAMETER_METHOD_CONSTRUCTOR_OR_LAMBDA,)
170
+ THROWS = (TargetType.TYPE_THROWS,)
171
+ LOCALVAR = (TargetType.TYPE_LOCAL_VARIABLE, TargetType.TYPE_RESOURCE_VARIABLE)
172
+ CATCH = (TargetType.TYPE_EXCEPTION_PARAMETER,)
173
+ OFFSET = (
174
+ TargetType.TYPE_INSTANCEOF,
175
+ TargetType.TYPE_NEW,
176
+ TargetType.TYPE_METHOD_NEW,
177
+ TargetType.TYPE_METHOD_IDENTIFIER,
178
+ )
179
+ TYPE_ARGUMENT = (
180
+ TargetType.TYPE_CAST,
181
+ TargetType.TYPE_GENERIC_CONSTRUCTOR,
182
+ TargetType.TYPE_GENERIC_METHOD,
183
+ TargetType.TYPE_GENERIC_CONSTRUCTOR_NEW,
184
+ TargetType.TYPE_GENERIC_METHOD_IDENTIFIER,
185
+ )
186
+
187
+
188
+ class TypePathKind(IntEnum):
189
+ """Kind values for type_path entries in type annotations (§4.7.20.2, Table 4.7.20.2-A)."""
190
+
191
+ ARRAY_TYPE = 0
192
+ NESTED_TYPE = 1
193
+ WILDCARD_TYPE = 2
194
+ PARAMETERIZED_TYPE = 3
195
+
196
+
197
+ class VerificationType(IntEnum):
198
+ """Verification type info tags for StackMapTable entries (§4.7.4, Table 4.7.4-A)."""
199
+
200
+ TOP = 0
201
+ INTEGER = 1
202
+ FLOAT = 2
203
+ DOUBLE = 3
204
+ LONG = 4
205
+ NULL = 5
206
+ UNINITIALIZED_THIS = 6
207
+ OBJECT = 7
208
+ UNINITIALIZED = 8
pytecode/debug_info.py ADDED
@@ -0,0 +1,319 @@
1
+ """Utilities for managing JVM debug information in class files.
2
+
3
+ Provides policies, state tracking, and stripping helpers for debug
4
+ attributes such as line numbers, local variables, and source file
5
+ references.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from enum import Enum
11
+ from typing import TYPE_CHECKING, overload
12
+
13
+ from .attributes import AttributeInfo, MethodParametersAttr, SourceDebugExtensionAttr, SourceFileAttr
14
+
15
+ if TYPE_CHECKING:
16
+ from .model import ClassModel, CodeModel, MethodModel
17
+
18
+ __all__ = [
19
+ "DebugInfoPolicy",
20
+ "DebugInfoState",
21
+ "apply_debug_info_policy",
22
+ "has_class_debug_info",
23
+ "has_code_debug_info",
24
+ "is_class_debug_info_stale",
25
+ "is_code_debug_info_stale",
26
+ "mark_class_debug_info_stale",
27
+ "mark_code_debug_info_stale",
28
+ "normalize_debug_info_policy",
29
+ "skip_debug_method_attributes",
30
+ "strip_class_debug_attributes",
31
+ "strip_debug_info",
32
+ ]
33
+
34
+
35
+ class DebugInfoPolicy(Enum):
36
+ """Policy controlling how debug information is handled.
37
+
38
+ Attributes:
39
+ PRESERVE: Keep existing debug information unchanged.
40
+ STRIP: Remove all debug information from the target.
41
+ """
42
+
43
+ PRESERVE = "preserve"
44
+ STRIP = "strip"
45
+
46
+
47
+ class DebugInfoState(Enum):
48
+ """Staleness state of debug information on a model.
49
+
50
+ Attributes:
51
+ FRESH: Debug information is up to date.
52
+ STALE: Debug information may be outdated due to bytecode changes.
53
+ """
54
+
55
+ FRESH = "fresh"
56
+ STALE = "stale"
57
+
58
+
59
+ def normalize_debug_info_policy(policy: DebugInfoPolicy | str) -> DebugInfoPolicy:
60
+ """Coerce a string or enum value into a ``DebugInfoPolicy``.
61
+
62
+ Args:
63
+ policy: A ``DebugInfoPolicy`` member or its string value.
64
+
65
+ Returns:
66
+ The corresponding ``DebugInfoPolicy`` member.
67
+
68
+ Raises:
69
+ ValueError: If the string does not match any policy value.
70
+ """
71
+ if isinstance(policy, DebugInfoPolicy):
72
+ return policy
73
+ try:
74
+ return DebugInfoPolicy(policy)
75
+ except ValueError as exc:
76
+ expected = ", ".join(member.value for member in DebugInfoPolicy)
77
+ raise ValueError(f"debug_info must be one of: {expected}") from exc
78
+
79
+
80
+ def strip_class_debug_attributes(attributes: list[AttributeInfo]) -> list[AttributeInfo]:
81
+ """Filter out class-level debug attributes from a list.
82
+
83
+ Removes ``SourceFileAttr`` and ``SourceDebugExtensionAttr`` entries,
84
+ returning only the non-debug attributes.
85
+
86
+ Args:
87
+ attributes: The attribute list to filter.
88
+
89
+ Returns:
90
+ A new list with class-level debug attributes removed.
91
+ """
92
+ return [
93
+ attribute for attribute in attributes if not isinstance(attribute, (SourceFileAttr, SourceDebugExtensionAttr))
94
+ ]
95
+
96
+
97
+ def skip_debug_method_attributes(attributes: list[AttributeInfo]) -> list[AttributeInfo]:
98
+ """Filter out method-level debug attributes from a list.
99
+
100
+ Removes ``MethodParametersAttr`` entries, returning only the
101
+ remaining attributes.
102
+
103
+ Args:
104
+ attributes: The attribute list to filter.
105
+
106
+ Returns:
107
+ A new list with method-level debug attributes removed.
108
+ """
109
+ return [attribute for attribute in attributes if not isinstance(attribute, MethodParametersAttr)]
110
+
111
+
112
+ def has_class_debug_info(target: ClassModel) -> bool:
113
+ """Check whether a class model contains class-level debug attributes.
114
+
115
+ Args:
116
+ target: The class model to inspect.
117
+
118
+ Returns:
119
+ ``True`` if the class has ``SourceFileAttr`` or
120
+ ``SourceDebugExtensionAttr`` attributes.
121
+ """
122
+ return any(isinstance(attribute, (SourceFileAttr, SourceDebugExtensionAttr)) for attribute in target.attributes)
123
+
124
+
125
+ def has_code_debug_info(target: CodeModel) -> bool:
126
+ """Check whether a code model contains code-level debug information.
127
+
128
+ Args:
129
+ target: The code model to inspect.
130
+
131
+ Returns:
132
+ ``True`` if the code has line numbers, local variables, or local
133
+ variable type entries.
134
+ """
135
+ return bool(target.line_numbers or target.local_variables or target.local_variable_types)
136
+
137
+
138
+ def is_class_debug_info_stale(target: ClassModel) -> bool:
139
+ """Check whether a class model has stale class-level debug info.
140
+
141
+ Args:
142
+ target: The class model to inspect.
143
+
144
+ Returns:
145
+ ``True`` if the debug state is stale and debug attributes are present.
146
+ """
147
+ return target.debug_info_state is DebugInfoState.STALE and has_class_debug_info(target)
148
+
149
+
150
+ def is_code_debug_info_stale(target: CodeModel) -> bool:
151
+ """Check whether a code model has stale code-level debug info.
152
+
153
+ Args:
154
+ target: The code model to inspect.
155
+
156
+ Returns:
157
+ ``True`` if the debug state is stale and debug information is present.
158
+ """
159
+ return target.debug_info_state is DebugInfoState.STALE and has_code_debug_info(target)
160
+
161
+
162
+ def mark_class_debug_info_stale(target: ClassModel) -> ClassModel:
163
+ """Mark a class model's debug information as stale.
164
+
165
+ If the class has debug attributes, sets its debug state to
166
+ ``DebugInfoState.STALE``.
167
+
168
+ Args:
169
+ target: The class model to mark.
170
+
171
+ Returns:
172
+ The same class model, possibly with updated debug state.
173
+ """
174
+ if has_class_debug_info(target):
175
+ target.debug_info_state = DebugInfoState.STALE
176
+ return target
177
+
178
+
179
+ @overload
180
+ def mark_code_debug_info_stale(target: CodeModel) -> CodeModel: ...
181
+
182
+
183
+ @overload
184
+ def mark_code_debug_info_stale(target: MethodModel) -> MethodModel: ...
185
+
186
+
187
+ @overload
188
+ def mark_code_debug_info_stale(target: ClassModel) -> ClassModel: ...
189
+
190
+
191
+ def mark_code_debug_info_stale(target: object) -> object:
192
+ """Mark code-level debug information as stale.
193
+
194
+ Accepts a ``CodeModel``, ``MethodModel``, or ``ClassModel``. For a
195
+ ``CodeModel``, sets its state to stale if it has debug info. For a
196
+ ``MethodModel``, delegates to its code attribute. For a ``ClassModel``,
197
+ recursively marks all methods.
198
+
199
+ Args:
200
+ target: The model whose code debug info should be marked stale.
201
+
202
+ Returns:
203
+ The same model, possibly with updated debug state.
204
+
205
+ Raises:
206
+ TypeError: If *target* is not a supported model type.
207
+ """
208
+ from .model import ClassModel, CodeModel, MethodModel
209
+
210
+ if isinstance(target, CodeModel):
211
+ if has_code_debug_info(target):
212
+ target.debug_info_state = DebugInfoState.STALE
213
+ return target
214
+ if isinstance(target, MethodModel):
215
+ if target.code is not None:
216
+ mark_code_debug_info_stale(target.code)
217
+ return target
218
+ if isinstance(target, ClassModel):
219
+ for method in target.methods:
220
+ mark_code_debug_info_stale(method)
221
+ return target
222
+ raise TypeError("code-debug helper expects a CodeModel, MethodModel, or ClassModel")
223
+
224
+
225
+ @overload
226
+ def apply_debug_info_policy(target: CodeModel, policy: DebugInfoPolicy | str) -> CodeModel: ...
227
+
228
+
229
+ @overload
230
+ def apply_debug_info_policy(target: MethodModel, policy: DebugInfoPolicy | str) -> MethodModel: ...
231
+
232
+
233
+ @overload
234
+ def apply_debug_info_policy(target: ClassModel, policy: DebugInfoPolicy | str) -> ClassModel: ...
235
+
236
+
237
+ def apply_debug_info_policy(target: object, policy: DebugInfoPolicy | str) -> object:
238
+ """Apply a debug information policy to a model.
239
+
240
+ When the policy is ``STRIP``, removes all debug information from the
241
+ target. When the policy is ``PRESERVE``, leaves the target unchanged.
242
+
243
+ Args:
244
+ target: The model to apply the policy to.
245
+ policy: The policy to apply, as an enum member or string.
246
+
247
+ Returns:
248
+ The same model, with debug information stripped if requested.
249
+
250
+ Raises:
251
+ TypeError: If *target* is not a supported model type.
252
+ ValueError: If *policy* is an invalid string.
253
+ """
254
+ from .model import ClassModel, CodeModel, MethodModel
255
+
256
+ debug_policy = normalize_debug_info_policy(policy)
257
+ if isinstance(target, CodeModel):
258
+ if debug_policy is DebugInfoPolicy.STRIP:
259
+ strip_debug_info(target)
260
+ return target
261
+ if isinstance(target, MethodModel):
262
+ if debug_policy is DebugInfoPolicy.STRIP:
263
+ strip_debug_info(target)
264
+ return target
265
+ if isinstance(target, ClassModel):
266
+ if debug_policy is DebugInfoPolicy.STRIP:
267
+ strip_debug_info(target)
268
+ return target
269
+ raise TypeError("debug-info helpers expect a CodeModel, MethodModel, or ClassModel")
270
+
271
+
272
+ @overload
273
+ def strip_debug_info(target: CodeModel) -> CodeModel: ...
274
+
275
+
276
+ @overload
277
+ def strip_debug_info(target: MethodModel) -> MethodModel: ...
278
+
279
+
280
+ @overload
281
+ def strip_debug_info(target: ClassModel) -> ClassModel: ...
282
+
283
+
284
+ def strip_debug_info(target: object) -> object:
285
+ """Remove all debug information from a model.
286
+
287
+ For a ``CodeModel``, clears line numbers, local variables, and local
288
+ variable types. For a ``MethodModel``, delegates to its code attribute.
289
+ For a ``ClassModel``, strips class-level debug attributes and recurses
290
+ into all methods.
291
+
292
+ Args:
293
+ target: The model to strip debug information from.
294
+
295
+ Returns:
296
+ The same model with debug information removed and state set to fresh.
297
+
298
+ Raises:
299
+ TypeError: If *target* is not a supported model type.
300
+ """
301
+ from .model import ClassModel, CodeModel, MethodModel
302
+
303
+ if isinstance(target, CodeModel):
304
+ target.line_numbers.clear()
305
+ target.local_variables.clear()
306
+ target.local_variable_types.clear()
307
+ target.debug_info_state = DebugInfoState.FRESH
308
+ return target
309
+ if isinstance(target, MethodModel):
310
+ if target.code is not None:
311
+ strip_debug_info(target.code)
312
+ return target
313
+ if isinstance(target, ClassModel):
314
+ target.attributes[:] = strip_class_debug_attributes(target.attributes)
315
+ target.debug_info_state = DebugInfoState.FRESH
316
+ for method in target.methods:
317
+ strip_debug_info(method)
318
+ return target
319
+ raise TypeError("debug-info helpers expect a CodeModel, MethodModel, or ClassModel")