SquirrelJME/SquirrelJME

View on GitHub
tools/squirreljme-debugger/src/main/java/cc/squirreljme/debugger/InfoFrame.java

Summary

Maintainability
A
0 mins
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 Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.debugger;

import cc.squirreljme.jdwp.JDWPCommandSet;
import cc.squirreljme.jdwp.JDWPCommandSetStackFrame;
import cc.squirreljme.jdwp.JDWPErrorType;
import cc.squirreljme.jdwp.JDWPId;
import cc.squirreljme.jdwp.JDWPPacket;
import cc.squirreljme.jdwp.JDWPValueTag;

/**
 * Tracks information on a single frame within a thread.
 *
 * @since 2024/01/25
 */
public class InfoFrame
    extends Info
{
    /** Possible tags used. */
    private static final JDWPValueTag[] _TAGS =
        JDWPValueTag.values();
    
    /** The thread this frame is in. */
    protected final InfoThread inThread;
    
    /** The location of the frame. */
    protected final FrameLocation location;
    
    /** Frame variables. */
    protected final KnownValue<InfoFrameLocals> variables;
    
    /**
     * Initializes the frame information.
     *
     * @param __state The debugger state.
     * @param __id The ID number of this info.
     * @param __thread The thread this is in.
     * @param __location The location of this frame.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/25
     */
    public InfoFrame(DebuggerState __state, JDWPId __id, InfoThread __thread,
        FrameLocation __location)
        throws NullPointerException
    {
        super(__state, __id, InfoKind.FRAME);
        
        if (__thread == null || __location == null)
            throw new NullPointerException("NARG");
        
        this.inThread = __thread;
        this.location = __location;
        this.variables = new KnownValue<InfoFrameLocals>(InfoFrameLocals.class,
            this::__updateVariables);
    }
    
    /**
     * Returns the current method.
     *
     * @return The current method this is in.
     * @since 2024/01/25
     */
    public InfoMethod inMethod()
    {
        return this.location.inMethod;
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/25
     */
    @Override
    protected boolean internalUpdate(DebuggerState __state)
        throws NullPointerException
    {
        return true;
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/25
     */
    @Override
    protected String internalString()
    {
        DebuggerState state = this.internalState();
        InfoMethod inMethod = this.location.inMethod;
        return String.format("%s:%s @ %d",
            (inMethod == null ? "<opaque>" :
                inMethod.name.getOrUpdateSync(state)),
            (inMethod == null ? "<opaque>" :
                inMethod.type.getOrUpdateSync(state)),
            this.location.index);
    }
    
    /**
     * Attempts variable update state and chains accordingly on failure.
     *
     * @param __state The state to update within.
     * @param __inThread The thread this is in.
     * @param __inFrame The frame this is in.
     * @param __value The resultant values.
     * @param __locals The local variable.
     * @param __varIndex The local variable index.
     * @param __tagType The tag type to request.
     * @param __varLimit The variable limit.
     * @param __sync The callback called when the variable has been updated.
     * @since 2024/01/26
     */
    private void __updateChain(DebuggerState __state, JDWPId __inThread,
        JDWPId __inFrame, KnownValue<InfoFrameLocals> __value,
        InfoFrameLocals __locals, int __varIndex, int __tagType,
        int __varLimit, KnownValueCallback<InfoFrameLocals> __sync)
    {
        // Determine which tag we are on
        JDWPValueTag[] tags = InfoFrame._TAGS;
        if (__tagType >= tags.length)
            return;
        JDWPValueTag tag = tags[__tagType];
        
        // Request variables
        try (JDWPPacket out = __state.request(JDWPCommandSet.STACK_FRAMES,
            JDWPCommandSetStackFrame.GET_VALUES))
        {
            // Current frame and thread
            out.writeId(__inThread);
            out.writeId(__inFrame);
            
            // Request a single slot with the tag
            out.writeInt(1);
            out.writeInt(__varIndex);
            out.writeByte(tag.tag);
            
            // Send it
            __state.sendKnown(out, __value, __sync,
                (__ignored, __reply) -> {
                    // Only read and set the first value found
                    int numValues = __reply.readInt();
                    if (numValues > 0)
                        __locals.set(__varIndex, __reply.readValue());
                    
                    // We got the value here, so move onto the next local
                    // variable index
                    if (__varIndex < InfoFrameLocals.MAX_LOCALS - 1)
                        this.__updateChain(__state, __inThread, __inFrame,
                            __value, __locals, __varIndex + 1,
                            0,
                            Math.min(InfoFrameLocals.MAX_LOCALS,
                                __varIndex + InfoFrameLocals._LOCAL_BUMP),
                            __sync);
                }, (__ignored, __reply) -> {
                    // If the slot is invalid, do not go down the chain
                    // to any more variables
                    if (__reply.hasError(JDWPErrorType.INVALID_SLOT))
                    {
                        // We do not want to get stuck on a single slot so
                        // try to skip it
                        if (__varIndex < __varLimit)
                            this.__updateChain(__state, __inThread, __inFrame,
                                __value, __locals, __varIndex + 1,
                                0, __varLimit, __sync);
                        return;
                    }
                    
                    // When updating the chain keep the current variable limit
                    this.__updateChain(__state, __inThread, __inFrame,
                        __value, __locals, __varIndex, __tagType + 1,
                        __varLimit, __sync);
                });
        }
    }
    
    /**
     * Updates variables.
     *
     * @param __state The current state.
     * @param __value The current value.
     * @param __sync The callback to execute.
     * @since 2024/01/26
     */
    private void __updateVariables(DebuggerState __state,
        KnownValue<InfoFrameLocals> __value,
        KnownValueCallback<InfoFrameLocals> __sync)
    {
        // Get the locals to update
        InfoFrameLocals locals = __value.get();
        if (locals == null)
        {
            locals = new InfoFrameLocals();
            __value.set(locals);
        }
        
        // Get current thread and frame IDs
        JDWPId inThread = this.inThread.id;
        JDWPId inFrame = this.id;
        
        // We need to ask the virtual machine for a ton of information to get
        // the proper local variables... This is a very expensive operation
        // so to reduce load this sequentially obtains the variables up
        // until it cannot any further
        this.__updateChain(__state, inThread, inFrame, __value,
            locals, 0, 0, InfoFrameLocals._LOCAL_BUMP,
            __sync);
    }
}