1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package net
.multiphasicapps
.classfile
;
12 import java
.io
.ByteArrayInputStream
;
13 import java
.io
.DataInputStream
;
14 import java
.io
.IOException
;
15 import java
.util
.LinkedHashMap
;
19 * This class is used to parse the stack map and initialize the initial
20 * snapshot states for jump targets within the method.
24 final class __StackMapParser__
26 /** The stream to decode from. */
27 protected final DataInputStream in
;
29 /** The number of stack entries. */
30 protected final int maxstack
;
32 /** The number of local entries. */
33 protected final int maxlocals
;
35 /** The method byte code. */
36 protected final ByteCode code
;
39 protected final Pool pool
;
42 protected final JavaType thistype
;
44 /** Verification targets. */
45 private final Map
<Integer
, StackMapTableState
> _targets
;
47 /** The next stack state. */
48 private final StackMapTableEntry
[] _nextstack
;
50 /** The next local variable state. */
51 private final StackMapTableEntry
[] _nextlocals
;
53 /** The placement address. */
54 private int _placeaddr
;
56 /** The top of the stack. */
57 private int _stacktop
;
60 * Initializes the stack map parser.
62 * @param __p The constant pool.
63 * @param __m The method this code exists within.
64 * @param __new Should the new stack map table format be used?
65 * @param __in The data for the stack map table.
66 * @param __bc The owning byte code.
67 * @param __tt This type.
68 * @throws InvalidClassFormatException If the stack map table is not
70 * @throws NullPointerException On null arguments.
73 __StackMapParser__(Pool __p
, Method __m
, boolean __new
, byte[] __in
,
74 ByteCode __bc
, JavaType __tt
)
75 throws InvalidClassFormatException
, NullPointerException
78 if (__p
== null || __m
== null || __in
== null || __bc
== null ||
80 throw new NullPointerException("NARG");
84 this.in
= (xin
= new DataInputStream(
85 new ByteArrayInputStream(__in
)));
86 int maxstack
= __bc
.maxStack(),
87 maxlocals
= __bc
.maxLocals();
88 this.maxstack
= maxstack
;
89 this.maxlocals
= maxlocals
;
94 // This is used to set which variables appear next before a state is
95 // constructed with them
96 StackMapTableEntry
[] nextstack
, nextlocals
;
97 this._nextstack
= (nextstack
= new StackMapTableEntry
[maxstack
]);
98 this._nextlocals
= (nextlocals
= new StackMapTableEntry
[maxlocals
]);
100 // Setup initial state
101 // {@squirreljme.error JC43 The arguments that are required for the
102 // given method exceeds the maximum number of permitted local
103 // variables. (The method in question; The required number of local
104 // variables; The maximum number of local variables)}
105 MethodHandle handle
= __m
.handle();
106 boolean isinstance
= !__m
.flags().isStatic();
107 JavaType
[] jis
= handle
.javaStack(isinstance
);
110 throw new InvalidClassFormatException(
111 String
.format("JC43 %s %d %d", handle
, jn
, maxlocals
));
114 // If this is an instance initializer method then only the first
115 // argument is not initialized
116 boolean isiinit
= isinstance
&& __m
.name().isInstanceInitializer();
117 for (int i
= 0; i
< jn
; i
++)
118 nextlocals
[i
] = new StackMapTableEntry(jis
[i
],
119 (!isiinit
|| (i
!= 0)));
121 // Initialize entries with nothing
122 for (int i
= 0, n
= nextstack
.length
; i
< n
; i
++)
123 if (nextstack
[i
] == null)
124 nextstack
[i
] = StackMapTableEntry
.NOTHING
;
125 for (int i
= 0, n
= nextlocals
.length
; i
< n
; i
++)
126 if (nextlocals
[i
] == null)
127 nextlocals
[i
] = StackMapTableEntry
.NOTHING
;
130 Map
<Integer
, StackMapTableState
> targets
= new LinkedHashMap
<>();
131 this._targets
= targets
;
134 this.__next(0, true, -1, -1);
136 // Parse the stack map table
137 try (DataInputStream in
= xin
)
139 // Parsing the class stack map table
142 // Read the number of entries in the table
143 int ne
= xin
.readUnsignedShort();
145 // All entries in the table are full frames
146 for (int i
= 0; i
< ne
; i
++)
147 this.__next(this.__oldStyle(), true, -1, i
);
150 // The modern stack map table
153 // Read the number of entries in the table
154 int ne
= xin
.readUnsignedShort();
157 for (int i
= 0; i
< ne
; i
++)
159 // Read the frame type
160 int type
= xin
.readUnsignedByte();
165 addr
= this.__fullFrame();
168 else if (type
>= 0 && type
<= 63)
169 addr
= this.__sameFrame(type
);
171 // Same locals but a single stack item
172 else if (type
>= 64 && type
<= 127)
173 addr
= this.__sameLocalsSingleStack(type
- 64);
175 // Same locals, single stack item, explicit delta
176 else if (type
== 247)
177 addr
= this.__sameLocalsSingleStackExplicit();
180 else if (type
>= 248 && type
<= 250)
181 addr
= this.__choppedFrame(251 - type
);
183 // Same frame but with a supplied delta
184 else if (type
== 251)
185 addr
= this.__sameFrameDelta();
188 else if (type
>= 252 && type
<= 254)
189 addr
= this.__appendFrame(type
- 251);
191 // {@squirreljme.error JC44 Unknown StackMapTable
192 // verification type. (The verification type)}
194 throw new InvalidClassFormatException(
195 String
.format("JC44 %d", type
));
198 this.__next(addr
, false, type
, i
);
203 // {@squirreljme.error JC45 Failed to parse the stack map table.}
204 catch (IOException e
)
206 throw new InvalidClassFormatException("JC45", e
);
211 * Returns the stack map table.
213 * @return The parsed stack map table.
216 public StackMapTable
get()
218 return new StackMapTable(this._targets
);
222 * Append extra locals to the frame and clear the stack.
224 * @param __addlocs The number of local variables to add.
225 * @return The address offset.
226 * @throws IOException On read errors.
229 private int __appendFrame(int __addlocs
)
232 // Get the atom to use
233 DataInputStream in
= this.in
;
234 int rv
= in
.readUnsignedShort();
239 // Read in local variables
240 StackMapTableEntry
[] nextlocals
= this._nextlocals
;
241 int n
= this.maxlocals
;
242 for (int i
= 0; __addlocs
> 0 && i
< n
; i
++)
245 StackMapTableEntry s
= nextlocals
[i
];
247 // If it is not empty, ignore it
248 if (!s
.equals(StackMapTableEntry
.NOTHING
))
252 StackMapTableEntry aa
;
253 nextlocals
[i
] = (aa
= this.__loadInfo());
256 // If a wide element was added, then the next one becomes TOP
258 nextlocals
[++i
] = aa
.topType();
261 // Error if added stuff remains
262 // {@squirreljme.error JC46 Appending local variables to the frame
263 // however there is no room to place them. (The remaining local count)}
265 throw new InvalidClassFormatException(
266 String
.format("JC46 %d", __addlocs
));
272 * Similar frame with no stack and the top few locals removed.
274 * @param __chops The number of variables which get chopped.
275 * @return The address offset.
276 * @throws IOException On read errors.
279 private int __choppedFrame(int __chops
)
282 // Get the atom to use
283 DataInputStream in
= this.in
;
284 int rv
= in
.readUnsignedShort();
289 // Chop off some locals
290 StackMapTableEntry
[] nextlocals
= this._nextlocals
;
291 int i
, n
= this.maxlocals
;
292 for (i
= n
- 1; __chops
> 0 && i
>= 0; i
--)
295 StackMapTableEntry s
= nextlocals
[i
];
297 // If it is empty, ignore it
298 if (s
.equals(StackMapTableEntry
.NOTHING
))
301 // Clear top off, but only if it is not an undefined top
302 if (s
.isTop() && !s
.equals(StackMapTableEntry
.TOP_UNDEFINED
))
303 nextlocals
[i
--] = StackMapTableEntry
.NOTHING
;
306 nextlocals
[i
] = StackMapTableEntry
.NOTHING
;
311 // {@squirreljme.error JC47 Could not chop off all local variables
312 // because there are no variables remaining to be chopped. (The
313 // remaining variables to remove)}
315 throw new InvalidClassFormatException(
316 String
.format("JC47 %d", __chops
));
322 * This reads and parses the full stack frame.
324 * @return The address offset.
325 * @throws IOException On read errors.
328 private int __fullFrame()
331 // Get the atom to use
332 DataInputStream in
= this.in
;
333 int rv
= in
.readUnsignedShort();
335 // Read in local variables
336 int nl
= in
.readUnsignedShort();
338 // {@squirreljme.error JC48 The number of specified local variables in
339 // the full frame exceeds the maximum permitted local variable
340 // count. (The read local variable count; The number of locals the
342 int maxlocals
= this.maxlocals
,
343 maxstack
= this.maxstack
;
345 throw new InvalidClassFormatException(
346 String
.format("JC48 %d %d", nl
, maxlocals
));
348 StackMapTableEntry
[] nextlocals
= this._nextlocals
;
349 for (i
= 0, o
= 0; i
< nl
; i
++)
351 StackMapTableEntry e
;
352 nextlocals
[o
++] = (e
= this.__loadInfo());
356 nextlocals
[o
++] = e
.topType();
358 for (;o
< maxlocals
; o
++)
359 nextlocals
[o
] = StackMapTableEntry
.NOTHING
;
361 // Read in stack variables
362 StackMapTableEntry
[] nextstack
= this._nextstack
;
363 int ns
= in
.readUnsignedShort();
364 for (i
= 0, o
= 0; i
< ns
; i
++)
366 StackMapTableEntry e
;
367 nextstack
[o
++] = (e
= this.__loadInfo());
371 nextstack
[o
++] = e
.topType();
379 * Loads type information for the stack.
381 * @return The type which was parsed.
382 * @throws IOException On read errors.
385 private StackMapTableEntry
__loadInfo()
389 DataInputStream in
= this.in
;
390 int tag
= in
.readUnsignedByte();
392 // Depends on the tag
397 return StackMapTableEntry
.TOP_UNDEFINED
;
401 return StackMapTableEntry
.INTEGER
;
405 return StackMapTableEntry
.FLOAT
;
409 return StackMapTableEntry
.DOUBLE
;
413 return StackMapTableEntry
.LONG
;
417 return StackMapTableEntry
.NOTHING
;
419 // Uninitialized this
421 return new StackMapTableEntry(this.thistype
, false);
423 // Initialized object
425 return new StackMapTableEntry(new JavaType(
426 this.pool
.<ClassName
>get(ClassName
.class,
427 in
.readUnsignedShort()).field()), true);
429 // Uninitialized variable for a new instruction, the pc points
430 // to the new instruction so the class must be read from
431 // that instruction to determine the type of that actual
434 return new StackMapTableEntry(new JavaType(this.pool
.
435 <ClassName
>get(ClassName
.class, this.code
.
436 readRawCodeUnsignedShort(in
.readUnsignedShort() + 1))),
441 // {@squirreljme.error JC49 The verification tag in the
442 // StackMap/StackMapTable attribute is not valid. (The tag)}
443 throw new InvalidClassFormatException(
444 String
.format("JC49 %d", tag
));
449 * Initializes the next state.
451 * @param __au The address offset.
452 * @param __abs Absolute position?
453 * @param __type The type of entry that was just handled, this is for
455 * @param __ne The entry number of this index.
456 * @return The state for the next address.
459 StackMapTableState
__next(int __au
, boolean __abs
, int __type
, int __ne
)
462 int naddr
= this._placeaddr
;
465 StackMapTableState rv
;
468 rv
= new StackMapTableState(this._nextlocals
,
469 this._nextstack
, this._stacktop
);
471 catch (InvalidClassFormatException e
)
473 // {@squirreljme.error JC4a Invalid stack map table at the
474 // specified address. (The address offset; Is the address offset
475 // absolute?; The placement address; The type of entry which
476 // was just handled, -1 means it was old-style or initial state.)}
477 throw new InvalidClassFormatException(String
.format(
478 "JC4a %d %b %d %d", __au
, __abs
, naddr
, __type
), e
);
481 // Set new placement address, the first is always absolute
482 int pp
= (__abs ? __au
:
483 naddr
+ (__au
+ (__ne
== 0 ?
0 : 1)));
484 this._placeaddr
= pp
;
486 // {@squirreljme.error JC4b A duplicate stack map information for the
487 // specified address has already been loaded. (The address; The
488 // already existing information; The information to be placed there;
489 // Absolute address?; Current address of parse; The address offset;
491 // Note that the first instruction if it is a jump target may have an
492 // explicit state even if it one is always defined implicitly, so
494 Map
<Integer
, StackMapTableState
> targets
= this._targets
;
495 if (pp
!= 0 && targets
.containsKey(pp
))
496 throw new IllegalStateException(String
.format(
497 "JC4b %d %s %s %b %d %d %d",
498 pp
, targets
.get(pp
), rv
, __abs
, naddr
, __au
, __type
));
502 /*Debugging.debugNote("Read state @%d: %s%n", pp, rv);*/
509 * Reads in an old style full frame.
511 * @return The address information.
512 * @throws IOException On read errors.
515 private int __oldStyle()
518 // Get the atom to use
519 DataInputStream in
= this.in
;
520 int rv
= in
.readUnsignedShort();
522 // Read in local variables
523 int nl
= in
.readUnsignedShort();
524 StackMapTableEntry
[] inlocals
= new StackMapTableEntry
[nl
];
525 for (int i
= 0; i
< nl
; i
++)
526 inlocals
[i
] = this.__loadInfo();
528 // Read in stack variables
529 int ns
= in
.readUnsignedShort();
530 StackMapTableEntry
[] instack
= new StackMapTableEntry
[ns
];
531 for (int i
= 0; i
< ns
; i
++)
532 instack
[i
] = this.__loadInfo();
534 // Assign read local variables
536 StackMapTableEntry
[] nextlocals
= this._nextlocals
;
537 for (int i
= 0; i
< nl
; i
++)
540 StackMapTableEntry e
= inlocals
[i
];
541 nextlocals
[lat
++] = e
;
543 // Handling wide type?
547 nextlocals
[lat
++] = e
.topType();
549 // If the top is explicit, then skip it
550 if (i
+ 1 < nl
&& inlocals
[i
+ 1].isTop())
555 // Assign read stack variables
557 StackMapTableEntry
[] nextstack
= this._nextstack
;
558 for (int i
= 0; i
< ns
; i
++)
561 StackMapTableEntry e
= instack
[i
];
562 nextstack
[sat
++] = e
;
564 // Handling wide type?
568 nextstack
[sat
++] = e
.topType();
570 // If the top is explicit, then skip it
571 if (i
+ 1 < ns
&& instack
[i
+ 1].isTop())
576 // Stack depth is where the next stack would have been placed
577 this._stacktop
= sat
;
583 * The same frame is used with no changes.
585 * @param __delta The offset from the earlier offset.
586 * @return The address information.
589 private int __sameFrame(int __delta
)
595 * Same frame but with a supplied delta rather than using it with the type.
597 * @return The address information.
598 * @throws IOException On read errors.
601 private int __sameFrameDelta()
604 return this.in
.readUnsignedShort();
608 * Same locals but the stack has only a single entry.
610 * @param __delta The delta offset.
611 * @return The address information.
612 * @throws IOException On read errors.
615 private int __sameLocalsSingleStack(int __delta
)
619 StackMapTableEntry ent
;
620 this._nextstack
[0] = (ent
= this.__loadInfo());
622 // If the entry is wide then the top type will not be specified as it
623 // will be implicit, so we need to set the according type
626 this._nextstack
[1] = ent
.topType();
630 // Only a single entry exists
638 * Same locals but the stack has only a single entry, the delta offset
641 * @return The address information.
642 * @throws IOException On read errors.
645 private int __sameLocalsSingleStackExplicit()
648 return this.__sameLocalsSingleStack(this.in
.readUnsignedShort());