modules/tool-classfile/src/main/java/net/multiphasicapps/classfile/__StackMapParser__.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package net.multiphasicapps.classfile;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* This class is used to parse the stack map and initialize the initial
* snapshot states for jump targets within the method.
*
* @since 2017/04/16
*/
final class __StackMapParser__
{
/** The stream to decode from. */
protected final DataInputStream in;
/** The number of stack entries. */
protected final int maxstack;
/** The number of local entries. */
protected final int maxlocals;
/** The method byte code. */
protected final ByteCode code;
/** Constant pool. */
protected final Pool pool;
/** This type. */
protected final JavaType thistype;
/** Verification targets. */
private final Map<Integer, StackMapTableState> _targets;
/** The next stack state. */
private final StackMapTableEntry[] _nextstack;
/** The next local variable state. */
private final StackMapTableEntry[] _nextlocals;
/** The placement address. */
private int _placeaddr;
/** The top of the stack. */
private int _stacktop;
/**
* Initializes the stack map parser.
*
* @param __p The constant pool.
* @param __m The method this code exists within.
* @param __new Should the new stack map table format be used?
* @param __in The data for the stack map table.
* @param __bc The owning byte code.
* @param __tt This type.
* @throws InvalidClassFormatException If the stack map table is not
* valid.
* @throws NullPointerException On null arguments.
* @since 2017/04/16
*/
__StackMapParser__(Pool __p, Method __m, boolean __new, byte[] __in,
ByteCode __bc, JavaType __tt)
throws InvalidClassFormatException, NullPointerException
{
// Check
if (__p == null || __m == null || __in == null || __bc == null ||
__tt == null)
throw new NullPointerException("NARG");
// Set
DataInputStream xin;
this.in = (xin = new DataInputStream(
new ByteArrayInputStream(__in)));
int maxstack = __bc.maxStack(),
maxlocals = __bc.maxLocals();
this.maxstack = maxstack;
this.maxlocals = maxlocals;
this.code = __bc;
this.pool = __p;
this.thistype = __tt;
// This is used to set which variables appear next before a state is
// constructed with them
StackMapTableEntry[] nextstack, nextlocals;
this._nextstack = (nextstack = new StackMapTableEntry[maxstack]);
this._nextlocals = (nextlocals = new StackMapTableEntry[maxlocals]);
// Setup initial state
/* {@squirreljme.error JC43 The arguments that are required for the
given method exceeds the maximum number of permitted local
variables. (The method in question; The required number of local
variables; The maximum number of local variables)} */
MethodHandle handle = __m.handle();
boolean isinstance = !__m.flags().isStatic();
JavaType[] jis = handle.javaStack(isinstance);
int jn = jis.length;
if (jn > maxlocals)
throw new InvalidClassFormatException(
String.format("JC43 %s %d %d", handle, jn, maxlocals));
// Setup entries
// If this is an instance initializer method then only the first
// argument is not initialized
boolean isiinit = isinstance && __m.name().isInstanceInitializer();
for (int i = 0; i < jn; i++)
nextlocals[i] = new StackMapTableEntry(jis[i],
(!isiinit || (i != 0)));
// Initialize entries with nothing
for (int i = 0, n = nextstack.length; i < n; i++)
if (nextstack[i] == null)
nextstack[i] = StackMapTableEntry.NOTHING;
for (int i = 0, n = nextlocals.length; i < n; i++)
if (nextlocals[i] == null)
nextlocals[i] = StackMapTableEntry.NOTHING;
// Where states go
Map<Integer, StackMapTableState> targets = new LinkedHashMap<>();
this._targets = targets;
// Record state
this.__next(0, true, -1, -1);
// Parse the stack map table
try (DataInputStream in = xin)
{
// Parsing the class stack map table
if (!__new)
{
// Read the number of entries in the table
int ne = xin.readUnsignedShort();
// All entries in the table are full frames
for (int i = 0; i < ne; i++)
this.__next(this.__oldStyle(), true, -1, i);
}
// The modern stack map table
else
{
// Read the number of entries in the table
int ne = xin.readUnsignedShort();
// Read them all
for (int i = 0; i < ne; i++)
{
// Read the frame type
int type = xin.readUnsignedByte();
int addr;
// Full frame?
if (type == 255)
addr = this.__fullFrame();
// Same frame?
else if (type >= 0 && type <= 63)
addr = this.__sameFrame(type);
// Same locals but a single stack item
else if (type >= 64 && type <= 127)
addr = this.__sameLocalsSingleStack(type - 64);
// Same locals, single stack item, explicit delta
else if (type == 247)
addr = this.__sameLocalsSingleStackExplicit();
// Chopped frame
else if (type >= 248 && type <= 250)
addr = this.__choppedFrame(251 - type);
// Same frame but with a supplied delta
else if (type == 251)
addr = this.__sameFrameDelta();
// Appended frame
else if (type >= 252 && type <= 254)
addr = this.__appendFrame(type - 251);
/* {@squirreljme.error JC44 Unknown StackMapTable
verification type. (The verification type)} */
else
throw new InvalidClassFormatException(
String.format("JC44 %d", type));
// Setup next
this.__next(addr, false, type, i);
}
}
}
/* {@squirreljme.error JC45 Failed to parse the stack map table.} */
catch (IOException e)
{
throw new InvalidClassFormatException("JC45", e);
}
}
/**
* Returns the stack map table.
*
* @return The parsed stack map table.
* @since 2017/10/16
*/
public StackMapTable get()
{
return new StackMapTable(this._targets);
}
/**
* Append extra locals to the frame and clear the stack.
*
* @param __addlocs The number of local variables to add.
* @return The address offset.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __appendFrame(int __addlocs)
throws IOException
{
// Get the atom to use
DataInputStream in = this.in;
int rv = in.readUnsignedShort();
// Stack is cleared
this._stacktop = 0;
// Read in local variables
StackMapTableEntry[] nextlocals = this._nextlocals;
int n = this.maxlocals;
for (int i = 0; __addlocs > 0 && i < n; i++)
{
// Get slot here
StackMapTableEntry s = nextlocals[i];
// If it is not empty, ignore it
if (!s.equals(StackMapTableEntry.NOTHING))
continue;
// Set it
StackMapTableEntry aa;
nextlocals[i] = (aa = this.__loadInfo());
__addlocs--;
// If a wide element was added, then the next one becomes TOP
if (aa.isWide())
nextlocals[++i] = aa.topType();
}
// Error if added stuff remains
/* {@squirreljme.error JC46 Appending local variables to the frame
however there is no room to place them. (The remaining local count)} */
if (__addlocs != 0)
throw new InvalidClassFormatException(
String.format("JC46 %d", __addlocs));
return rv;
}
/**
* Similar frame with no stack and the top few locals removed.
*
* @param __chops The number of variables which get chopped.
* @return The address offset.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __choppedFrame(int __chops)
throws IOException
{
// Get the atom to use
DataInputStream in = this.in;
int rv = in.readUnsignedShort();
// No stack
this._stacktop = 0;
// Chop off some locals
StackMapTableEntry[] nextlocals = this._nextlocals;
int i, n = this.maxlocals;
for (i = n - 1; __chops > 0 && i >= 0; i--)
{
// Get slot here
StackMapTableEntry s = nextlocals[i];
// If it is empty, ignore it
if (s.equals(StackMapTableEntry.NOTHING))
continue;
// Clear top off, but only if it is not an undefined top
if (s.isTop() && !s.equals(StackMapTableEntry.TOP_UNDEFINED))
nextlocals[i--] = StackMapTableEntry.NOTHING;
// Clear it
nextlocals[i] = StackMapTableEntry.NOTHING;
__chops--;
}
// Still chops left?
/* {@squirreljme.error JC47 Could not chop off all local variables
because there are no variables remaining to be chopped. (The
remaining variables to remove)} */
if (__chops != 0)
throw new InvalidClassFormatException(
String.format("JC47 %d", __chops));
return rv;
}
/**
* This reads and parses the full stack frame.
*
* @return The address offset.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __fullFrame()
throws IOException
{
// Get the atom to use
DataInputStream in = this.in;
int rv = in.readUnsignedShort();
// Read in local variables
int nl = in.readUnsignedShort();
/* {@squirreljme.error JC48 The number of specified local variables in
the full frame exceeds the maximum permitted local variable
count. (The read local variable count; The number of locals the
method uses)} */
int maxlocals = this.maxlocals,
maxstack = this.maxstack;
if (nl > maxlocals)
throw new InvalidClassFormatException(
String.format("JC48 %d %d", nl, maxlocals));
int i, o;
StackMapTableEntry[] nextlocals = this._nextlocals;
for (i = 0, o = 0; i < nl; i++)
{
StackMapTableEntry e;
nextlocals[o++] = (e = this.__loadInfo());
// Add top?
if (e.isWide())
nextlocals[o++] = e.topType();
}
for (;o < maxlocals; o++)
nextlocals[o] = StackMapTableEntry.NOTHING;
// Read in stack variables
StackMapTableEntry[] nextstack = this._nextstack;
int ns = in.readUnsignedShort();
for (i = 0, o = 0; i < ns; i++)
{
StackMapTableEntry e;
nextstack[o++] = (e = this.__loadInfo());
// Add top?
if (e.isWide())
nextstack[o++] = e.topType();
}
this._stacktop = o;
return rv;
}
/**
* Loads type information for the stack.
*
* @return The type which was parsed.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private StackMapTableEntry __loadInfo()
throws IOException
{
// Read the tag
DataInputStream in = this.in;
int tag = in.readUnsignedByte();
// Depends on the tag
switch (tag)
{
// Top
case 0:
return StackMapTableEntry.TOP_UNDEFINED;
// Integer
case 1:
return StackMapTableEntry.INTEGER;
// Float
case 2:
return StackMapTableEntry.FLOAT;
// Double
case 3:
return StackMapTableEntry.DOUBLE;
// Long
case 4:
return StackMapTableEntry.LONG;
// Nothing
case 5:
return StackMapTableEntry.NOTHING;
// Uninitialized this
case 6:
return new StackMapTableEntry(this.thistype, false);
// Initialized object
case 7:
return new StackMapTableEntry(new JavaType(
this.pool.<ClassName>get(ClassName.class,
in.readUnsignedShort()).field()), true);
// Uninitialized variable for a new instruction, the pc points
// to the new instruction so the class must be read from
// that instruction to determine the type of that actual
// object
case 8:
return new StackMapTableEntry(new JavaType(this.pool.
<ClassName>get(ClassName.class, this.code.
readRawCodeUnsignedShort(in.readUnsignedShort() + 1))),
false);
// Unknown
default:
/* {@squirreljme.error JC49 The verification tag in the
StackMap/StackMapTable attribute is not valid. (The tag)} */
throw new InvalidClassFormatException(
String.format("JC49 %d", tag));
}
}
/**
* Initializes the next state.
*
* @param __au The address offset.
* @param __abs Absolute position?
* @param __type The type of entry that was just handled, this is for
* debug purposes.
* @param __ne The entry number of this index.
* @return The state for the next address.
* @since 2016/05/20
*/
StackMapTableState __next(int __au, boolean __abs, int __type, int __ne)
{
// Where are we?
int naddr = this._placeaddr;
// Generate it
StackMapTableState rv;
try
{
rv = new StackMapTableState(this._nextlocals,
this._nextstack, this._stacktop);
}
catch (InvalidClassFormatException e)
{
/* {@squirreljme.error JC4a Invalid stack map table at the
specified address. (The address offset; Is the address offset
absolute?; The placement address; The type of entry which
was just handled, -1 means it was old-style or initial state.)} */
throw new InvalidClassFormatException(String.format(
"JC4a %d %b %d %d", __au, __abs, naddr, __type), e);
}
// Set new placement address, the first is always absolute
int pp = (__abs ? __au :
naddr + (__au + (__ne == 0 ? 0 : 1)));
this._placeaddr = pp;
/* {@squirreljme.error JC4b A duplicate stack map information for the
specified address has already been loaded. (The address; The
already existing information; The information to be placed there;
Absolute address?; Current address of parse; The address offset;
The parsed type)} */
// Note that the first instruction if it is a jump target may have an
// explicit state even if it one is always defined implicitly, so
// just ignore it
Map<Integer, StackMapTableState> targets = this._targets;
if (pp != 0 && targets.containsKey(pp))
throw new IllegalStateException(String.format(
"JC4b %d %s %s %b %d %d %d",
pp, targets.get(pp), rv, __abs, naddr, __au, __type));
targets.put(pp, rv);
// Debug
/*Debugging.debugNote("Read state @%d: %s%n", pp, rv);*/
// The stored state
return rv;
}
/**
* Reads in an old style full frame.
*
* @return The address information.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __oldStyle()
throws IOException
{
// Get the atom to use
DataInputStream in = this.in;
int rv = in.readUnsignedShort();
// Read in local variables
int nl = in.readUnsignedShort();
StackMapTableEntry[] inlocals = new StackMapTableEntry[nl];
for (int i = 0; i < nl; i++)
inlocals[i] = this.__loadInfo();
// Read in stack variables
int ns = in.readUnsignedShort();
StackMapTableEntry[] instack = new StackMapTableEntry[ns];
for (int i = 0; i < ns; i++)
instack[i] = this.__loadInfo();
// Assign read local variables
int lat = 0;
StackMapTableEntry[] nextlocals = this._nextlocals;
for (int i = 0; i < nl; i++)
{
// Copy in
StackMapTableEntry e = inlocals[i];
nextlocals[lat++] = e;
// Handling wide type?
if (e.isWide())
{
// Set top
nextlocals[lat++] = e.topType();
// If the top is explicit, then skip it
if (i + 1 < nl && inlocals[i + 1].isTop())
i++;
}
}
// Assign read stack variables
int sat = 0;
StackMapTableEntry[] nextstack = this._nextstack;
for (int i = 0; i < ns; i++)
{
// Copy in
StackMapTableEntry e = instack[i];
nextstack[sat++] = e;
// Handling wide type?
if (e.isWide())
{
// Set top
nextstack[sat++] = e.topType();
// If the top is explicit, then skip it
if (i + 1 < ns && instack[i + 1].isTop())
i++;
}
}
// Stack depth is where the next stack would have been placed
this._stacktop = sat;
return rv;
}
/**
* The same frame is used with no changes.
*
* @param __delta The offset from the earlier offset.
* @return The address information.
* @since 2016/03/26
*/
private int __sameFrame(int __delta)
{
return __delta;
}
/**
* Same frame but with a supplied delta rather than using it with the type.
*
* @return The address information.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __sameFrameDelta()
throws IOException
{
return this.in.readUnsignedShort();
}
/**
* Same locals but the stack has only a single entry.
*
* @param __delta The delta offset.
* @return The address information.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __sameLocalsSingleStack(int __delta)
throws IOException
{
// Load single entry
StackMapTableEntry ent;
this._nextstack[0] = (ent = this.__loadInfo());
// If the entry is wide then the top type will not be specified as it
// will be implicit, so we need to set the according type
if (ent.isWide())
{
this._nextstack[1] = ent.topType();
this._stacktop = 2;
}
// Only a single entry exists
else
this._stacktop = 1;
return __delta;
}
/**
* Same locals but the stack has only a single entry, the delta offset
* is specified.
*
* @return The address information.
* @throws IOException On read errors.
* @since 2016/03/26
*/
private int __sameLocalsSingleStackExplicit()
throws IOException
{
return this.__sameLocalsSingleStack(this.in.readUnsignedShort());
}
}