SquirrelJME/SquirrelJME

View on GitHub
emulators/springcoat-vm/src/main/java/cc/squirreljme/vm/springcoat/DebugViewType.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.vm.springcoat;

import cc.squirreljme.jdwp.JDWPCommandException;
import cc.squirreljme.jdwp.JDWPLocalVariable;
import cc.squirreljme.jdwp.host.JDWPHostState;
import cc.squirreljme.jdwp.host.JDWPHostValue;
import cc.squirreljme.jdwp.host.trips.JDWPTripBreakpoint;
import cc.squirreljme.jdwp.host.views.JDWPViewType;
import cc.squirreljme.vm.springcoat.exceptions.SpringNoSuchFieldException;
import cc.squirreljme.vm.springcoat.exceptions.SpringNoSuchMethodException;
import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.multiphasicapps.classfile.ByteCode;
import net.multiphasicapps.classfile.ConstantValueString;
import net.multiphasicapps.classfile.LocalVariableInfo;
import net.multiphasicapps.classfile.LocalVariableTable;
import net.multiphasicapps.classfile.StackMapTable;
import net.multiphasicapps.classfile.StackMapTableEntry;
import net.multiphasicapps.classfile.StackMapTableState;

/**
 * A viewer around class types.
 *
 * @since 2021/04/10
 */
public class DebugViewType
    implements JDWPViewType
{
    /** The state of the debugger. */
    protected final Reference<JDWPHostState> state;
    
    /**
     * Initializes the type viewer.
     * 
     * @param __state The state.
     * @throws NullPointerException On null arguments.
     * @since 2021/04/10
     */
    public DebugViewType(Reference<JDWPHostState> __state)
    {
        if (__state == null)
            throw new NullPointerException("NARG");
        
        this.state = __state;
    }
    
    /**
     * {@inheritDoc}
     * @since 2022/08/28
     */
    @Override
    public boolean canCastTo(Object __fromWhich, Object __toWhich)
    {
        SpringClass from = DebugViewType.__class(__fromWhich);
        SpringClass to = DebugViewType.__class(__toWhich);
        
        return to.isAssignableFrom(from);
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/20
     */
    @Override
    public Object classLoader(Object __which)
    {
        return DebugViewType.__class(__which).classLoader();
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/20
     */
    @Override
    public int constantPoolCount(Object __which)
    {
        // Is this a valid class with a pool?
        SpringClass classy = DebugViewType.__class(__which);
        if (classy.file == null || classy.isArray() ||
            classy.isPrimitive())
            return -1;
        
        return classy.file.pool().size();
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/20
     */
    @Override
    public byte[] constantPoolRaw(Object __which)
    {
        // Is this a valid class with a pool?
        SpringClass classy = DebugViewType.__class(__which);
        if (classy.file == null || classy.isArray() ||
            classy.isPrimitive())
            return null;
        
        return classy.file.pool().rawData();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/11
     */
    @Override
    public Object componentType(Object __which)
    {
        return DebugViewType.__class(__which).componentType();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public int fieldFlags(Object __which, int __fieldDx)
    {
        return DebugViewType.__field(__which, __fieldDx).flags().toJavaBits();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public String fieldName(Object __which, int __fieldDx)
    {
        return DebugViewType.__field(__which, __fieldDx).nameAndType()
            .name().toString();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public String fieldSignature(Object __which, int __fieldDx)
    {
        return DebugViewType.__field(__which, __fieldDx).nameAndType()
            .type().toString();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/30
     */
    @Override
    public void fieldWatch(Object __which, int __fieldDx, boolean __write)
    {
        // Set watching on the given field
        SpringField field = DebugViewType.__field(__which, __fieldDx);
        if (__write)
            field._watchWrite = true;
        else
            field._watchRead = true;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/19
     */
    @Override
    public Object instance(Object __which)
    {
        try
        {
            return DebugViewType.__class(__which).classObject();
        }
        catch (IllegalStateException ignored)
        {
            return null;
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public int[] fields(Object __which)
    {
        SpringClass type = DebugViewType.__class(__which);
        SpringField[] fields = type.fieldLookup();
        
        // Use base field since we only care about our own fields
        int base = type._fieldLookupBase;
        
        // Get these field IDs
        int n = fields.length - base;
        int[] rv = new int[n];
        for (int i = 0; i < n; i++)
            rv[i] = fields[base + i].index;
        
        return rv;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/11
     */
    @Override
    public int flags(Object __which)
    {
        return DebugViewType.__class(__which).flags().toJavaBits();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public Object[] interfaceTypes(Object __which)
    {
        return DebugViewType.__class(__which).interfaceClasses();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/10
     */
    @Override
    public boolean isValid(Object __which)
    {
        return (__which instanceof SpringClass);
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public boolean isValidField(Object __which, int __fieldDx)
    {
        try
        {
            DebugViewType.__field(__which, __fieldDx);
            return true;
        }
        catch (SpringNoSuchFieldException|JDWPCommandException ignored)
        {
            return false;
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public boolean isValidMethod(Object __which, int __methodDx)
    {
        try
        {
            DebugViewType.__method(__which, __methodDx);
            return true;
        }
        catch (SpringNoSuchMethodException|JDWPCommandException ignored)
        {
            return false;
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/25
     */
    @Override
    public void methodBreakpoint(Object __which, int __methodDx, int __codeDx,
        JDWPTripBreakpoint __trip)
    {
        SpringMethod method = DebugViewType.__method(__which, __methodDx);
        
        // Return the method byte code, so we can get the true PC address
        // If there is no byte code then we just ignore
        ByteCode byteCode = method.byteCode();
        if (byteCode == null)
            return;
        
        // Obtain the breakpoint map
        Map<Integer, JDWPTripBreakpoint> breakpoints =
            method.__breakpoints(true);
        
        // Lock for thread safety
        synchronized (breakpoints)
        {
            breakpoints.put(byteCode.indexToAddress(__codeDx), __trip);
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public byte[] methodByteCode(Object __which, int __methodDx)
    {
        // If there is no method byte code then ignore
        ByteCode byteCode = DebugViewType.__method(__which, __methodDx)
            .method.byteCode();
        if (byteCode == null)
            return null;
        
        return byteCode.rawByteCode();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public int methodFlags(Object __which, int __methodDx)
    {
        return DebugViewType.__method(__which, __methodDx)
            .flags().toJavaBits();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public int[] methodLineTable(Object __which, int __methodDx)
    {
        SpringMethod springMethod = DebugViewType.__method(__which,
            __methodDx);
        
        // Pre-cached?
        int[] lineTable = springMethod._lineTable;
        if (lineTable != null)
            return lineTable.clone();
        
        // If there is no method byte code then ignore
        ByteCode byteCode = springMethod.method.byteCode();
        if (byteCode == null)
            return null;
        
        // Otherwise map each unique address to a line number
        int[] addrs = byteCode.validAddresses();
        int n = addrs.length;
        int[] rv = new int[n];
        for (int i = 0; i < n; i++)
            rv[i] = byteCode.lineOfAddress(addrs[i]);
        
        // Cache it and return a safe copy of it
        springMethod._lineTable = rv;
        return rv.clone();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public int methodLocationCount(Object __which, int __methodDx)
    {
        // If there is no method byte code then ignore
        ByteCode byteCode = DebugViewType.__method(__which, __methodDx)
            .method.byteCode();
        if (byteCode != null)
            return byteCode.instructionCount();
        
        // No locations are valid!
        return -1;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public String methodName(Object __which, int __methodDx)
    {
        return DebugViewType.__method(__which, __methodDx).name().toString();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public String methodSignature(Object __which, int __methodDx)
    {
        return DebugViewType.__method(__which, __methodDx).nameAndType()
            .type().toString();
    }
    
    /**
     * {@inheritDoc}
     * @since 2022/09/21
     */
    @Override
    public JDWPLocalVariable[] methodVariableTable(Object __which,
        int __methodDx)
    {
        SpringMethod method = DebugViewType.__method(__which,
            __methodDx);
        
        // If there is no byte code then this is likely a native method
        ByteCode byteCode = method.method.byteCode();
        if (byteCode == null)
            return null;
        
        // Resultant builder
        List<JDWPLocalVariable> result = new ArrayList<>();
        
        // We can use both of these as a source for variables, although the
        // StackMapTable is not precise
        LocalVariableTable localVariables = byteCode.localVariables();
        StackMapTable stackMap = byteCode.stackMapTable();
        
        // Build from debugged local variables
        if (localVariables != null && localVariables.size() > 0)
        {
            // Direct copy over
            for (LocalVariableInfo var : localVariables)
                result.add(new JDWPLocalVariable(var.slot,
                    var.startPc, var.length, var.type.toString(),
                    var.name.toString()));
        }
        
        // Otherwise, make up something based on the stack map
        else if (stackMap != null)
        {
            // Needed to determine the end of the table
            int codeLen = byteCode.length();
            
            // Determine gap lengths for each entry, so we can spread values
            // around accordingly
            Map<Integer, Integer> atLengths = new LinkedHashMap<>();
            int lastAt = 0;
            for (Map.Entry<Integer, StackMapTableState> entry : stackMap)
            {
                int at = entry.getKey();
                
                // Put in the length to the last one
                Integer old = atLengths.get(lastAt);
                atLengths.put(lastAt, (old == null ? 0 : old) + (at - lastAt));
                
                // New position
                lastAt = at;
            }
            
            // The final length is
            atLengths.put(lastAt, codeLen - lastAt);
            
            // Go through and add entries for these
            for (Map.Entry<Integer, StackMapTableState> entry : stackMap)
            {
                int at = entry.getKey();
                StackMapTableState state = entry.getValue();
                
                // We need to actually go through each slot in this state
                // to get the correct information
                int maxLocals = state.maxLocals();
                for (int slot = 0; slot < maxLocals; slot++)
                {
                    StackMapTableEntry local = state.getLocal(slot);
                    
                    // Ignore uninitialized values as they have nothing that
                    // can be stored there
                    // We also do not know what top variables are
                    if (!local.isInitialized() || local.isTop())
                        continue;
                    
                    // Store this, assume 0 is always this
                    result.add(new JDWPLocalVariable(
                        slot, at, atLengths.get(at),
                        local.type().type().toString(),
                        (slot == 0 ? "this" :
                            String.format("var$%d@%d", slot, at))));
                }
            }
        }
        
        return result.toArray(new JDWPLocalVariable[result.size()]);
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public int[] methods(Object __which)
    {
        SpringClass type = DebugViewType.__class(__which);
        SpringMethod[] methods = type.methodLookup();
        
        // Get base for this method, we do not want inherited methods!
        int base = type._methodLookupBase;
        
        // Only get our own methods
        int n = methods.length - base;
        int[] rv = new int[n];
        for (int i = 0; i < n; i++)
            rv[i] = methods[base + i].methodIndex;
        
        return rv;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/14
     */
    @Override
    public boolean readValue(Object __which, int __index, JDWPHostValue __out)
    {
        SpringClass classy = DebugViewType.__class(__which);
        
        // Get the static field storage for the class
        SpringFieldStorage[] store = classy._staticFields;
        if (__index >= classy._staticFieldBase && __index < store.length)
            return DebugViewType.__readValue(__out, store[__index],
                classy.classLoader().machine());
        
        // Not a valid static field
        return false;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/11
     */
    @Override
    public String signature(Object __which)
    {
        return DebugViewType.__class(__which).name.field().toString();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/15
     */
    @Override
    public String sourceFile(Object __which)
    {
        return DebugViewType.__class(__which).file.sourceFile();
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/12
     */
    @Override
    public Object superType(Object __which)
    {
        return DebugViewType.__class(__which).superclass;
    }
    
    /**
     * {@inheritDoc}
     * @since 2022/09/01
     */
    @Override
    public Object typeOfClassInstance(Object __object)
    {
        // Invalid?
        __object = DebugViewObject.__normalizeNull(__object);
        if (!(__object instanceof SpringSimpleObject))
            return null;
        
        // Not Class object?
        SpringSimpleObject object = (SpringSimpleObject)__object;
        if (!"java/lang/Class".equals(object.type().name.toString()))
            return null;
        
        // Lookup the field for the type
        SpringFieldStorage field = object.fieldByNameAndType(false,
            "_type",
            "Lcc/squirreljme/jvm/mle/brackets/TypeBracket;");
        if (field == null)
            return null;
        
        // Read the value here
        return MLEType.__type(field.get()).type();
    }
    
    /**
     * Gets the class from the given value.
     * 
     * @param __which Which to convert from.
     * @return The spring class of the given type.
     * @since 2021/04/15
     */
    private static SpringClass __class(Object __which)
    {
        // Missing the class?
        if (__which == null)
            throw JDWPCommandException.tossInvalidClass(__which, null);
        
        // Return cast form of it
        try
        {
            return ((SpringClass)__which);
        }
        catch (ClassCastException e)
        {
            throw JDWPCommandException.tossInvalidClass(__which, e);
        }
    }
    
    /**
     * Looks up the given field.
     * 
     * @param __which Which class to read from.
     * @param __fieldDx The field index.
     * @return The method for the given index.
     * @since 2021/04/16
     */
    private static SpringField __field(Object __which, int __fieldDx)
    {
        try
        {
            return DebugViewType.__class(__which).lookupField(__fieldDx);
        }
        catch (SpringNoSuchMethodException e)
        {
            throw JDWPCommandException.tossInvalidField(
                __which, __fieldDx, e);
        }
    }
    
    /**
     * Looks up the given method.
     * 
     * @param __which Which class to read from.
     * @param __methodDx The method index.
     * @return The method for the given index.
     * @since 2021/04/15
     */
    private static SpringMethod __method(Object __which, int __methodDx)
    {
        try
        {
            return DebugViewType.__class(__which).lookupMethod(__methodDx);
        }
        catch (SpringNoSuchMethodException e)
        {
            throw JDWPCommandException.tossInvalidMethod(
                __which, __methodDx, e);
        }
    }
    
    /**
     * Reads from storage into the output value.
     * 
     * @param __out The output value.
     * @param __store The field storage.
     * @param __machine The machine used for potential object manipulation.
     * @return {@code true} on success.
     * @since 2022/09/01
     */
    static boolean __readValue(JDWPHostValue __out, SpringFieldStorage __store,
        SpringMachine __machine)
    {
        Object value = __store.get();
        
        // If this is a string, we need to intern it for the debugger otherwise
        // it will fail
        if ((value instanceof String) ||
            (value instanceof ConstantValueString))
            try (CallbackThread callback = __machine.obtainCallbackThread(
                true))
            {
                value = callback.thread()._worker
                    .asVMObject(new ConstantValueString(value.toString()));
            }
        
        __out.set(DebugViewObject.__normalizeNull(value));
        
        return true;
    }
}