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/operands.py ADDED
@@ -0,0 +1,683 @@
1
+ """Symbolic instruction operand wrappers for the editing model.
2
+
3
+ Provides editing-model instruction types that replace raw constant-pool
4
+ indexes and local-variable slot encodings with resolved symbolic values.
5
+ These types are used inside ``CodeModel.instructions`` and are lifted from
6
+ raw ``InsnInfo`` records during ``ClassModel.from_classfile()``, then
7
+ lowered back to spec-faithful ``InsnInfo`` records during
8
+ ``to_classfile()``.
9
+
10
+ All wrapper types inherit from ``InsnInfo`` so the existing
11
+ ``type CodeItem = InsnInfo | Label`` alias and ``_instruction_byte_size``
12
+ dispatch remain valid without changes to their signatures.
13
+
14
+ Covered instruction families:
15
+ Constant-pool-backed: field access, method invocation, type operations,
16
+ constant loading, invokedynamic, multianewarray.
17
+
18
+ Local-variable-backed: all load/store families (including implicit
19
+ ``_0``–``_3`` variants and ``WIDE`` forms), ``RET``, ``IINC``.
20
+
21
+ Out of scope (remain raw ``InsnInfo`` records):
22
+ ``BIPUSH`` / ``SIPUSH`` — immediate integer values, no CP or slot
23
+ reference. ``NEWARRAY`` — primitive-type enum, no CP reference.
24
+ No-operand instructions — nothing to symbolise. Branch / switch
25
+ instructions — already symbolic via ``labels.py``.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from dataclasses import dataclass
31
+ from typing import TYPE_CHECKING
32
+
33
+ from .instructions import (
34
+ InsnInfo,
35
+ InsnInfoType,
36
+ )
37
+
38
+ if TYPE_CHECKING:
39
+ pass
40
+
41
+ __all__ = [
42
+ "FieldInsn",
43
+ "IIncInsn",
44
+ "InterfaceMethodInsn",
45
+ "InvokeDynamicInsn",
46
+ "LdcClass",
47
+ "LdcDouble",
48
+ "LdcDynamic",
49
+ "LdcFloat",
50
+ "LdcInsn",
51
+ "LdcInt",
52
+ "LdcLong",
53
+ "LdcMethodHandle",
54
+ "LdcMethodType",
55
+ "LdcString",
56
+ "LdcValue",
57
+ "MethodInsn",
58
+ "MultiANewArrayInsn",
59
+ "TypeInsn",
60
+ "VarInsn",
61
+ ]
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Opcode classification sets (used for validation in __init__)
65
+ # ---------------------------------------------------------------------------
66
+
67
+ _FIELD_OPCODES: frozenset[InsnInfoType] = frozenset(
68
+ {
69
+ InsnInfoType.GETFIELD,
70
+ InsnInfoType.PUTFIELD,
71
+ InsnInfoType.GETSTATIC,
72
+ InsnInfoType.PUTSTATIC,
73
+ }
74
+ )
75
+
76
+ _METHOD_OPCODES: frozenset[InsnInfoType] = frozenset(
77
+ {
78
+ InsnInfoType.INVOKEVIRTUAL,
79
+ InsnInfoType.INVOKESPECIAL,
80
+ InsnInfoType.INVOKESTATIC,
81
+ }
82
+ )
83
+
84
+ _TYPE_OPCODES: frozenset[InsnInfoType] = frozenset(
85
+ {
86
+ InsnInfoType.NEW,
87
+ InsnInfoType.CHECKCAST,
88
+ InsnInfoType.INSTANCEOF,
89
+ InsnInfoType.ANEWARRAY,
90
+ }
91
+ )
92
+
93
+ # All opcodes that normalize into VarInsn, keyed for fast lookup.
94
+ # Explicit-index forms: ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE,
95
+ # FSTORE, DSTORE, ASTORE, RET (LocalIndex); and their WIDE counterparts
96
+ # (LocalIndexW).
97
+ _VAR_EXPLICIT_OPCODES: frozenset[InsnInfoType] = frozenset(
98
+ {
99
+ InsnInfoType.ILOAD,
100
+ InsnInfoType.LLOAD,
101
+ InsnInfoType.FLOAD,
102
+ InsnInfoType.DLOAD,
103
+ InsnInfoType.ALOAD,
104
+ InsnInfoType.ISTORE,
105
+ InsnInfoType.LSTORE,
106
+ InsnInfoType.FSTORE,
107
+ InsnInfoType.DSTORE,
108
+ InsnInfoType.ASTORE,
109
+ InsnInfoType.RET,
110
+ }
111
+ )
112
+
113
+ # Base opcodes that are valid canonical types for VarInsn (accepted in __init__)
114
+ _VAR_BASE_OPCODES: frozenset[InsnInfoType] = _VAR_EXPLICIT_OPCODES
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # Implicit-slot variant mapping
118
+ # Maps each _N opcode → (canonical_opcode, slot)
119
+ # ---------------------------------------------------------------------------
120
+
121
+ _IMPLICIT_VAR_SLOTS: dict[InsnInfoType, tuple[InsnInfoType, int]] = {
122
+ # ILOAD_0..3
123
+ InsnInfoType.ILOAD_0: (InsnInfoType.ILOAD, 0),
124
+ InsnInfoType.ILOAD_1: (InsnInfoType.ILOAD, 1),
125
+ InsnInfoType.ILOAD_2: (InsnInfoType.ILOAD, 2),
126
+ InsnInfoType.ILOAD_3: (InsnInfoType.ILOAD, 3),
127
+ # LLOAD_0..3
128
+ InsnInfoType.LLOAD_0: (InsnInfoType.LLOAD, 0),
129
+ InsnInfoType.LLOAD_1: (InsnInfoType.LLOAD, 1),
130
+ InsnInfoType.LLOAD_2: (InsnInfoType.LLOAD, 2),
131
+ InsnInfoType.LLOAD_3: (InsnInfoType.LLOAD, 3),
132
+ # FLOAD_0..3
133
+ InsnInfoType.FLOAD_0: (InsnInfoType.FLOAD, 0),
134
+ InsnInfoType.FLOAD_1: (InsnInfoType.FLOAD, 1),
135
+ InsnInfoType.FLOAD_2: (InsnInfoType.FLOAD, 2),
136
+ InsnInfoType.FLOAD_3: (InsnInfoType.FLOAD, 3),
137
+ # DLOAD_0..3
138
+ InsnInfoType.DLOAD_0: (InsnInfoType.DLOAD, 0),
139
+ InsnInfoType.DLOAD_1: (InsnInfoType.DLOAD, 1),
140
+ InsnInfoType.DLOAD_2: (InsnInfoType.DLOAD, 2),
141
+ InsnInfoType.DLOAD_3: (InsnInfoType.DLOAD, 3),
142
+ # ALOAD_0..3
143
+ InsnInfoType.ALOAD_0: (InsnInfoType.ALOAD, 0),
144
+ InsnInfoType.ALOAD_1: (InsnInfoType.ALOAD, 1),
145
+ InsnInfoType.ALOAD_2: (InsnInfoType.ALOAD, 2),
146
+ InsnInfoType.ALOAD_3: (InsnInfoType.ALOAD, 3),
147
+ # ISTORE_0..3
148
+ InsnInfoType.ISTORE_0: (InsnInfoType.ISTORE, 0),
149
+ InsnInfoType.ISTORE_1: (InsnInfoType.ISTORE, 1),
150
+ InsnInfoType.ISTORE_2: (InsnInfoType.ISTORE, 2),
151
+ InsnInfoType.ISTORE_3: (InsnInfoType.ISTORE, 3),
152
+ # LSTORE_0..3
153
+ InsnInfoType.LSTORE_0: (InsnInfoType.LSTORE, 0),
154
+ InsnInfoType.LSTORE_1: (InsnInfoType.LSTORE, 1),
155
+ InsnInfoType.LSTORE_2: (InsnInfoType.LSTORE, 2),
156
+ InsnInfoType.LSTORE_3: (InsnInfoType.LSTORE, 3),
157
+ # FSTORE_0..3
158
+ InsnInfoType.FSTORE_0: (InsnInfoType.FSTORE, 0),
159
+ InsnInfoType.FSTORE_1: (InsnInfoType.FSTORE, 1),
160
+ InsnInfoType.FSTORE_2: (InsnInfoType.FSTORE, 2),
161
+ InsnInfoType.FSTORE_3: (InsnInfoType.FSTORE, 3),
162
+ # DSTORE_0..3
163
+ InsnInfoType.DSTORE_0: (InsnInfoType.DSTORE, 0),
164
+ InsnInfoType.DSTORE_1: (InsnInfoType.DSTORE, 1),
165
+ InsnInfoType.DSTORE_2: (InsnInfoType.DSTORE, 2),
166
+ InsnInfoType.DSTORE_3: (InsnInfoType.DSTORE, 3),
167
+ # ASTORE_0..3
168
+ InsnInfoType.ASTORE_0: (InsnInfoType.ASTORE, 0),
169
+ InsnInfoType.ASTORE_1: (InsnInfoType.ASTORE, 1),
170
+ InsnInfoType.ASTORE_2: (InsnInfoType.ASTORE, 2),
171
+ InsnInfoType.ASTORE_3: (InsnInfoType.ASTORE, 3),
172
+ }
173
+
174
+ # Reverse: (canonical_opcode, slot) → implicit opcode (for lowering)
175
+ _VAR_SHORTCUTS: dict[tuple[InsnInfoType, int], InsnInfoType] = {v: k for k, v in _IMPLICIT_VAR_SLOTS.items()}
176
+
177
+ # WIDE opcode → canonical base opcode
178
+ _WIDE_TO_BASE: dict[InsnInfoType, InsnInfoType] = {
179
+ InsnInfoType.ILOADW: InsnInfoType.ILOAD,
180
+ InsnInfoType.LLOADW: InsnInfoType.LLOAD,
181
+ InsnInfoType.FLOADW: InsnInfoType.FLOAD,
182
+ InsnInfoType.DLOADW: InsnInfoType.DLOAD,
183
+ InsnInfoType.ALOADW: InsnInfoType.ALOAD,
184
+ InsnInfoType.ISTOREW: InsnInfoType.ISTORE,
185
+ InsnInfoType.LSTOREW: InsnInfoType.LSTORE,
186
+ InsnInfoType.FSTOREW: InsnInfoType.FSTORE,
187
+ InsnInfoType.DSTOREW: InsnInfoType.DSTORE,
188
+ InsnInfoType.ASTOREW: InsnInfoType.ASTORE,
189
+ InsnInfoType.RETW: InsnInfoType.RET,
190
+ }
191
+
192
+ # Canonical base opcode → WIDE opcode (for lowering)
193
+ _BASE_TO_WIDE: dict[InsnInfoType, InsnInfoType] = {v: k for k, v in _WIDE_TO_BASE.items()}
194
+
195
+ _U1_MAX = 0xFF
196
+ _U2_MAX = 0xFFFF
197
+ _I2_MIN = -(1 << 15)
198
+ _I2_MAX = (1 << 15) - 1
199
+
200
+
201
+ def _require_u2(value: int, *, context: str) -> int:
202
+ if not 0 <= value <= _U2_MAX:
203
+ raise ValueError(f"{context} must be in range [0, {_U2_MAX}], got {value}")
204
+ return value
205
+
206
+
207
+ def _require_i2(value: int, *, context: str) -> int:
208
+ if not _I2_MIN <= value <= _I2_MAX:
209
+ raise ValueError(f"{context} must be in range [{_I2_MIN}, {_I2_MAX}], got {value}")
210
+ return value
211
+
212
+
213
+ def _require_u1(value: int, *, context: str, minimum: int = 0) -> int:
214
+ if not minimum <= value <= _U1_MAX:
215
+ raise ValueError(f"{context} must be in range [{minimum}, {_U1_MAX}], got {value}")
216
+ return value
217
+
218
+
219
+ # ---------------------------------------------------------------------------
220
+ # LDC value types (frozen dataclasses)
221
+ # ---------------------------------------------------------------------------
222
+
223
+
224
+ @dataclass(frozen=True)
225
+ class LdcInt:
226
+ """Integer constant for ``ldc`` / ``ldc_w`` (CONSTANT_Integer, §4.4.4).
227
+
228
+ Attributes:
229
+ value: The signed 32-bit integer constant.
230
+ """
231
+
232
+ value: int
233
+
234
+
235
+ @dataclass(frozen=True)
236
+ class LdcFloat:
237
+ """Float constant for ``ldc`` / ``ldc_w`` (CONSTANT_Float, §4.4.4).
238
+
239
+ Stored as a raw IEEE 754 bit pattern rather than a Python ``float`` to
240
+ preserve NaN bit patterns and signed zeros exactly.
241
+
242
+ Attributes:
243
+ raw_bits: IEEE 754 single-precision bit pattern as an unsigned
244
+ 32-bit integer.
245
+ """
246
+
247
+ raw_bits: int
248
+
249
+
250
+ @dataclass(frozen=True)
251
+ class LdcLong:
252
+ """Long constant for ``ldc2_w`` (CONSTANT_Long, §4.4.5).
253
+
254
+ Attributes:
255
+ value: The signed 64-bit integer constant.
256
+ """
257
+
258
+ value: int
259
+
260
+
261
+ @dataclass(frozen=True)
262
+ class LdcDouble:
263
+ """Double constant for ``ldc2_w`` (CONSTANT_Double, §4.4.5).
264
+
265
+ Stored as split high/low 32-bit words to preserve exact bit patterns.
266
+
267
+ Attributes:
268
+ high_bytes: Upper 32 bits of the IEEE 754 double-precision bit
269
+ pattern.
270
+ low_bytes: Lower 32 bits of the IEEE 754 double-precision bit
271
+ pattern.
272
+ """
273
+
274
+ high_bytes: int
275
+ low_bytes: int
276
+
277
+
278
+ @dataclass(frozen=True)
279
+ class LdcString:
280
+ """String constant for ``ldc`` / ``ldc_w`` (CONSTANT_String, §4.4.3).
281
+
282
+ Attributes:
283
+ value: The string constant value.
284
+ """
285
+
286
+ value: str
287
+
288
+
289
+ @dataclass(frozen=True)
290
+ class LdcClass:
291
+ """Class literal for ``ldc`` / ``ldc_w`` (CONSTANT_Class, §4.4.1).
292
+
293
+ Attributes:
294
+ name: JVM internal class name (e.g. ``java/lang/Object``).
295
+ """
296
+
297
+ name: str
298
+
299
+
300
+ @dataclass(frozen=True)
301
+ class LdcMethodType:
302
+ """MethodType constant for ``ldc`` / ``ldc_w`` (CONSTANT_MethodType, §4.4.9).
303
+
304
+ Attributes:
305
+ descriptor: JVM method descriptor (e.g. ``(II)V``).
306
+ """
307
+
308
+ descriptor: str
309
+
310
+
311
+ @dataclass(frozen=True)
312
+ class LdcMethodHandle:
313
+ """MethodHandle constant for ``ldc`` / ``ldc_w`` (CONSTANT_MethodHandle, §4.4.8).
314
+
315
+ Attributes:
316
+ reference_kind: Method handle behaviour kind (1–9, per
317
+ JVMS Table 5.4.3.5-A). Kinds 1–4 reference a Fieldref;
318
+ 5–8 reference a Methodref or InterfaceMethodref; 9 references
319
+ an InterfaceMethodref only.
320
+ owner: JVM internal name of the class owning the referenced member.
321
+ name: Name of the referenced field or method.
322
+ descriptor: JVM field or method descriptor of the referenced member.
323
+ is_interface: Whether the owner is an interface type. Controls
324
+ emission of CONSTANT_InterfaceMethodref vs CONSTANT_Methodref.
325
+
326
+ Raises:
327
+ ValueError: If ``reference_kind`` is outside the [1, 9] range.
328
+ """
329
+
330
+ reference_kind: int
331
+ owner: str
332
+ name: str
333
+ descriptor: str
334
+ is_interface: bool = False
335
+
336
+ def __post_init__(self) -> None:
337
+ if not 1 <= self.reference_kind <= 9:
338
+ raise ValueError(f"reference_kind must be in range [1, 9], got {self.reference_kind}")
339
+
340
+
341
+ @dataclass(frozen=True)
342
+ class LdcDynamic:
343
+ """Dynamic constant for ``ldc`` / ``ldc_w`` (CONSTANT_Dynamic / condy, §4.4.10).
344
+
345
+ Attributes:
346
+ bootstrap_method_attr_index: Index into the ``BootstrapMethods``
347
+ attribute (must fit the JVM ``u2`` range).
348
+ name: Symbolic name of the dynamic constant.
349
+ descriptor: JVM field descriptor of the produced value.
350
+
351
+ Raises:
352
+ ValueError: If ``bootstrap_method_attr_index`` exceeds the ``u2``
353
+ range.
354
+ """
355
+
356
+ bootstrap_method_attr_index: int
357
+ name: str
358
+ descriptor: str
359
+
360
+ def __post_init__(self) -> None:
361
+ _require_u2(
362
+ self.bootstrap_method_attr_index,
363
+ context="bootstrap_method_attr_index",
364
+ )
365
+
366
+
367
+ type LdcValue = (
368
+ LdcInt | LdcFloat | LdcLong | LdcDouble | LdcString | LdcClass | LdcMethodType | LdcMethodHandle | LdcDynamic
369
+ )
370
+
371
+ # ---------------------------------------------------------------------------
372
+ # Symbolic instruction wrapper types
373
+ # ---------------------------------------------------------------------------
374
+
375
+
376
+ @dataclass(init=False)
377
+ class FieldInsn(InsnInfo):
378
+ """Symbolic instruction for field access (§6.5.getfield, §6.5.putfield, etc.).
379
+
380
+ Wraps GETFIELD, PUTFIELD, GETSTATIC, and PUTSTATIC with resolved
381
+ symbolic references instead of raw constant-pool indices.
382
+
383
+ Attributes:
384
+ owner: JVM internal name of the field's declaring class
385
+ (e.g. ``java/lang/System``).
386
+ name: Field name.
387
+ descriptor: JVM field descriptor (e.g. ``I``,
388
+ ``Ljava/lang/String;``).
389
+
390
+ Raises:
391
+ ValueError: If ``insn_type`` is not a field access opcode.
392
+ """
393
+
394
+ owner: str
395
+ name: str
396
+ descriptor: str
397
+
398
+ def __init__(
399
+ self,
400
+ insn_type: InsnInfoType,
401
+ owner: str,
402
+ name: str,
403
+ descriptor: str,
404
+ bytecode_offset: int = -1,
405
+ ) -> None:
406
+ if insn_type not in _FIELD_OPCODES:
407
+ raise ValueError(f"{insn_type.name} is not a field access opcode")
408
+ super().__init__(insn_type, bytecode_offset)
409
+ self.owner = owner
410
+ self.name = name
411
+ self.descriptor = descriptor
412
+
413
+
414
+ @dataclass(init=False)
415
+ class MethodInsn(InsnInfo):
416
+ """Symbolic instruction for method invocation (§6.5.invokevirtual, etc.).
417
+
418
+ Wraps INVOKEVIRTUAL, INVOKESPECIAL, and INVOKESTATIC with resolved
419
+ symbolic references. Use ``InterfaceMethodInsn`` for INVOKEINTERFACE.
420
+
421
+ Attributes:
422
+ owner: JVM internal name of the method's declaring class or
423
+ interface.
424
+ name: Method name.
425
+ descriptor: JVM method descriptor (e.g. ``(II)I``).
426
+ is_interface: Whether ``owner`` is an interface type. Controls
427
+ emission of CONSTANT_InterfaceMethodref vs CONSTANT_Methodref
428
+ (relevant for INVOKESTATIC / INVOKESPECIAL on interface
429
+ methods since Java 8+).
430
+
431
+ Raises:
432
+ ValueError: If ``insn_type`` is not a supported method invocation
433
+ opcode.
434
+ """
435
+
436
+ owner: str
437
+ name: str
438
+ descriptor: str
439
+ is_interface: bool
440
+
441
+ def __init__(
442
+ self,
443
+ insn_type: InsnInfoType,
444
+ owner: str,
445
+ name: str,
446
+ descriptor: str,
447
+ is_interface: bool = False,
448
+ bytecode_offset: int = -1,
449
+ ) -> None:
450
+ if insn_type not in _METHOD_OPCODES:
451
+ raise ValueError(
452
+ f"{insn_type.name} is not a method invocation opcode (use InterfaceMethodInsn for INVOKEINTERFACE)"
453
+ )
454
+ super().__init__(insn_type, bytecode_offset)
455
+ self.owner = owner
456
+ self.name = name
457
+ self.descriptor = descriptor
458
+ self.is_interface = is_interface
459
+
460
+
461
+ @dataclass(init=False)
462
+ class InterfaceMethodInsn(InsnInfo):
463
+ """Symbolic instruction for INVOKEINTERFACE (§6.5.invokeinterface).
464
+
465
+ The ``count`` operand (argument word count) is computed automatically
466
+ during lowering from the method descriptor; callers do not set it.
467
+
468
+ Attributes:
469
+ owner: JVM internal name of the interface declaring the method.
470
+ name: Method name.
471
+ descriptor: JVM method descriptor.
472
+ """
473
+
474
+ owner: str
475
+ name: str
476
+ descriptor: str
477
+
478
+ def __init__(
479
+ self,
480
+ owner: str,
481
+ name: str,
482
+ descriptor: str,
483
+ bytecode_offset: int = -1,
484
+ ) -> None:
485
+ super().__init__(InsnInfoType.INVOKEINTERFACE, bytecode_offset)
486
+ self.owner = owner
487
+ self.name = name
488
+ self.descriptor = descriptor
489
+
490
+
491
+ @dataclass(init=False)
492
+ class TypeInsn(InsnInfo):
493
+ """Symbolic instruction for type operations (§6.5.new, §6.5.checkcast, etc.).
494
+
495
+ Wraps NEW, CHECKCAST, INSTANCEOF, and ANEWARRAY with resolved symbolic
496
+ class references.
497
+
498
+ Attributes:
499
+ class_name: JVM internal class name
500
+ (e.g. ``java/lang/StringBuilder``). For ANEWARRAY, the
501
+ element type's internal name or descriptor.
502
+
503
+ Raises:
504
+ ValueError: If ``insn_type`` is not a type instruction opcode.
505
+ """
506
+
507
+ class_name: str
508
+
509
+ def __init__(
510
+ self,
511
+ insn_type: InsnInfoType,
512
+ class_name: str,
513
+ bytecode_offset: int = -1,
514
+ ) -> None:
515
+ if insn_type not in _TYPE_OPCODES:
516
+ raise ValueError(f"{insn_type.name} is not a type instruction opcode")
517
+ super().__init__(insn_type, bytecode_offset)
518
+ self.class_name = class_name
519
+
520
+
521
+ @dataclass(init=False)
522
+ class VarInsn(InsnInfo):
523
+ """Symbolic instruction for local variable access (§6.5.iload, §6.5.astore, etc.).
524
+
525
+ Normalises all implicit slot-encoded opcodes (``ILOAD_0``–``ASTORE_3``),
526
+ standard explicit forms (``ILOAD`` through ``ASTORE``, ``RET``), and WIDE
527
+ variants (``ILOADW``–``RETW``) into a single representation.
528
+
529
+ The ``type`` field always holds the *canonical* non-WIDE base opcode
530
+ (e.g. ``ILOAD``, not ``ILOAD_0`` or ``ILOADW``).
531
+
532
+ Lowering selects the optimal encoding automatically:
533
+
534
+ - slot 0–3 with a matching implicit form → implicit 1-byte opcode
535
+ - slot 0–255 → explicit 2-byte form (opcode + u1)
536
+ - slot 256–65535 → WIDE 4-byte form (WIDE prefix + opcode + u2)
537
+
538
+ RET has no implicit forms, but supports WIDE encoding for slots > 255.
539
+
540
+ Attributes:
541
+ slot: Local variable table index (``u2`` range, 0–65535).
542
+
543
+ Raises:
544
+ ValueError: If ``insn_type`` is not a local variable opcode or
545
+ ``slot`` exceeds the ``u2`` range.
546
+ """
547
+
548
+ slot: int
549
+
550
+ def __init__(
551
+ self,
552
+ insn_type: InsnInfoType,
553
+ slot: int,
554
+ bytecode_offset: int = -1,
555
+ ) -> None:
556
+ if insn_type not in _VAR_BASE_OPCODES:
557
+ raise ValueError(f"{insn_type.name} is not a local variable instruction opcode")
558
+ super().__init__(insn_type, bytecode_offset)
559
+ self.slot = _require_u2(slot, context="local variable slot")
560
+
561
+
562
+ @dataclass(init=False)
563
+ class IIncInsn(InsnInfo):
564
+ """Symbolic instruction for IINC / IINCW (§6.5.iinc).
565
+
566
+ Normalises both the standard and WIDE forms. Lowering selects the
567
+ appropriate encoding:
568
+
569
+ - slot 0–255 and increment fits in i1 (–128..127) → standard 3-byte form
570
+ - slot > 255 or increment outside i1 range → WIDE 6-byte form
571
+
572
+ Attributes:
573
+ slot: Local variable table index (``u2`` range, 0–65535).
574
+ increment: Signed increment value (``i2`` range, –32768..32767).
575
+
576
+ Raises:
577
+ ValueError: If ``slot`` exceeds the ``u2`` range or ``increment``
578
+ exceeds the ``i2`` range.
579
+ """
580
+
581
+ slot: int
582
+ increment: int
583
+
584
+ def __init__(
585
+ self,
586
+ slot: int,
587
+ increment: int,
588
+ bytecode_offset: int = -1,
589
+ ) -> None:
590
+ super().__init__(InsnInfoType.IINC, bytecode_offset)
591
+ self.slot = _require_u2(slot, context="local variable slot")
592
+ self.increment = _require_i2(increment, context="iinc increment")
593
+
594
+
595
+ @dataclass(init=False)
596
+ class LdcInsn(InsnInfo):
597
+ """Symbolic instruction for constant loading (§6.5.ldc, §6.5.ldc_w, §6.5.ldc2_w).
598
+
599
+ Lowering selects the minimal encoding: ``ldc`` (2 bytes) when the
600
+ constant-pool index fits in one byte (≤ 255), ``ldc_w`` (3 bytes)
601
+ otherwise, for single-slot constants (int, float, string, class,
602
+ method-type, method-handle, dynamic). Double-slot constants (long,
603
+ double) always use ``ldc2_w`` (3 bytes).
604
+
605
+ Attributes:
606
+ value: Tagged constant determining the constant-pool entry type.
607
+ """
608
+
609
+ value: LdcValue
610
+
611
+ def __init__(
612
+ self,
613
+ value: LdcValue,
614
+ bytecode_offset: int = -1,
615
+ ) -> None:
616
+ super().__init__(InsnInfoType.LDC_W, bytecode_offset)
617
+ self.value = value
618
+
619
+
620
+ @dataclass(init=False)
621
+ class InvokeDynamicInsn(InsnInfo):
622
+ """Symbolic instruction for INVOKEDYNAMIC (§6.5.invokedynamic).
623
+
624
+ Attributes:
625
+ bootstrap_method_attr_index: Index into the ``BootstrapMethods``
626
+ attribute (must fit the JVM ``u2`` range).
627
+ name: Symbolic method name resolved via the bootstrap method.
628
+ descriptor: JVM method descriptor of the call site.
629
+
630
+ Raises:
631
+ ValueError: If ``bootstrap_method_attr_index`` exceeds the ``u2``
632
+ range.
633
+ """
634
+
635
+ bootstrap_method_attr_index: int
636
+ name: str
637
+ descriptor: str
638
+
639
+ def __init__(
640
+ self,
641
+ bootstrap_method_attr_index: int,
642
+ name: str,
643
+ descriptor: str,
644
+ bytecode_offset: int = -1,
645
+ ) -> None:
646
+ super().__init__(InsnInfoType.INVOKEDYNAMIC, bytecode_offset)
647
+ self.bootstrap_method_attr_index = _require_u2(
648
+ bootstrap_method_attr_index,
649
+ context="bootstrap_method_attr_index",
650
+ )
651
+ self.name = name
652
+ self.descriptor = descriptor
653
+
654
+
655
+ @dataclass(init=False)
656
+ class MultiANewArrayInsn(InsnInfo):
657
+ """Symbolic instruction for MULTIANEWARRAY (§6.5.multianewarray).
658
+
659
+ Attributes:
660
+ class_name: JVM internal name of the array type
661
+ (e.g. ``[[Ljava/lang/String;`` for ``String[][]``).
662
+ dimensions: Number of dimensions to allocate (``u1`` range, 1–255).
663
+
664
+ Raises:
665
+ ValueError: If ``dimensions`` is outside the [1, 255] range.
666
+ """
667
+
668
+ class_name: str
669
+ dimensions: int
670
+
671
+ def __init__(
672
+ self,
673
+ class_name: str,
674
+ dimensions: int,
675
+ bytecode_offset: int = -1,
676
+ ) -> None:
677
+ super().__init__(InsnInfoType.MULTIANEWARRAY, bytecode_offset)
678
+ self.class_name = class_name
679
+ self.dimensions = _require_u1(
680
+ dimensions,
681
+ context="multianewarray dimensions",
682
+ minimum=1,
683
+ )
pytecode/py.typed ADDED
File without changes