SquirrelJME/SquirrelJME

View on GitHub
modules/tool-classfile/src/main/java/dev/shadowtail/classfile/xlate/ByteCodeProcessor.java

Summary

Maintainability
D
2 days
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// Multi-Phasic Applications: SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the GNU General Public License v3+, or later.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package dev.shadowtail.classfile.xlate;

import dev.shadowtail.classfile.pool.InvokeType;
import java.util.Map;
import net.multiphasicapps.classfile.ByteCode;
import net.multiphasicapps.classfile.ClassName;
import net.multiphasicapps.classfile.ConstantValue;
import net.multiphasicapps.classfile.ConstantValueClass;
import net.multiphasicapps.classfile.FieldDescriptor;
import net.multiphasicapps.classfile.FieldReference;
import net.multiphasicapps.classfile.Instruction;
import net.multiphasicapps.classfile.InstructionIndex;
import net.multiphasicapps.classfile.InstructionJumpTarget;
import net.multiphasicapps.classfile.InstructionJumpTargets;
import net.multiphasicapps.classfile.InvalidClassFormatException;
import net.multiphasicapps.classfile.JavaType;
import net.multiphasicapps.classfile.LookupSwitch;
import net.multiphasicapps.classfile.MethodHandle;
import net.multiphasicapps.classfile.MethodReference;
import net.multiphasicapps.classfile.PrimitiveType;
import net.multiphasicapps.classfile.StackMapTable;
import net.multiphasicapps.classfile.StackMapTableState;

/**
 * This class goes through the byte code for a method and then performs stack
 * and instruction work on it.
 *
 * @since 2019/04/06
 */
public final class ByteCodeProcessor
{
    /** The input byte code to be read. */
    protected final ByteCode bytecode;
    
    /** Handle for byte-code operations. */
    protected final ByteCodeHandler handler;
    
    /** The state of the byte code. */
    protected final ByteCodeState state;
    
    /** Stack map table, needed for local wiping. */
    protected final StackMapTable stackmaptable;
    
    /** Reverse jump table. */
    private final Map<Integer, InstructionJumpTargets> _revjumps;
    
    /** Jump targets for this instruction. */
    private InstructionJumpTargets _ijt;
    
    /** Flase is the preprocessor state, otherwise run the handler. */
    private boolean _dohandling;
    
    /** Is an exception possible. */
    private boolean _canexception;
    
    /**
     * Initializes the byte code processor.
     *
     * @param __bc The target byte code.
     * @param __h Handler for the byte code operations.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    public ByteCodeProcessor(ByteCode __bc, ByteCodeHandler __h)
        throws NullPointerException
    {
        if (__bc == null || __h == null)
            throw new NullPointerException("NARG");
        
        this.bytecode = __bc;
        this.handler = __h;
        
        // The state is used to share between the processor and the handler
        ByteCodeState state = __h.state();
        this.state = state;
        
        // Load initial Java stack state from the initial stack map
        JavaStackState s;
        StackMapTable stackmaptable = __bc.stackMapTable();
        this.stackmaptable = stackmaptable;
        state.stack = (s = JavaStackState.of(stackmaptable.get(0),
            __bc.writtenLocals()));
        state.stacks.put(0, s);
        
        // Reverse jump table to detect jump backs
        this._revjumps = __bc.reverseJumpTargets();
        
        // Get details and names of the stuff
        state.classname = __bc.thisType();
        state.methodname = __bc.name();
        state.methodtype = __bc.type();
        
        // Store info
        state.exceptionranges = new ExceptionHandlerRanges(__bc);
    }
    
    /**
     * Processes the byte code and calls the destination handler.
     *
     * @since 2019/04/06
     */
    public final void process()
    {
        // Run process
        try
        {
            this.__aaaProcess();
        }
        
        // {@squirreljme.error JC17 Failed to process the byte code, this may
        // be due to an invalid class or an internal compiler error. (The
        // last processed instruction)}
        catch (InvalidClassFormatException|IllegalArgumentException|
             IllegalStateException|IndexOutOfBoundsException e)
        {
            throw new InvalidClassFormatException("JC17 " + this.state, e);
        }
    }
    
    /**
     * Processes the byte code and calls the destination handler.
     *
     * @since 2019/04/06
     */
    private final void __aaaProcess()
    {
        ByteCode bytecode = this.bytecode;
        ByteCodeState state = this.state;
        ByteCodeHandler handler = this.handler;
        StackMapTable stackmaptable = this.stackmaptable;
        Map<Integer, JavaStackState> stacks = state.stacks;
        Map<Integer, StateOperations> stackpoison = state.stackpoison;
        
        // Go through each operation twice, performing pre-processing first
        // to make things a bit simpler and more well known when it comes
        // to caching.
        for (int pp = 0; pp < 2; pp++)
        {
            // Is handling to be done?
            boolean dohandling = (pp != 0);
            this._dohandling = dohandling;
            
            // Since this is the start, the last address needs to be reset
            // because it will be invalid!
            state.lastaddr = -1;
            state.followaddr = -1;
            
            // Process instruction
            Instruction lastinst = null;
            SimplifiedJavaInstruction lastsji = null;
            for (Instruction inst : bytecode)
            {
                // Translate to simple instruction for easier handling
                SimplifiedJavaInstruction sji =
                    new SimplifiedJavaInstruction(inst);
                
                // Debug
                if (__Debug__.ENABLED)
                    todo.DEBUG.note("%s %s (%s)", (dohandling ? "Handling" :
                        "Preprocessing"), sji, inst);
                
                // Current instruction info
                state.instruction = inst;
                state.simplified = sji;
                
                // Store the last processed address
                state.lastaddr = state.addr;
                
                // Current processing this address
                int addr = inst.address();
                state.addr = addr;
                
                // Set line where this code was found
                state.line = bytecode.lineOfAddress(addr);
                
                // Following address, may be used to calculate if the stack
                // needs to be transitioned
                state.followaddr = bytecode.addressFollowing(addr);
                
                // These jump targets are used to map out the state of stacks
                // across various points
                InstructionJumpTargets ijt, rijt;
                this._ijt = (ijt = inst.jumpTargets());
                state.jumptargets = ijt;
                
                // Reverse jump targets are used to detect jumps to previous
                // addresses
                rijt = this._revjumps.get(addr);
                state.reversejumptargets = (rijt != null ? rijt :
                    new InstructionJumpTargets());
                
                // Get the stack, which must exist
                JavaStackState stack = stacks.get(addr);
                if (stack == null)
                {
                    // Some code generated by the older compilers ends up
                    // defining parts of loops or exception handlers which
                    // are defined but completely skipped via a goto, but they
                    // are then taken back to this point. They should hopefully
                    // have a stack map state defined for them, so just
                    // initialize a blank state from that instead.
                    StackMapTableState smts = stackmaptable.get(addr);
                    if (smts != null)
                        stack = JavaStackState.of(smts,
                            this.bytecode.writtenLocals());
                    
                    // {@squirreljme.error JC18 No recorded stack state for
                    // this position. (The address to check)}
                    if (stack == null)
                        throw new InvalidClassFormatException("JC18 " + addr);
                }
                
                // Load stack
                state.stack = stack;
                
                // Reset exception possibility, this is used to determine
                // if the stack update should actually accept exception
                // targets even if they are specified
                this._canexception = false;
                state.canexception = false;
                
                // Preprocessing operations
                if (!dohandling)
                {
                    // If there is a stack map table, adjust types that are
                    // used on the stack along with the removal of locals
                    // and such
                    StackMapTableState smts = stackmaptable.get(addr);
                    if (smts != null)
                    {
                        // Debug
                        if (__Debug__.ENABLED)
                            todo.DEBUG.note("SMT BEF: %s", stack);
                        
                        stack = stack.filterByStackMap(smts);
                        
                        // Debug
                        if (__Debug__.ENABLED)
                            todo.DEBUG.note("SMT AFT: %s", stack);
                    }
                    
                    // If we are jumping back to this instruction at any point
                    // we need to flush the stack so that nothing is cached on
                    // it. The resulting flushed stack is then used instead
                    // Note that if we jump to ourselves we might have entered
                    // with something cached and might end up using that when
                    // we do not want to
                    if (state.reversejumptargets.hasSameOrLaterAddress(addr))
                    {
                        // Perform a flush of the cache
                        JavaStackResult fres = stack.doCacheFlush();
                        
                        // Set natural flow as poisoned, operations have to
                        // be done to match the correct state
                        StateOperations sops = fres.operations();
                        if (!sops.isEmpty())
                            stackpoison.put(addr, sops);
                        
                        // Use the result of the flush as the state instead so
                        // that it propagates ahead from now on
                        stack = fres.after();
                    }
                    
                    // Was the stack changed?
                    if (state.stack != stack)
                        state.stack = stack;
                }
                
                // Handle instruction
                else
                {
                    // Call pre-handler
                    handler.instructionSetup();
                    
                    // If the stack has been adjusted for any reason, replace
                    // the stored stack for this point
                    JavaStackState mns = state.stack;
                    if (!stack.equals(mns))
                        stacks.put(addr, (stack = mns));
                }
                
                // Handle the operation
                switch (sji.operation())
                {
                        // Object array load
                    case InstructionIndex.AALOAD:
                        this.__doArrayLoad(null);
                        break;
                        
                        // Object array store
                    case InstructionIndex.AASTORE:
                        this.__doArrayStore(null);
                        break;
                        
                        // Null constant
                    case InstructionIndex.ACONST_NULL:
                        this.__doAConstNull();
                        break;
                        
                        // Allocate new array
                    case InstructionIndex.ANEWARRAY:
                        this.__doNewArray(sji.<ClassName>argument(0,
                            ClassName.class));
                        break;
                        
                        // Length of array
                    case InstructionIndex.ARRAYLENGTH:
                        this.__doArrayLength();
                        break;
                        
                        // Throw exception
                    case InstructionIndex.ATHROW:
                        this.__doThrow();
                        break;
                        
                        // Check that object is of a type, or fail
                    case InstructionIndex.CHECKCAST:
                        this.__doCheckCast(sji.<ClassName>argument(0,
                            ClassName.class));
                        break;
                        
                        // Convert data
                    case SimplifiedJavaInstruction.CONVERT:
                        this.__doConvert(sji.<StackJavaType>argument(0,
                                StackJavaType.class),
                            sji.<StackJavaType>argument(1,
                                StackJavaType.class));
                        break;
                        
                        // Get field
                    case InstructionIndex.GETFIELD:
                        this.__doFieldGet(sji.<FieldReference>argument(0,
                            FieldReference.class));
                        break;
                        
                        // Get static
                    case InstructionIndex.GETSTATIC:
                        this.__doStaticGet(sji.<FieldReference>argument(0,
                            FieldReference.class));
                        break;
                        
                        // Goto
                    case InstructionIndex.GOTO:
                        this.__doGoto(sji.<InstructionJumpTarget>argument(0,
                            InstructionJumpTarget.class));
                        break;
                        
                        // If comparison against zero
                    case SimplifiedJavaInstruction.IF:
                        this.__doIf(sji.<DataType>argument(0, DataType.class),
                            sji.<CompareType>argument(1, CompareType.class),
                            sji.<InstructionJumpTarget>argument(2,
                                InstructionJumpTarget.class));
                        break;
                        
                        // Compare two values
                    case SimplifiedJavaInstruction.IF_CMP:
                        this.__doIfCmp(
                            sji.<DataType>argument(0, DataType.class),
                            sji.<CompareType>argument(1, CompareType.class),
                            sji.<InstructionJumpTarget>argument(2,
                                InstructionJumpTarget.class));
                        break;
                        
                        // Increment local
                    case InstructionIndex.IINC:
                        this.__doIInc(sji.intArgument(0), sji.intArgument(1));
                        break;
                        
                        // Invoke interface
                    case InstructionIndex.INVOKEINTERFACE:
                        this.__doInvoke(InvokeType.INTERFACE,
                            sji.<MethodReference>argument(0,
                                MethodReference.class));
                        break;
                    
                        // Invoke special
                    case InstructionIndex.INVOKESPECIAL:
                        this.__doInvoke(InvokeType.SPECIAL,
                            sji.<MethodReference>argument(0,
                                MethodReference.class));
                        break;
                    
                        // Invoke static
                    case InstructionIndex.INVOKESTATIC:
                        this.__doInvoke(InvokeType.STATIC,
                            sji.<MethodReference>argument(0,
                                MethodReference.class));
                        break;
                        
                        // Invoke virtual
                    case InstructionIndex.INVOKEVIRTUAL:
                        this.__doInvoke(InvokeType.VIRTUAL,
                            sji.<MethodReference>argument(0,
                                MethodReference.class));
                        break;
                        
                        // Checks that the given class is an instance of an
                        // object.
                    case InstructionIndex.INSTANCEOF:
                        this.__doInstanceOf(sji.<ClassName>argument(0,
                            ClassName.class));
                        break;
                    
                        // Load constant
                    case InstructionIndex.LDC:
                        this.__doLdc(sji.<ConstantValue>argument(0,
                            ConstantValue.class));
                        break;
                    
                        // Load local variable to the stack
                    case SimplifiedJavaInstruction.LOAD:
                        this.__doLoad(sji.<DataType>argument(0,
                            DataType.class), sji.intArgument(1));
                        break;
                        
                        // Lookup switch
                    case InstructionIndex.LOOKUPSWITCH:
                        this.__doLookupSwitch(sji.<LookupSwitch>argument(0,
                            LookupSwitch.class));
                        break;
                        
                        // Math
                    case SimplifiedJavaInstruction.MATH:
                        this.__doMath(sji.<DataType>argument(0,
                            DataType.class), sji.<MathType>argument(1,
                                MathType.class));
                        break;
                        
                        // Math with constant
                    case SimplifiedJavaInstruction.MATH_CONST:
                        this.__doMathConst(sji.<DataType>argument(0,
                            DataType.class), sji.<MathType>argument(1,
                                MathType.class), sji.<Number>argument(2,
                                    Number.class));
                        break;
                        
                        // Enter monitor
                    case InstructionIndex.MONITORENTER:
                        this.__doMonitor(true);
                        break;
                        
                        // Exit monitor
                    case InstructionIndex.MONITOREXIT:
                        this.__doMonitor(false);
                        break;
                    
                        // Multiple new array
                    case InstructionIndex.MULTIANEWARRAY:
                        this.__doMultiANewArray(
                            sji.<ClassName>argument(0, ClassName.class),
                            sji.intArgument(1));
                        break;
                    
                        // Create new instance of something
                    case InstructionIndex.NEW:
                        this.__doNew(sji.<ClassName>argument(0,
                            ClassName.class));
                        break;
                        
                        // This literally does nothing so no output code needs to
                        // be generated at all
                    case InstructionIndex.NOP:
                        this.__doNop();
                        break;
                        
                        // Primitive array load
                    case SimplifiedJavaInstruction.PALOAD:
                        this.__doArrayLoad(sji.<PrimitiveType>argument(0,
                            PrimitiveType.class));
                        break;
                        
                        // Primitive array store
                    case SimplifiedJavaInstruction.PASTORE:
                        this.__doArrayStore(sji.<PrimitiveType>argument(0,
                            PrimitiveType.class));
                        break;
                    
                        // Put of instance field
                    case InstructionIndex.PUTFIELD:
                        this.__doFieldPut(sji.<FieldReference>argument(0,
                            FieldReference.class));
                        break;
                        
                        // Put static field
                    case InstructionIndex.PUTSTATIC:
                        this.__doStaticPut(sji.<FieldReference>argument(0,
                            FieldReference.class));
                        break;
                    
                        // Return from method, with no return value
                    case InstructionIndex.RETURN:
                        this.__doReturn(null);
                        break;
                    
                        // Stack shuffle
                    case SimplifiedJavaInstruction.STACKSHUFFLE:
                        this.__doStackShuffle(
                            sji.<JavaStackShuffleType>argument(0,
                                JavaStackShuffleType.class));
                        break;
                    
                        // Place stack variable into local
                    case SimplifiedJavaInstruction.STORE:
                        this.__doStore(sji.<DataType>argument(0,
                            DataType.class), sji.intArgument(1));
                        break;
                        
                        // Return value
                    case SimplifiedJavaInstruction.VRETURN:
                        this.__doReturn(sji.<DataType>argument(0,
                            DataType.class).toJavaType());
                        break;
                    
                        // Not yet implemented
                    default:
                        throw new todo.OOPS(
                            sji.toString() + "/" + inst.toString());
                }
                
                // Call post-handler
                if (dohandling)
                    handler.instructionFinish();
                
                // Set last
                lastsji = sji;
                lastinst = inst;
            }
        }
    }
    
    /**
     * Pushes a null constant to the stack.
     *
     * @since 2019/04/16
     */
    private final void __doAConstNull()
    {
        // -> [ref]
        JavaStackResult result = this.state.stack.doStack(0, JavaType.OBJECT);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doCopy(JavaStackResult.INPUT_ZERO, result.out(0));
    }
    
    /**
     * Gets length of array.
     *
     * @since 2019/04/06
     */
    private final void __doArrayLength()
    {
        // An exception may be thrown
        this._canexception = true;
        
        // [array] -> [len]
        JavaStackResult result = this.state.stack.doStack(1, JavaType.INTEGER);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doArrayLength(result.in(0), result.out(0));
    }
    
    /**
     * Loads value from value.
     *
     * @param __pt The type to load, {@code null} is considered to be an
     * object.
     * @since 2019/04/06
     */
    private final void __doArrayLoad(PrimitiveType __pt)
    {
        // An exception may be thrown
        this._canexception = true;
        
        // This is easily determined from the primitive type
        JavaType faketype;
        if (__pt != null)
            faketype = __pt.stackJavaType();
        
        // Otherwise, pop twice and see what the array is and work from
        // that
        else
        {
            // Pop two
            JavaStackResult wouldbe = this.state.stack.doStack(2);
            
            // Get the base type
            ClassName maybecl = wouldbe.in(0).type.type().className();
            
            // If this is an array then get the component type and work
            // from it, otherwise just assume it is Object since we could
            // not derive that info at all
            faketype = (maybecl.isArray() ?
                new JavaType(maybecl.componentType()) : JavaType.OBJECT);
        }
        
        // [array, index] -> [value]
        JavaStackResult result = this.state.stack.doStack(2, faketype);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doArrayLoad(DataType.of(__pt), result.in(0),
            result.in(1), result.out(0));
    }
    
    /**
     * Stores value into an array.
     *
     * @param __pt The type to store, {@code null} is considered to be an
     * object.
     * @since 2019/04/06
     */
    private final void __doArrayStore(PrimitiveType __pt)
    {
        // An exception may be thrown
        this._canexception = true;
        
        // [array, index, value]
        JavaStackResult result = this.state.stack.doStack(3);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doArrayStore(DataType.of(__pt), result.in(0),
            result.in(1), result.in(2));
    }
    
    /**
     * Checks that the object on the stack is of the given type.
     *
     * @param __cn The name of the class to check.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doCheckCast(ClassName __cn)
        throws NullPointerException
    {
        // An exception may be thrown
        this._canexception = true;
        
        // [object] -> [object]
        JavaStackResult result = this.state.stack.doCheckCast(
            new JavaType(__cn));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Do check cast
        this.handler.doCheckCast(__cn, result.in(0));
    }
    
    /**
     * Converts from one Java type to another
     *
     * @param __from The source type.
     * @param __to The destination type.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/16
     */
    private final void __doConvert(StackJavaType __from, StackJavaType __to)
        throws NullPointerException
    {
        if (__from == null || __to == null)
            throw new NullPointerException("NARG");
        
        // [from] -> [to]
        JavaStackResult result = this.state.stack.doStack(1, __to.toJavaType());
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doConvert(__from, result.in(0), __to, result.out(0));
    }
    
    /**
     * Reads a value from a field.
     *
     * @param __fr The field reference.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doFieldGet(FieldReference __fr)
        throws NullPointerException
    {
        if (__fr == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // [inst] -> [value]
        ByteCodeState state = this.state;
        JavaStackResult result = state.stack.doStack(1,
            new JavaType(__fr.memberType()));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doFieldGet(__fr, result.in(0), result.out(0));
    }
    
    /**
     * Puts a value into a field.
     *
     * @param __fr The field reference.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/04
     */
    private final void __doFieldPut(FieldReference __fr)
        throws NullPointerException
    {
        if (__fr == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // [inst, value] ->
        ByteCodeState state = this.state;
        JavaStackResult result = state.stack.doStack(2);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doFieldPut(__fr, result.in(0), result.in(1));
    }
    
    /**
     * Goes to another address.
     *
     * @param __jt The target.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doGoto(InstructionJumpTarget __jt)
        throws NullPointerException
    {
        if (__jt == null)
            throw new NullPointerException("NARG");
        
        // Do nothing!
        this.__update(this.state.stack.doNothing());
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Just do an always true comparison on the zero register
        this.handler.doIfICmp(CompareType.TRUE, JavaStackResult.INPUT_ZERO,
            JavaStackResult.INPUT_ZERO, __jt);
    }
    
    /**
     * Performs if comparison against zero.
     *
     * @param __type The type to work with on the stack.
     * @param __ct The comparison type.
     * @param __ijt The instruction jump target.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/05
     */
    private final void __doIf(DataType __type, CompareType __ct,
        InstructionJumpTarget __ijt)
        throws NullPointerException
    {
        if (__type == null || __ct == null || __ijt == null)
            throw new NullPointerException("NARG");
        
        // {@squirreljme.error JC19 Cannot compare float or double.}
        if (__type == DataType.FLOAT || __type == DataType.DOUBLE)
            throw new IllegalArgumentException("JC19");
        
        // [val] ->
        JavaStackResult result = this.state.stack.doStack(1);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doIfICmp(__ct, result.in(0), JavaStackResult.INPUT_ZERO,
            __ijt);
    }
    
    /**
     * Performs if comparison of two values against each other.
     *
     * @param __type The type to work with on the stack.
     * @param __ct The comparison type.
     * @param __ijt The instruction jump target.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doIfCmp(DataType __type, CompareType __ct,
        InstructionJumpTarget __ijt)
        throws NullPointerException
    {
        if (__type == null || __ct == null || __ijt == null)
            throw new NullPointerException("NARG");
        
        // {@squirreljme.error JC1a Cannot compare float or double.}
        if (__type == DataType.FLOAT || __type == DataType.DOUBLE)
            throw new IllegalArgumentException("JC1a");
        
        // [a, b] ->
        JavaStackResult result = this.state.stack.doStack(2);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doIfICmp(__ct, result.in(0), result.in(1), __ijt);
    }
    
    /**
     * Increments local variable.
     *
     * @param __l The local to increment.
     * @param __v The value to increment by.
     * @since 2019/04/06
     */
    private final void __doIInc(int __l, int __v)
    {
        // Just write an integer to the integer so its state is known
        JavaStackResult result = this.state.stack.
            doLocalSet(JavaType.INTEGER, __l);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
            
        // There might be items on the stack which were cached and now no
        // longer are because they got pulverized, so de-cache those
        ByteCodeHandler handler = this.handler;
        handler.doStateOperations(result.operations());
        
        // Add value
        handler.doMath(StackJavaType.INTEGER, MathType.ADD,
            result.out(0).asInput(), __v,
            result.out(0));
    }
    
    /**
     * Checks that the class is the given instance.
     *
     * @param __cl The class to check.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/16
     */
    private final void __doInstanceOf(ClassName __cl)
        throws NullPointerException
    {
        if (__cl == null)
            throw new NullPointerException("NARG");
        
        // [object] -> [int]
        JavaStackResult result = this.state.stack.doStack(1, JavaType.INTEGER);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doInstanceOf(__cl, result.in(0), result.out(0));
    }
    
    /**
     * Handles invocation of other methods.
     *
     * @param __t The type of invocation to perform.
     * @param __r The method to invoke.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/03
     */
    private final void __doInvoke(InvokeType __t, MethodReference __r)
        throws NullPointerException
    {
        if (__t == null || __r == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // Return value type, if any
        MethodHandle mf = __r.handle();
        FieldDescriptor rv = mf.descriptor().returnValue();
        boolean hasrv = (rv != null);
        
        // The number of arguments to pop is the instance (if non-static) and
        // the number of arguments taken
        int popcount = (__t.hasInstance() ? 1 : 0) +
            mf.descriptor().argumentCount();
        
        // Perform stack operation
        ByteCodeState state = this.state;
        JavaStackResult result = (!hasrv ? state.stack.doStack(popcount) :
            state.stack.doStack(popcount, new JavaType(rv)));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doInvoke(__t, __r, (!hasrv ? null :
            result.out(0)), result.in());
    }
    
    /**
     * Loads constant value onto the stack.
     *
     * @param __v The value to push.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/03
     */
    private final void __doLdc(ConstantValue __v)
        throws NullPointerException
    {
        if (__v == null)
            throw new NullPointerException("NARG");
        
        // Get push properties
        JavaType jt = __v.type().javaType();
        
        // An exception is only throwable on classes, because the class
        // could potentially fail to initialize properly
        this._canexception = (__v instanceof ConstantValueClass);
        
        // Push to the stack this type, the result is always cached
        JavaStackResult result = this.state.stack.doStack(0, true, jt);
        this.__update(result);
        
        // Do not call generator, we just want the stack result
        if (!this._dohandling)
            return;
        
        // Call the appropriate handler
        ByteCodeHandler handler = this.handler;
        switch (__v.type())
        {
            case INTEGER:
                handler.doMath(StackJavaType.INTEGER, MathType.OR,
                    JavaStackResult.INPUT_ZERO, (Integer)__v.boxedValue(),
                    result.out(0));
                break;
                
            case FLOAT:
                handler.doMath(StackJavaType.FLOAT, MathType.OR,
                    JavaStackResult.INPUT_ZERO, (Float)__v.boxedValue(),
                    result.out(0));
                break;
            
            case LONG:
                handler.doMath(StackJavaType.LONG, MathType.OR,
                    JavaStackResult.INPUT_ZERO, (Long)__v.boxedValue(),
                    result.out(0));
                break;
                
            case DOUBLE:
                handler.doMath(StackJavaType.DOUBLE, MathType.OR,
                    JavaStackResult.INPUT_ZERO, (Double)__v.boxedValue(),
                    result.out(0));
                break;
            
            case STRING:
                handler.doPoolLoad(__v.boxedValue(), result.out(0));
                break;
                
            case CLASS:
                handler.doClassObjectLoad((ClassName)__v.boxedValue(),
                    result.out(0));
                break;
            
            default:
                throw new todo.OOPS();
        }
    }
    
    /**
     * Loads from a local and puts to the stack.
     *
     * @param __jt The type to push.
     * @param __from The source local.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/03
     */
    private final void __doLoad(DataType __jt, int __from)
        throws NullPointerException
    {
        if (__jt == null)
            throw new NullPointerException("NARG");
        
        // Load from local variable
        JavaStackResult result = this.state.stack.doLocalLoad(__from);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Only perform the copy if the value is different, because otherwise
        // it would have just been cached
        if (result.in(0).register != result.out(0).register)
            this.handler.doCopy(result.in(0), result.out(0));
    }
    
    /**
     * Handles lookup switch.
     *
     * @param __ls The switch.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/16
     */
    private final void __doLookupSwitch(LookupSwitch __ls)
        throws NullPointerException
    {
        if (__ls == null)
            throw new NullPointerException("NARG");
        
        // [key] ->
        JavaStackResult result = this.state.stack.doStack(1);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doLookupSwitch(result.in(0), __ls);
    }
    
    /**
     * Performs math operation.
     *
     * @param __pt The primitive type.
     * @param __mot The math operation type.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doMath(DataType __pt, MathType __mot)
        throws NullPointerException
    {
        if (__pt == null || __mot == null)
            throw new NullPointerException("NARG");
        
        // We may throw an exception here if we divide by zero!
        if (__pt == DataType.INTEGER || __pt == DataType.LONG)
            if (__mot == MathType.DIV || __mot == MathType.REM)
                this._canexception = true;
        
        // [a, b] -> [result]
        JavaStackResult result = this.state.stack.doStack(2,
            __pt.toJavaType());
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Perform the math
        this.handler.doMath(__pt.toStackJavaType(), __mot, result.in(0),
            result.in(1), result.out(0));
    }
    
    /**
     * Performs math operation with constant.
     *
     * @param __pt The primitive type.
     * @param __mot The math operation type.
     * @param __c The constant.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doMathConst(DataType __pt, MathType __mot, Number __c)
        throws NullPointerException
    {
        if (__pt == null || __mot == null || __c == null)
            throw new NullPointerException("NARG");
        
        // [a, b] -> [result]
        JavaStackResult result = this.state.stack.doStack(1,
            __pt.toJavaType());
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Monitor operation
        this.handler.doMath(__pt.toStackJavaType(), __mot, result.in(0),
            __c, result.out(0));
    }
    
    /**
     * Enters or exits the monitor.
     *
     * @param __enter If the monitor is to be entered.
     * @since 2019/04/16
     */
    private final void __doMonitor(boolean __enter)
    {
        // Can toss exception
        this._canexception = true;
        
        // [object] ->
        JavaStackResult result = this.state.stack.doStack(1);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Monitor operation
        this.handler.doMonitor(__enter, result.in(0));
    }
    
    /**
     * Allocate multi-dimensional array.
     *
     * @param __cl The class to allocate.
     * @param __dims The number of dimensions to allocate.
     * @throws NullPointerException On null arguments.
     * @since 2019/05/04
     */
    private final void __doMultiANewArray(ClassName __cl, int __dims)
        throws NullPointerException
    {
        if (__cl == null)
            throw new NullPointerException("NARG");
            
        // Can toss exception
        this._canexception = true;
        
        // [__dims, ...] -> [object]
        JavaStackResult result = this.state.stack.doStack(__dims,
            new JavaType(__cl));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle array creation
        this.handler.doMultiANewArray(__cl, __dims, result.out(0),
            result.in());
    }
    
    /**
     * Creates a new instance of the given class.
     *
     * @param __cn The class to create.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/04
     */
    private final void __doNew(ClassName __cn)
        throws NullPointerException
    {
        if (__cn == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // Just the type is pushed to the stack
        JavaStackResult result = this.state.stack.
            doStack(0, new JavaType(__cn));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doNew(__cn, result.out(0));
    }
    
    /**
     * Allocates a new array.
     *
     * @param __cn The component type of the array.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/05
     */
    private final void __doNewArray(ClassName __cn)
        throws NullPointerException
    {
        if (__cn == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // Add dimension to the class since it lacks it
        __cn = __cn.addDimensions(1);
        
        // [len] -> [array]
        JavaStackResult result = this.state.stack.
            doStack(1, new JavaType(__cn));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Generate
        this.handler.doNewArray(__cn, result.in(0), result.out(0));
    }
    
    /**
     * Do nothing.
     *
     * @since 2019/04/07
     */
    private final void __doNop()
    {
        // Just do nothing
        this.__update(this.state.stack.doNothing());
    }
    
    /**
     * Handles returning.
     *
     * @param __rt The type to return, {@code null} means nothing is to be
     * returned.
     * @since 2019/04/03
     */
    private final void __doReturn(JavaType __rt)
    {
        ByteCodeState state = this.state;
        
        // Destroy everything and bring destruction to the method!!!
        JavaStackResult result = state.stack.doDestroy(__rt != null);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Call handler
        this.handler.doReturn((__rt != null ? result.in(0) : null));
    }
    
    /**
     * Performs shuffling of the stack.
     *
     * @param __st The type of shuffle to do.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/04
     */
    private final void __doStackShuffle(JavaStackShuffleType __st)
        throws NullPointerException
    {
        if (__st == null)
            throw new NullPointerException("NARG");
        
        // Shuffle the stack
        JavaStackResult result = this.state.stack.doStackShuffle(__st);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Potentially run state operations as needed
        StateOperations ops = result.operations();
        if (!ops.isEmpty())
            this.handler.doStateOperations(ops);
    }
    
    /**
     * Reads a value from a static field.
     *
     * @param __fr The field reference.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doStaticGet(FieldReference __fr)
        throws NullPointerException
    {
        if (__fr == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // [] -> [value]
        ByteCodeState state = this.state;
        JavaStackResult result = state.stack.doStack(0,
            new JavaType(__fr.memberType()));
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doStaticGet(__fr, result.out(0));
    }
    
    /**
     * Writes to static field.
     *
     * @param __fr The field reference.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/13
     */
    private final void __doStaticPut(FieldReference __fr)
        throws NullPointerException
    {
        if (__fr == null)
            throw new NullPointerException("NARG");
        
        // An exception may be thrown
        this._canexception = true;
        
        // [value] ->
        ByteCodeState state = this.state;
        JavaStackResult result = state.stack.doStack(1);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Forward
        this.handler.doStaticPut(__fr, result.in(0));
    }
    
    /**
     * Stores an entry on the stack.
     *
     * @param __jt The type to pop.
     * @param __to The destination local.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/06
     */
    private final void __doStore(DataType __jt, int __to)
        throws NullPointerException
    {
        if (__jt == null)
            throw new NullPointerException("NARG");
        
        // Store onto the stack, locals are never cached
        JavaStackResult result = this.state.stack.doLocalStore(__to);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // There might be items on the stack which were cached and now no
        // longer are because they got pulverized, so de-cache those
        ByteCodeHandler handler = this.handler;
        handler.doStateOperations(result.operations());
        
        // Perform plain copy
        handler.doCopy(result.in(0), result.out(0));
    }
    
    /**
     * Performs a throw of an exception on the stack.
     *
     * @since 2019/04/05
     */
    private final void __doThrow()
    {
        // An exception will be thrown
        this._canexception = true;
        
        // Pop item from the stack
        JavaStackResult result = this.state.stack.doStack(1);
        this.__update(result);
        
        // Stop pre-processing here
        if (!this._dohandling)
            return;
        
        // Handle
        this.handler.doThrow(result.in(0));
    }
    
    /**
     * Updates the stack state and result.
     *
     * @param __jsr The stack result.
     * @throws NullPointerException On null arguments.
     * @since 2019/04/07
     */
    private final void __update(JavaStackResult __jsr)
        throws NullPointerException
    {
        if (__jsr == null)
            throw new NullPointerException("NARG");
        
        // The new stack state
        JavaStackState newstack = __jsr.after();
        
        // Needed for processing
        ByteCodeState state = this.state;
        Map<Integer, JavaStackState> stacks = state.stacks;
        int addr = state.addr;
        
        // Store result and the new stack
        state.result = __jsr;
        state.stack = newstack;
        
        // Can an exception handler be called?
        boolean canexception = this._canexception;
        state.canexception = canexception;
        
        // Target stack states are not touched in the normal handling state
        // because collisions and transitioning of states is handled in the
        // pre-processing step
        if (this._dohandling)
            return;
        
        // The result of the jump calculations may result in the stack
        // being poisoned potentially
        Map<Integer, StateOperations> stackpoison = state.stackpoison;
        Map<Integer, JavaStackEnqueueList> stackcollides = state.stackcollides;
        
        // Set target stack states for destinations of this instruction
        // Calculate the exception state only if it is needed
        JavaStackState hypoex = null;
        InstructionJumpTargets ijt = this._ijt;
        if (ijt != null && !ijt.isEmpty())
            for (int i = 0, n = ijt.size(); i < n; i++)
            {
                int jta = ijt.get(i).target();
                
                // If an exception is never thrown by the instruction being
                // processed then just ignore any exception points which may
                // be defined since they will have no effect
                boolean isexception = ijt.isException(i);
                if (!canexception && isexception)
                    continue;
                
                // Lazily calculate the exception handler since it might
                // not always be needed
                if (isexception && hypoex == null)
                    hypoex = newstack.doExceptionHandler(new JavaType(
                        new ClassName("java/lang/Throwable"))).after();
                
                // The type of stack to target
                JavaStackState use = (isexception ? hypoex : newstack);
                
                // Is empty state, use this state because we defined it first
                JavaStackState dss = stacks.get(jta);
                if (dss == null)
                    stacks.put(jta, use);
                
                // For later addresses which do not have an exact stack state
                // match, a partial un-cache will have to be used.
                // Note that jump backs are ignored here since those were
                // processed and we cannot adjust the states anymore
                else if (jta > addr && !use.canTransition(dss))
                {
                    // Debug
                    if (__Debug__.ENABLED)
                    {
                        todo.DEBUG.note("Transition is required! %d -> %d",
                            addr, jta);
                        todo.DEBUG.note("From: %s", use);
                        todo.DEBUG.note("To  : %s", dss);
                    }
                    
                    // Get pre-existing collision state here, if any
                    JavaStackEnqueueList preq = stackcollides.get(jta);
                    if (preq == null)
                        preq = new JavaStackEnqueueList(0);
                    
                    // Merge these two register lists
                    JavaStackEnqueueList mcol = JavaStackEnqueueList.merge(
                        preq, use.cacheCollision(dss));
                    
                    // Store the resulting collision
                    stacks.put(jta, (dss = dss.cacheClearState(mcol)));
                    
                    // Debug
                    if (__Debug__.ENABLED)
                        todo.DEBUG.note("Coll: %s", dss);
                }
            }
    }
}