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.
@@ -0,0 +1,810 @@
1
+ """Parse JVM ``.class`` file bytes into a :class:`ClassFile` tree.
2
+
3
+ This module implements a single-pass reader that deserialises the binary
4
+ class-file format defined in *The Java Virtual Machine Specification* (JVMS §4)
5
+ into the in-memory ``ClassFile`` structure exposed by :mod:`pytecode.info`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+
12
+ from . import attributes, constant_pool, constants, info, instructions
13
+ from .bytes_utils import BytesReader
14
+ from .modified_utf8 import decode_modified_utf8
15
+
16
+ __all__ = ["ClassReader", "MalformedClassException"]
17
+
18
+
19
+ class MalformedClassException(Exception):
20
+ """Raised when the input bytes do not conform to the JVM class-file format (JVMS §4)."""
21
+
22
+
23
+ class ClassReader(BytesReader):
24
+ """Single-pass parser that converts ``.class`` file bytes into a :class:`~pytecode.info.ClassFile` tree.
25
+
26
+ The reader walks the binary layout defined in JVMS §4.1, populating the
27
+ constant pool first (§4.4) and then deserialising fields, methods, and
28
+ attributes in declaration order. The resulting :attr:`class_info` object
29
+ mirrors the on-disk ``ClassFile`` structure.
30
+ """
31
+
32
+ class_info: info.ClassFile
33
+
34
+ def __init__(self, bytes_or_bytearray: bytes | bytearray) -> None:
35
+ """Initialise the reader and immediately parse the class-file bytes.
36
+
37
+ Args:
38
+ bytes_or_bytearray: Raw bytes of a ``.class`` file.
39
+
40
+ Raises:
41
+ MalformedClassException: If the bytes are not a valid class file.
42
+ """
43
+ super().__init__(bytes_or_bytearray)
44
+ self.constant_pool: list[constant_pool.ConstantPoolInfo | None] = []
45
+ self.read_class()
46
+
47
+ @classmethod
48
+ def from_file(cls, path: str | os.PathLike[str]) -> ClassReader:
49
+ """Construct a :class:`ClassReader` from a ``.class`` file on disk.
50
+
51
+ Args:
52
+ path: Filesystem path to the ``.class`` file.
53
+
54
+ Returns:
55
+ A fully-parsed :class:`ClassReader` instance.
56
+ """
57
+ with open(path, "rb") as f:
58
+ file_bytes = f.read()
59
+ return cls(file_bytes)
60
+
61
+ @classmethod
62
+ def from_bytes(cls, bytes_or_bytearray: bytes | bytearray) -> ClassReader:
63
+ """Construct a :class:`ClassReader` from raw bytes.
64
+
65
+ Args:
66
+ bytes_or_bytearray: Raw bytes of a ``.class`` file.
67
+
68
+ Returns:
69
+ A fully-parsed :class:`ClassReader` instance.
70
+ """
71
+ return cls(bytes_or_bytearray)
72
+
73
+ def read_constant_pool_index(self, index: int) -> tuple[constant_pool.ConstantPoolInfo, int]:
74
+ """Read a single constant-pool entry at the given logical index (JVMS §4.4).
75
+
76
+ Args:
77
+ index: One-based constant-pool index for the entry being read.
78
+
79
+ Returns:
80
+ A tuple of the parsed constant-pool info object and the number of
81
+ extra index slots consumed (1 for ``long``/``double``, else 0).
82
+
83
+ Raises:
84
+ ValueError: If the constant-pool tag is unrecognised.
85
+ """
86
+ index_extra, offset, tag = 0, self.offset, self.read_u1()
87
+ cp_type = constant_pool.ConstantPoolInfoType(tag)
88
+
89
+ if cp_type is constant_pool.ConstantPoolInfoType.CLASS:
90
+ cp_info = constant_pool.ClassInfo(index, offset, tag, self.read_u2())
91
+ elif cp_type is constant_pool.ConstantPoolInfoType.STRING:
92
+ cp_info = constant_pool.StringInfo(index, offset, tag, self.read_u2())
93
+ elif cp_type is constant_pool.ConstantPoolInfoType.METHOD_TYPE:
94
+ cp_info = constant_pool.MethodTypeInfo(index, offset, tag, self.read_u2())
95
+ elif cp_type is constant_pool.ConstantPoolInfoType.MODULE:
96
+ cp_info = constant_pool.ModuleInfo(index, offset, tag, self.read_u2())
97
+ elif cp_type is constant_pool.ConstantPoolInfoType.PACKAGE:
98
+ cp_info = constant_pool.PackageInfo(index, offset, tag, self.read_u2())
99
+ elif cp_type is constant_pool.ConstantPoolInfoType.FIELD_REF:
100
+ cp_info = constant_pool.FieldrefInfo(index, offset, tag, self.read_u2(), self.read_u2())
101
+ elif cp_type is constant_pool.ConstantPoolInfoType.METHOD_REF:
102
+ cp_info = constant_pool.MethodrefInfo(index, offset, tag, self.read_u2(), self.read_u2())
103
+ elif cp_type is constant_pool.ConstantPoolInfoType.INTERFACE_METHOD_REF:
104
+ cp_info = constant_pool.InterfaceMethodrefInfo(index, offset, tag, self.read_u2(), self.read_u2())
105
+ elif cp_type is constant_pool.ConstantPoolInfoType.NAME_AND_TYPE:
106
+ cp_info = constant_pool.NameAndTypeInfo(index, offset, tag, self.read_u2(), self.read_u2())
107
+ elif cp_type is constant_pool.ConstantPoolInfoType.DYNAMIC:
108
+ cp_info = constant_pool.DynamicInfo(index, offset, tag, self.read_u2(), self.read_u2())
109
+ elif cp_type is constant_pool.ConstantPoolInfoType.INVOKE_DYNAMIC:
110
+ cp_info = constant_pool.InvokeDynamicInfo(index, offset, tag, self.read_u2(), self.read_u2())
111
+ elif cp_type is constant_pool.ConstantPoolInfoType.INTEGER:
112
+ cp_info = constant_pool.IntegerInfo(index, offset, tag, self.read_u4())
113
+ elif cp_type is constant_pool.ConstantPoolInfoType.FLOAT:
114
+ cp_info = constant_pool.FloatInfo(index, offset, tag, self.read_u4())
115
+ elif cp_type is constant_pool.ConstantPoolInfoType.LONG:
116
+ cp_info = constant_pool.LongInfo(index, offset, tag, self.read_u4(), self.read_u4())
117
+ index_extra = 1
118
+ elif cp_type is constant_pool.ConstantPoolInfoType.DOUBLE:
119
+ cp_info = constant_pool.DoubleInfo(index, offset, tag, self.read_u4(), self.read_u4())
120
+ index_extra = 1
121
+ elif cp_type is constant_pool.ConstantPoolInfoType.UTF8:
122
+ length = self.read_u2()
123
+ str_bytes = self.read_bytes(length)
124
+ cp_info = constant_pool.Utf8Info(index, offset, tag, length, str_bytes)
125
+ elif cp_type is constant_pool.ConstantPoolInfoType.METHOD_HANDLE:
126
+ cp_info = constant_pool.MethodHandleInfo(index, offset, tag, self.read_u1(), self.read_u2())
127
+ else:
128
+ raise ValueError(f"Unknown ConstantPoolInfoType: {cp_type}")
129
+ return cp_info, index_extra
130
+
131
+ def read_align_bytes(self, current_offset: int) -> bytes:
132
+ """Read and discard padding bytes to reach 4-byte alignment.
133
+
134
+ Used by ``tableswitch`` and ``lookupswitch`` instructions whose
135
+ operands must be 4-byte aligned (JVMS §6.5).
136
+
137
+ Args:
138
+ current_offset: Current bytecode offset within the method body.
139
+
140
+ Returns:
141
+ The consumed padding bytes (0–3 bytes).
142
+ """
143
+ align_bytes = (4 - current_offset % 4) % 4
144
+ return self.read_bytes(align_bytes)
145
+
146
+ def read_instruction(self, current_method_offset: int) -> instructions.InsnInfo:
147
+ """Read a single JVM bytecode instruction (JVMS §6.5).
148
+
149
+ Args:
150
+ current_method_offset: Byte offset of this instruction relative to
151
+ the start of the method's ``Code`` attribute bytecode array.
152
+
153
+ Returns:
154
+ The decoded instruction info object.
155
+
156
+ Raises:
157
+ Exception: If the opcode or its ``wide`` variant is invalid.
158
+ """
159
+ opcode = self.read_u1()
160
+ inst_type = instructions.InsnInfoType(opcode)
161
+ instinfo = inst_type.instinfo
162
+ if instinfo is instructions.LocalIndex:
163
+ return instructions.LocalIndex(inst_type, current_method_offset, self.read_u1())
164
+ elif instinfo is instructions.ConstPoolIndex:
165
+ return instructions.ConstPoolIndex(inst_type, current_method_offset, self.read_u2())
166
+ elif instinfo is instructions.ByteValue:
167
+ return instructions.ByteValue(inst_type, current_method_offset, self.read_i1())
168
+ elif instinfo is instructions.ShortValue:
169
+ return instructions.ShortValue(inst_type, current_method_offset, self.read_i2())
170
+ elif instinfo is instructions.Branch:
171
+ return instructions.Branch(inst_type, current_method_offset, self.read_i2())
172
+ elif instinfo is instructions.BranchW:
173
+ return instructions.BranchW(inst_type, current_method_offset, self.read_i4())
174
+ elif instinfo is instructions.IInc:
175
+ index, value = self.read_u1(), self.read_i1()
176
+ return instructions.IInc(inst_type, current_method_offset, index, value)
177
+ elif instinfo is instructions.InvokeDynamic:
178
+ index, unused = self.read_u2(), self.read_bytes(2)
179
+ return instructions.InvokeDynamic(inst_type, current_method_offset, index, unused)
180
+ elif instinfo is instructions.InvokeInterface:
181
+ index, count, unused = self.read_u2(), self.read_u1(), self.read_bytes(1)
182
+ return instructions.InvokeInterface(inst_type, current_method_offset, index, count, unused)
183
+ elif instinfo is instructions.MultiANewArray:
184
+ index, dimensions = self.read_u2(), self.read_u1()
185
+ return instructions.MultiANewArray(inst_type, current_method_offset, index, dimensions)
186
+ elif instinfo is instructions.NewArray:
187
+ atype = instructions.ArrayType(self.read_u1())
188
+ return instructions.NewArray(inst_type, current_method_offset, atype)
189
+ elif instinfo is instructions.LookupSwitch:
190
+ self.read_align_bytes(current_method_offset + 1)
191
+ default, npairs = self.read_i4(), self.read_u4()
192
+ pairs = [instructions.MatchOffsetPair(self.read_i4(), self.read_i4()) for _ in range(npairs)]
193
+ return instructions.LookupSwitch(inst_type, current_method_offset, default, npairs, pairs)
194
+ elif instinfo is instructions.TableSwitch:
195
+ self.read_align_bytes(current_method_offset + 1)
196
+ default, low, high = self.read_i4(), self.read_i4(), self.read_i4()
197
+ offsets = [self.read_i4() for _ in range(high - low + 1)]
198
+ return instructions.TableSwitch(inst_type, current_method_offset, default, low, high, offsets)
199
+ elif inst_type is instructions.InsnInfoType.WIDE:
200
+ wide_opcode = self.read_u1()
201
+ wide_inst_type = instructions.InsnInfoType(opcode + wide_opcode)
202
+ if wide_inst_type.instinfo is instructions.LocalIndexW:
203
+ return instructions.LocalIndexW(wide_inst_type, current_method_offset, self.read_u2())
204
+ elif wide_inst_type.instinfo is instructions.IIncW:
205
+ index, value = self.read_u2(), self.read_i2()
206
+ return instructions.IIncW(wide_inst_type, current_method_offset, index, value)
207
+ elif instinfo is instructions.InsnInfo:
208
+ return instructions.InsnInfo(inst_type, current_method_offset)
209
+
210
+ raise Exception(f"Invalid InstInfoType: {inst_type.name} {inst_type.instinfo}")
211
+
212
+ def read_code_bytes(self, code_length: int) -> list[instructions.InsnInfo]:
213
+ """Read the full bytecode array of a ``Code`` attribute (JVMS §4.7.3).
214
+
215
+ Args:
216
+ code_length: Number of bytes in the bytecode array.
217
+
218
+ Returns:
219
+ Ordered list of decoded instructions.
220
+ """
221
+ start_method_offset = self.offset
222
+ results: list[instructions.InsnInfo] = []
223
+ while (current_method_offset := self.offset - start_method_offset) < code_length:
224
+ insn = self.read_instruction(current_method_offset)
225
+ results.append(insn)
226
+ return results
227
+
228
+ def read_verification_type_info(self) -> attributes.VerificationTypeInfo:
229
+ """Read a single ``verification_type_info`` union (JVMS §4.7.4).
230
+
231
+ Returns:
232
+ The decoded verification-type info variant.
233
+
234
+ Raises:
235
+ ValueError: If the verification-type tag is unrecognised.
236
+ """
237
+ tag = self.read_u1()
238
+ match tag:
239
+ case constants.VerificationType.TOP:
240
+ return attributes.TopVariableInfo(tag)
241
+ case constants.VerificationType.INTEGER:
242
+ return attributes.IntegerVariableInfo(tag)
243
+ case constants.VerificationType.FLOAT:
244
+ return attributes.FloatVariableInfo(tag)
245
+ case constants.VerificationType.DOUBLE:
246
+ return attributes.DoubleVariableInfo(tag)
247
+ case constants.VerificationType.LONG:
248
+ return attributes.LongVariableInfo(tag)
249
+ case constants.VerificationType.NULL:
250
+ return attributes.NullVariableInfo(tag)
251
+ case constants.VerificationType.UNINITIALIZED_THIS:
252
+ return attributes.UninitializedThisVariableInfo(tag)
253
+ case constants.VerificationType.OBJECT:
254
+ return attributes.ObjectVariableInfo(tag, self.read_u2())
255
+ case constants.VerificationType.UNINITIALIZED:
256
+ return attributes.UninitializedVariableInfo(tag, self.read_u2())
257
+ case _:
258
+ raise ValueError(f"Unknown verification type tag: {tag}")
259
+
260
+ def read_element_value_info(self) -> attributes.ElementValueInfo:
261
+ """Read an ``element_value`` structure from an annotation (JVMS §4.7.16.1).
262
+
263
+ Returns:
264
+ The decoded element-value info.
265
+
266
+ Raises:
267
+ ValueError: If the element-value tag character is unrecognised.
268
+ """
269
+ tag = self.read_u1().to_bytes(1, "big").decode("ascii")
270
+
271
+ match tag:
272
+ case x if x in ("B", "C", "D", "F", "I", "J", "S", "Z", "s"):
273
+ return attributes.ElementValueInfo(tag, attributes.ConstValueInfo(self.read_u2()))
274
+ case "e":
275
+ return attributes.ElementValueInfo(
276
+ tag,
277
+ attributes.EnumConstantValueInfo(self.read_u2(), self.read_u2()),
278
+ )
279
+ case "c":
280
+ return attributes.ElementValueInfo(tag, attributes.ClassInfoValueInfo(self.read_u2()))
281
+ case "@":
282
+ return attributes.ElementValueInfo(tag, self.read_annotation_info())
283
+ case "[":
284
+ num_values = self.read_u2()
285
+ values = [self.read_element_value_info() for _ in range(num_values)]
286
+ return attributes.ElementValueInfo(tag, attributes.ArrayValueInfo(num_values, values))
287
+ case _:
288
+ raise ValueError(f"Unknown element value tag: {tag}")
289
+
290
+ def read_annotation_info(self) -> attributes.AnnotationInfo:
291
+ """Read an ``annotation`` structure (JVMS §4.7.16).
292
+
293
+ Returns:
294
+ The decoded annotation info including its element-value pairs.
295
+ """
296
+ type_index = self.read_u2()
297
+ num_element_value_pairs = self.read_u2()
298
+ element_value_pairs = [
299
+ attributes.ElementValuePairInfo(self.read_u2(), self.read_element_value_info())
300
+ for _ in range(num_element_value_pairs)
301
+ ]
302
+ return attributes.AnnotationInfo(type_index, num_element_value_pairs, element_value_pairs)
303
+
304
+ def read_target_info(self, target_type: int) -> attributes.TargetInfo:
305
+ """Read a ``target_info`` union for a type annotation (JVMS §4.7.20).
306
+
307
+ Args:
308
+ target_type: The ``target_type`` byte that selects the union variant.
309
+
310
+ Returns:
311
+ The decoded target info variant.
312
+
313
+ Raises:
314
+ ValueError: If the target type is unrecognised.
315
+ """
316
+ match target_type:
317
+ case x if x in constants.TargetInfoType.TYPE_PARAMETER.value:
318
+ return attributes.TypeParameterTargetInfo(self.read_u1())
319
+ case x if x in constants.TargetInfoType.SUPERTYPE.value:
320
+ return attributes.SupertypeTargetInfo(self.read_u2())
321
+ case x if x in constants.TargetInfoType.TYPE_PARAMETER_BOUND.value:
322
+ return attributes.TypeParameterBoundTargetInfo(self.read_u1(), self.read_u1())
323
+ case x if x in constants.TargetInfoType.EMPTY.value:
324
+ return attributes.EmptyTargetInfo()
325
+ case x if x in constants.TargetInfoType.FORMAL_PARAMETER.value:
326
+ return attributes.FormalParameterTargetInfo(self.read_u1())
327
+ case x if x in constants.TargetInfoType.THROWS.value:
328
+ return attributes.ThrowsTargetInfo(self.read_u2())
329
+ case x if x in constants.TargetInfoType.LOCALVAR.value:
330
+ table_length = self.read_u2()
331
+ table = [
332
+ attributes.TableInfo(self.read_u2(), self.read_u2(), self.read_u2()) for _ in range(table_length)
333
+ ]
334
+ return attributes.LocalvarTargetInfo(table_length, table)
335
+ case x if x in constants.TargetInfoType.CATCH.value:
336
+ return attributes.CatchTargetInfo(self.read_u2())
337
+ case x if x in constants.TargetInfoType.OFFSET.value:
338
+ return attributes.OffsetTargetInfo(self.read_u2())
339
+ case x if x in constants.TargetInfoType.TYPE_ARGUMENT.value:
340
+ return attributes.TypeArgumentTargetInfo(self.read_u2(), self.read_u1())
341
+ case _:
342
+ raise ValueError(f"Unknown target info type: {target_type}")
343
+
344
+ def read_target_path(self) -> attributes.TypePathInfo:
345
+ """Read a ``type_path`` structure for a type annotation (JVMS §4.7.20.2).
346
+
347
+ Returns:
348
+ The decoded type-path info.
349
+ """
350
+ path_length = self.read_u1()
351
+ path = [attributes.PathInfo(self.read_u1(), self.read_u1()) for _ in range(path_length)]
352
+ return attributes.TypePathInfo(path_length, path)
353
+
354
+ def read_type_annotation_info(self) -> attributes.TypeAnnotationInfo:
355
+ """Read a ``type_annotation`` structure (JVMS §4.7.20).
356
+
357
+ Returns:
358
+ The decoded type-annotation info.
359
+ """
360
+ target_type = self.read_u1()
361
+ target_info = self.read_target_info(target_type)
362
+ target_path = self.read_target_path()
363
+ type_index = self.read_u2()
364
+ num_element_value_pairs = self.read_u2()
365
+ element_value_pairs = [
366
+ attributes.ElementValuePairInfo(self.read_u2(), self.read_element_value_info())
367
+ for _ in range(num_element_value_pairs)
368
+ ]
369
+ return attributes.TypeAnnotationInfo(
370
+ target_type,
371
+ target_info,
372
+ target_path,
373
+ type_index,
374
+ num_element_value_pairs,
375
+ element_value_pairs,
376
+ )
377
+
378
+ def read_attribute(self) -> attributes.AttributeInfo:
379
+ """Read a single ``attribute_info`` structure (JVMS §4.7).
380
+
381
+ Recognised attribute names are decoded into their specific subtypes;
382
+ unknown attributes are returned as :class:`~pytecode.attributes.UnimplementedAttr`.
383
+
384
+ Returns:
385
+ The decoded attribute info.
386
+
387
+ Raises:
388
+ ValueError: If the attribute name index does not reference a
389
+ ``CONSTANT_Utf8_info`` entry.
390
+ """
391
+ name_index, length = self.read_u2(), self.read_u4()
392
+
393
+ name_cp = self.constant_pool[name_index]
394
+ if not isinstance(name_cp, constant_pool.Utf8Info):
395
+ raise ValueError(f"name_index({name_index}) should be Utf8Info, not {type(name_cp)}")
396
+
397
+ name = decode_modified_utf8(name_cp.str_bytes)
398
+ attr_type = attributes.AttributeInfoType(name)
399
+
400
+ if attr_type is attributes.AttributeInfoType.SYNTHETIC:
401
+ return attributes.SyntheticAttr(name_index, length)
402
+
403
+ elif attr_type is attributes.AttributeInfoType.DEPRECATED:
404
+ return attributes.DeprecatedAttr(name_index, length)
405
+
406
+ elif attr_type is attributes.AttributeInfoType.CONSTANT_VALUE:
407
+ return attributes.ConstantValueAttr(name_index, length, self.read_u2())
408
+
409
+ elif attr_type is attributes.AttributeInfoType.SIGNATURE:
410
+ return attributes.SignatureAttr(name_index, length, self.read_u2())
411
+
412
+ elif attr_type is attributes.AttributeInfoType.SOURCE_FILE:
413
+ return attributes.SourceFileAttr(name_index, length, self.read_u2())
414
+
415
+ elif attr_type is attributes.AttributeInfoType.MODULE_MAIN_CLASS:
416
+ return attributes.ModuleMainClassAttr(name_index, length, self.read_u2())
417
+
418
+ elif attr_type is attributes.AttributeInfoType.NEST_HOST:
419
+ return attributes.NestHostAttr(name_index, length, self.read_u2())
420
+
421
+ elif attr_type is attributes.AttributeInfoType.CODE:
422
+ max_stack, max_locals = self.read_u2(), self.read_u2()
423
+ code_length = self.read_u4()
424
+ code = self.read_code_bytes(code_length)
425
+ exception_table_length = self.read_u2()
426
+ exception_table = [
427
+ attributes.ExceptionInfo(self.read_u2(), self.read_u2(), self.read_u2(), self.read_u2())
428
+ for _ in range(exception_table_length)
429
+ ]
430
+ attributes_count = self.read_u2()
431
+ attributes_list = [self.read_attribute() for _ in range(attributes_count)]
432
+ return attributes.CodeAttr(
433
+ name_index,
434
+ length,
435
+ max_stack,
436
+ max_locals,
437
+ code_length,
438
+ code,
439
+ exception_table_length,
440
+ exception_table,
441
+ attributes_count,
442
+ attributes_list,
443
+ )
444
+
445
+ elif attr_type is attributes.AttributeInfoType.STACK_MAP_TABLE:
446
+ number_of_entries = self.read_u2()
447
+ entries: list[attributes.StackMapFrameInfo] = []
448
+ for _ in range(number_of_entries):
449
+ frame_type = self.read_u1()
450
+
451
+ match frame_type:
452
+ case x if x in range(0, 64):
453
+ entries.append(attributes.SameFrameInfo(frame_type))
454
+ case x if x in range(64, 128):
455
+ entries.append(
456
+ attributes.SameLocals1StackItemFrameInfo(frame_type, self.read_verification_type_info())
457
+ )
458
+ case 247:
459
+ entries.append(
460
+ attributes.SameLocals1StackItemFrameExtendedInfo(
461
+ frame_type,
462
+ self.read_u2(),
463
+ self.read_verification_type_info(),
464
+ )
465
+ )
466
+ case x if x in range(248, 251):
467
+ entries.append(attributes.ChopFrameInfo(frame_type, self.read_u2()))
468
+ case 251:
469
+ entries.append(attributes.SameFrameExtendedInfo(frame_type, self.read_u2()))
470
+ case x if x in range(252, 255):
471
+ offset_delta = self.read_u2()
472
+ verification_type_infos = [self.read_verification_type_info() for __ in range(frame_type - 251)]
473
+ entries.append(attributes.AppendFrameInfo(frame_type, offset_delta, verification_type_infos))
474
+ case 255:
475
+ offset_delta = self.read_u2()
476
+ number_of_locals = self.read_u2()
477
+ locals = [self.read_verification_type_info() for __ in range(number_of_locals)]
478
+ number_of_stack_items = self.read_u2()
479
+ stack = [self.read_verification_type_info() for __ in range(number_of_stack_items)]
480
+ entries.append(
481
+ attributes.FullFrameInfo(
482
+ frame_type,
483
+ offset_delta,
484
+ number_of_locals,
485
+ locals,
486
+ number_of_stack_items,
487
+ stack,
488
+ )
489
+ )
490
+ case _:
491
+ raise ValueError(f"Unknown stack map frame type: {frame_type}")
492
+
493
+ return attributes.StackMapTableAttr(name_index, length, number_of_entries, entries)
494
+
495
+ elif attr_type is attributes.AttributeInfoType.EXCEPTIONS:
496
+ number_of_exceptions = self.read_u2()
497
+ exception_index_table = [self.read_u2() for _ in range(number_of_exceptions)]
498
+ return attributes.ExceptionsAttr(name_index, length, number_of_exceptions, exception_index_table)
499
+
500
+ elif attr_type is attributes.AttributeInfoType.INNER_CLASSES:
501
+ number_of_classes = self.read_u2()
502
+ classes = [
503
+ attributes.InnerClassInfo(
504
+ self.read_u2(),
505
+ self.read_u2(),
506
+ self.read_u2(),
507
+ constants.NestedClassAccessFlag(self.read_u2()),
508
+ )
509
+ for _ in range(number_of_classes)
510
+ ]
511
+ return attributes.InnerClassesAttr(name_index, length, number_of_classes, classes)
512
+
513
+ elif attr_type is attributes.AttributeInfoType.ENCLOSING_METHOD:
514
+ return attributes.EnclosingMethodAttr(name_index, length, self.read_u2(), self.read_u2())
515
+
516
+ elif attr_type is attributes.AttributeInfoType.SOURCE_DEBUG_EXTENSION:
517
+ return attributes.SourceDebugExtensionAttr(name_index, length, self.read_bytes(length).decode("utf-8"))
518
+
519
+ elif attr_type is attributes.AttributeInfoType.LINE_NUMBER_TABLE:
520
+ line_number_table_length = self.read_u2()
521
+ line_number_table = [
522
+ attributes.LineNumberInfo(self.read_u2(), self.read_u2()) for _ in range(line_number_table_length)
523
+ ]
524
+ return attributes.LineNumberTableAttr(name_index, length, line_number_table_length, line_number_table)
525
+
526
+ elif attr_type is attributes.AttributeInfoType.LOCAL_VARIABLE_TABLE:
527
+ local_variable_table_length = self.read_u2()
528
+ local_variable_table = [
529
+ attributes.LocalVariableInfo(
530
+ self.read_u2(),
531
+ self.read_u2(),
532
+ self.read_u2(),
533
+ self.read_u2(),
534
+ self.read_u2(),
535
+ )
536
+ for _ in range(local_variable_table_length)
537
+ ]
538
+ return attributes.LocalVariableTableAttr(
539
+ name_index, length, local_variable_table_length, local_variable_table
540
+ )
541
+
542
+ elif attr_type is attributes.AttributeInfoType.LOCAL_VARIABLE_TYPE_TABLE:
543
+ local_variable_type_table_length = self.read_u2()
544
+ local_variable_type_table = [
545
+ attributes.LocalVariableTypeInfo(
546
+ self.read_u2(),
547
+ self.read_u2(),
548
+ self.read_u2(),
549
+ self.read_u2(),
550
+ self.read_u2(),
551
+ )
552
+ for _ in range(local_variable_type_table_length)
553
+ ]
554
+ return attributes.LocalVariableTypeTableAttr(
555
+ name_index, length, local_variable_type_table_length, local_variable_type_table
556
+ )
557
+
558
+ elif attr_type is attributes.AttributeInfoType.RUNTIME_VISIBLE_ANNOTATIONS:
559
+ num_annotations = self.read_u2()
560
+ annotation_list = [self.read_annotation_info() for _ in range(num_annotations)]
561
+ return attributes.RuntimeVisibleAnnotationsAttr(name_index, length, num_annotations, annotation_list)
562
+
563
+ elif attr_type is attributes.AttributeInfoType.RUNTIME_INVISIBLE_ANNOTATIONS:
564
+ num_annotations = self.read_u2()
565
+ annotation_list = [self.read_annotation_info() for _ in range(num_annotations)]
566
+ return attributes.RuntimeInvisibleAnnotationsAttr(name_index, length, num_annotations, annotation_list)
567
+
568
+ elif attr_type is attributes.AttributeInfoType.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS:
569
+ num_parameters = self.read_u1()
570
+ parameter_annotations: list[attributes.ParameterAnnotationInfo] = []
571
+ for _ in range(num_parameters):
572
+ num_annotations = self.read_u2()
573
+ annotation_list = [self.read_annotation_info() for _ in range(num_annotations)]
574
+ parameter_annotations.append(attributes.ParameterAnnotationInfo(num_annotations, annotation_list))
575
+ return attributes.RuntimeVisibleParameterAnnotationsAttr(
576
+ name_index, length, num_parameters, parameter_annotations
577
+ )
578
+
579
+ elif attr_type is attributes.AttributeInfoType.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS:
580
+ num_parameters = self.read_u1()
581
+ parameter_annotations_list: list[attributes.ParameterAnnotationInfo] = []
582
+ for _ in range(num_parameters):
583
+ num_annotations = self.read_u2()
584
+ annotation_list = [self.read_annotation_info() for _ in range(num_annotations)]
585
+ parameter_annotations_list.append(attributes.ParameterAnnotationInfo(num_annotations, annotation_list))
586
+ return attributes.RuntimeInvisibleParameterAnnotationsAttr(
587
+ name_index, length, num_parameters, parameter_annotations_list
588
+ )
589
+
590
+ elif attr_type is attributes.AttributeInfoType.RUNTIME_VISIBLE_TYPE_ANNOTATIONS:
591
+ num_annotations = self.read_u2()
592
+ type_annotation_list = [self.read_type_annotation_info() for _ in range(num_annotations)]
593
+ return attributes.RuntimeVisibleTypeAnnotationsAttr(
594
+ name_index, length, num_annotations, type_annotation_list
595
+ )
596
+
597
+ elif attr_type is attributes.AttributeInfoType.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS:
598
+ num_annotations = self.read_u2()
599
+ type_annotation_list = [self.read_type_annotation_info() for _ in range(num_annotations)]
600
+ return attributes.RuntimeInvisibleTypeAnnotationsAttr(
601
+ name_index, length, num_annotations, type_annotation_list
602
+ )
603
+
604
+ elif attr_type is attributes.AttributeInfoType.ANNOTATION_DEFAULT:
605
+ return attributes.AnnotationDefaultAttr(name_index, length, self.read_element_value_info())
606
+
607
+ elif attr_type is attributes.AttributeInfoType.BOOTSTRAP_METHODS:
608
+ num_bootstrap_methods = self.read_u2()
609
+ bootstrap_methods: list[attributes.BootstrapMethodInfo] = []
610
+ for _ in range(num_bootstrap_methods):
611
+ bootstrap_method_ref = self.read_u2()
612
+ num_bootstrap_arguments = self.read_u2()
613
+ bootstrap_arguments = [self.read_u2() for __ in range(num_bootstrap_arguments)]
614
+ bootstrap_methods.append(
615
+ attributes.BootstrapMethodInfo(
616
+ bootstrap_method_ref,
617
+ num_bootstrap_arguments,
618
+ bootstrap_arguments,
619
+ )
620
+ )
621
+ return attributes.BootstrapMethodsAttr(name_index, length, num_bootstrap_methods, bootstrap_methods)
622
+
623
+ elif attr_type is attributes.AttributeInfoType.METHOD_PARAMETERS:
624
+ parameters_count = self.read_u1()
625
+ parameters = [
626
+ attributes.MethodParameterInfo(self.read_u2(), constants.MethodParameterAccessFlag(self.read_u2()))
627
+ for _ in range(parameters_count)
628
+ ]
629
+ return attributes.MethodParametersAttr(name_index, length, parameters_count, parameters)
630
+
631
+ elif attr_type is attributes.AttributeInfoType.MODULE:
632
+ module_name_index = self.read_u2()
633
+ module_flags = constants.ModuleAccessFlag(self.read_u2())
634
+ module_version_index = self.read_u2()
635
+
636
+ requires_count = self.read_u2()
637
+ requires = [
638
+ attributes.RequiresInfo(
639
+ self.read_u2(),
640
+ constants.ModuleRequiresAccessFlag(self.read_u2()),
641
+ self.read_u2(),
642
+ )
643
+ for _ in range(requires_count)
644
+ ]
645
+
646
+ exports_count = self.read_u2()
647
+ exports: list[attributes.ExportInfo] = []
648
+ for _ in range(exports_count):
649
+ exports_index = self.read_u2()
650
+ exports_flags = constants.ModuleExportsAccessFlag(self.read_u2())
651
+ exports_to_count = self.read_u2()
652
+ exports_to_index = [self.read_u2() for __ in range(exports_to_count)]
653
+ exports.append(attributes.ExportInfo(exports_index, exports_flags, exports_to_count, exports_to_index))
654
+
655
+ opens_count = self.read_u2()
656
+ opens: list[attributes.OpensInfo] = []
657
+ for _ in range(opens_count):
658
+ opens_index = self.read_u2()
659
+ opens_flags = constants.ModuleOpensAccessFlag(self.read_u2())
660
+ opens_to_count = self.read_u2()
661
+ opens_to_index = [self.read_u2() for __ in range(opens_to_count)]
662
+ opens.append(attributes.OpensInfo(opens_index, opens_flags, opens_to_count, opens_to_index))
663
+
664
+ uses_count = self.read_u2()
665
+ uses = [self.read_u2() for _ in range(uses_count)]
666
+
667
+ provides_count = self.read_u2()
668
+ provides: list[attributes.ProvidesInfo] = []
669
+ for _ in range(provides_count):
670
+ provides_index = self.read_u2()
671
+ provides_with_count = self.read_u2()
672
+ provides_with_index = [self.read_u2() for __ in range(provides_with_count)]
673
+ provides.append(attributes.ProvidesInfo(provides_index, provides_with_count, provides_with_index))
674
+
675
+ return attributes.ModuleAttr(
676
+ name_index,
677
+ length,
678
+ module_name_index,
679
+ module_flags,
680
+ module_version_index,
681
+ requires_count,
682
+ requires,
683
+ exports_count,
684
+ exports,
685
+ opens_count,
686
+ opens,
687
+ uses_count,
688
+ uses,
689
+ provides_count,
690
+ provides,
691
+ )
692
+
693
+ elif attr_type is attributes.AttributeInfoType.MODULE_PACKAGES:
694
+ package_count = self.read_u2()
695
+ package_index = [self.read_u2() for _ in range(package_count)]
696
+ return attributes.ModulePackagesAttr(name_index, length, package_count, package_index)
697
+
698
+ elif attr_type is attributes.AttributeInfoType.NEST_MEMBERS:
699
+ number_of_classes = self.read_u2()
700
+ classes_list = [self.read_u2() for _ in range(number_of_classes)]
701
+ return attributes.NestMembersAttr(name_index, length, number_of_classes, classes_list)
702
+
703
+ elif attr_type is attributes.AttributeInfoType.RECORD:
704
+ components_count = self.read_u2()
705
+ components: list[attributes.RecordComponentInfo] = []
706
+ for _ in range(components_count):
707
+ comp_name_index = self.read_u2()
708
+ descriptor_index = self.read_u2()
709
+ attributes_count = self.read_u2()
710
+ _attributes = [self.read_attribute() for _ in range(attributes_count)]
711
+ components.append(
712
+ attributes.RecordComponentInfo(comp_name_index, descriptor_index, attributes_count, _attributes)
713
+ )
714
+ return attributes.RecordAttr(name_index, length, components_count, components)
715
+
716
+ elif attr_type is attributes.AttributeInfoType.PERMITTED_SUBCLASSES:
717
+ number_of_classes = self.read_u2()
718
+ classes_list = [self.read_u2() for _ in range(number_of_classes)]
719
+ return attributes.PermittedSubclassesAttr(name_index, length, number_of_classes, classes_list)
720
+
721
+ return attributes.UnimplementedAttr(name_index, length, self.read_bytes(length), attr_type)
722
+
723
+ def read_field(self) -> info.FieldInfo:
724
+ """Read a single ``field_info`` structure (JVMS §4.5).
725
+
726
+ Returns:
727
+ The decoded field info including its attributes.
728
+ """
729
+ access_flags = constants.FieldAccessFlag(self.read_u2())
730
+ name_index = self.read_u2()
731
+ descriptor_index = self.read_u2()
732
+ attributes_count = self.read_u2()
733
+ attributes = [self.read_attribute() for _ in range(attributes_count)]
734
+ return info.FieldInfo(access_flags, name_index, descriptor_index, attributes_count, attributes)
735
+
736
+ def read_method(self) -> info.MethodInfo:
737
+ """Read a single ``method_info`` structure (JVMS §4.6).
738
+
739
+ Returns:
740
+ The decoded method info including its attributes.
741
+ """
742
+ access_flags = constants.MethodAccessFlag(self.read_u2())
743
+ name_index = self.read_u2()
744
+ descriptor_index = self.read_u2()
745
+ attributes_count = self.read_u2()
746
+ attributes = [self.read_attribute() for _ in range(attributes_count)]
747
+ return info.MethodInfo(access_flags, name_index, descriptor_index, attributes_count, attributes)
748
+
749
+ def read_class(self) -> None:
750
+ """Parse the complete ``ClassFile`` structure (JVMS §4.1).
751
+
752
+ Validates the magic number and version, reads the constant pool,
753
+ access flags, class hierarchy info, fields, methods, and attributes.
754
+ The result is stored in :attr:`class_info`.
755
+
756
+ Raises:
757
+ MalformedClassException: If the magic number or version is invalid.
758
+ """
759
+ self.rewind()
760
+ magic = self.read_u4()
761
+ if magic != constants.MAGIC:
762
+ raise MalformedClassException(f"Invalid magic number 0x{magic:x}, requires 0x{constants.MAGIC:x}")
763
+
764
+ minor, major = self.read_u2(), self.read_u2()
765
+ if major >= 56 and minor not in (0, 65535):
766
+ raise MalformedClassException(f"Invalid version {major}/{minor}")
767
+
768
+ cp_count = self.read_u2()
769
+
770
+ self.constant_pool = [None] * cp_count
771
+ index = 1
772
+ while index < cp_count:
773
+ cp_info, index_extra = self.read_constant_pool_index(index)
774
+ self.constant_pool[index] = cp_info
775
+ index += 1 + index_extra
776
+
777
+ access_flags = constants.ClassAccessFlag(self.read_u2())
778
+ this_class = self.read_u2()
779
+ super_class = self.read_u2()
780
+
781
+ interfaces_count = self.read_u2()
782
+ interfaces = [self.read_u2() for _ in range(interfaces_count)]
783
+
784
+ fields_count = self.read_u2()
785
+ fields = [self.read_field() for _ in range(fields_count)]
786
+
787
+ methods_count = self.read_u2()
788
+ methods = [self.read_method() for _ in range(methods_count)]
789
+
790
+ attributes_count = self.read_u2()
791
+ attributes = [self.read_attribute() for _ in range(attributes_count)]
792
+
793
+ self.class_info = info.ClassFile(
794
+ magic,
795
+ minor,
796
+ major,
797
+ cp_count,
798
+ self.constant_pool,
799
+ access_flags,
800
+ this_class,
801
+ super_class,
802
+ interfaces_count,
803
+ interfaces,
804
+ fields_count,
805
+ fields,
806
+ methods_count,
807
+ methods,
808
+ attributes_count,
809
+ attributes,
810
+ )