SquirrelJME/SquirrelJME

View on GitHub
modules/debug-jdwp-vm-host/src/main/java/cc/squirreljme/jdwp/host/JDWPHostCommandSetMethod.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- 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 cc.squirreljme.jdwp.host;

import cc.squirreljme.jdwp.JDWPCommand;
import cc.squirreljme.jdwp.JDWPCommandSetMethod;
import cc.squirreljme.jdwp.JDWPErrorType;
import cc.squirreljme.jdwp.JDWPException;
import cc.squirreljme.jdwp.JDWPLocalVariable;
import cc.squirreljme.jdwp.JDWPPacket;
import cc.squirreljme.jdwp.host.views.JDWPViewType;
import net.multiphasicapps.classfile.MethodDescriptor;

/**
 * Method command set.
 *
 * @since 2021/03/14
 */
public enum JDWPHostCommandSetMethod
    implements JDWPCommandHandler
{
    /** Line number table. */
    LINE_TABLE(JDWPCommandSetMethod.LINE_TABLE)
    {
        /**
         * {@inheritDoc}
         * @since 2021/03/14
         */
        @Override
        public JDWPPacket execute(JDWPHostController __controller,
            JDWPPacket __packet)
            throws JDWPException
        {
            // Read class and the method it is in
            Object classy = __controller.readType(__packet, false);
            int methodId = __packet.readId();
            
            // Not a valid method?
            JDWPViewType viewType = __controller.viewType();
            if (!viewType.isValidMethod(classy, methodId))
                throw JDWPErrorType.INVALID_METHOD_ID.toss(classy, methodId);
            
            JDWPPacket rv = __controller.reply(
                __packet.id(), JDWPErrorType.NO_ERROR);
                
            // Put down all the valid indexes in the method, even if there
            // are no possible lines we may still want to break at specific
            // byte code addresses even without the lines?
            long addrCount = viewType.methodLocationCount(classy, methodId);
            rv.writeLong(0);
            rv.writeLong(Math.max(-1, addrCount));
            
            // Obtain the line table to record, ensure that it exists and is
            // valid. If this method has no byte code (abstract/native?),
            // then do nothing.
            int[] lineTable = viewType.methodLineTable(classy, methodId);
            if (addrCount <= 0 || lineTable == null ||
                lineTable.length == 0 || lineTable[0] < 0)
            {
                // No information, so there are no lines
                rv.writeInt(0);
            }
            
            // Otherwise record the information
            else
            {
                // Shrink the table so less information is given
                int countedLines = 0;
                int[] paddedPCs = new int[lineTable.length];
                int[] paddedLines = new int[lineTable.length];
                for (int i = 0, n = lineTable.length, lastLine = -1;
                    i < n; i++)
                {
                    // If the line has changed, record it
                    int at = lineTable[i];
                    if (i == 0 || at != lastLine)
                    {
                        paddedPCs[countedLines] = i;
                        paddedLines[countedLines++] = at;
                        
                        // Do not repeat this line
                        lastLine = at;
                    }
                }
                
                // Write out the condensed line table
                rv.writeInt(countedLines);
                for (int i = 0, n = countedLines; i < n; i++)
                {
                    rv.writeLong(paddedPCs[i]);
                    rv.writeInt(Math.max(0, paddedLines[i]));
                }
            }
            
            return rv;
        }
    },
    
    /** Variable table without generics. */
    VARIABLE_TABLE(JDWPCommandSetMethod.VARIABLE_TABLE)
    {
        /**
         * {@inheritDoc}
         * @since 2021/04/30
         */
        @Override
        public JDWPPacket execute(JDWPHostController __controller,
            JDWPPacket __packet)
            throws JDWPException
        {
            return this.__variables(false, __controller, __packet);
        }
    },
    
    /** Method byte code. */
    BYTE_CODES(JDWPCommandSetMethod.BYTE_CODES)
    {
        /**
         * {@inheritDoc}
         * @since 2021/03/21
         */
        @Override
        public JDWPPacket execute(JDWPHostController __controller,
            JDWPPacket __packet)
            throws JDWPException
        {
            // Read class and the method it is in
            Object classy = __controller.readType(__packet, false);
            int methodId = __packet.readId();
            
            // Not a valid method?
            JDWPViewType viewType = __controller.viewType();
            if (!viewType.isValidMethod(classy, methodId))
                throw JDWPErrorType.INVALID_METHOD_ID.toss(classy, methodId);
            
            // Absent information is not returned normally, but in this case
            // return it
            byte[] byteCode = viewType.methodByteCode(classy, methodId);
            if (byteCode == null)
                throw JDWPErrorType.ABSENT_INFORMATION.toss(classy, methodId);
            
            JDWPPacket rv = __controller.reply(
                __packet.id(), JDWPErrorType.NO_ERROR);
            
            // Dump the byte code
            rv.writeInt(byteCode.length);
            rv.write(byteCode, 0, byteCode.length);
            
            return rv;
        }
    },
    
    /** Variable table with generics. */
    VARIABLE_TABLE_WITH_GENERIC(JDWPCommandSetMethod.VARIABLE_TABLE_WITH_GENERIC)
    {
        /**
         * {@inheritDoc}
         * @since 2021/03/15
         */
        @Override
        public JDWPPacket execute(JDWPHostController __controller,
            JDWPPacket __packet)
            throws JDWPException
        {
            return this.__variables(true, __controller, __packet);
        }
    },
        
    /* End. */
    ;
    
    /** The base command. */
    public final JDWPCommand command;
    
    /** The ID of the packet. */
    public final int id;
    
    /**
     * Returns the ID used.
     * 
     * @param __id The ID used.
     * @since 2021/03/14
     */
    JDWPHostCommandSetMethod(JDWPCommand __id)
    {
        this.command = __id;
        this.id = __id.debuggerId();
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/23
     */
    @Override
    public final JDWPCommand command()
    {
        return this.command;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/03/14
     */
    @Override
    public final int debuggerId()
    {
        return this.id;
    }
    
    /**
     * Handles standard or generic variable table information.
     * 
     * @param __generic Write generic variable tables?
     * @param __controller The controller used.
     * @param __packet The packet to read from.
     * @return The resultant packet.
     * @throws JDWPException If this is not valid.
     * @since 2021/04/30
     */
    JDWPPacket __variables(boolean __generic, JDWPHostController __controller,
        JDWPPacket __packet)
        throws JDWPException
    {
        // Read class and the method it is in
        Object classy = __controller.readType(__packet, false);
        int methodId = __packet.readId();
        
        // Not a valid method?
        JDWPViewType viewType = __controller.viewType();
        if (!viewType.isValidMethod(classy, methodId))
            throw JDWPErrorType.INVALID_METHOD_ID.toss(classy, methodId);
        
        // Get the variable table, if missing then ignore it
        JDWPLocalVariable[] variables = viewType.methodVariableTable(classy,
            methodId);
        if (variables == null || variables.length <= 0)
            return __controller.reply(
                __packet.id(), JDWPErrorType.ABSENT_INFORMATION);
        
        // Setup packet
        JDWPPacket rv = __controller.reply(
            __packet.id(), JDWPErrorType.NO_ERROR);
        
        // Write down the number of argument slots
        MethodDescriptor desc = new MethodDescriptor(
            viewType.methodSignature(classy, methodId));
        rv.writeInt(desc.argumentSlotCount());
        
        // Write down everything we know
        int count = variables.length;
        rv.writeInt(count);
        for (int i = 0; i < count; i++)
        {
            JDWPLocalVariable variable = variables[i];
            
            rv.writeLong(variable.startPc);
            rv.writeString(variable.variableName);
            rv.writeString(variable.fieldDescriptor);
            
            // No generics are used
            if (__generic)
                rv.writeString("");
            
            rv.writeInt(variable.length);
            rv.writeInt(variable.localSlot);
        }
        
        return rv;
    }
}