SquirrelJME/SquirrelJME

View on GitHub
modules/debug-jdwp/src/main/java/cc/squirreljme/jdwp/event/EventFilter.java

Summary

Maintainability
C
1 day
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// 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 cc.squirreljme.jdwp.event;

import cc.squirreljme.jdwp.EventKind;
import cc.squirreljme.jdwp.JDWPController;
import cc.squirreljme.jdwp.JDWPLocation;
import cc.squirreljme.jdwp.JDWPStepTracker;
import cc.squirreljme.jdwp.JDWPUtils;
import cc.squirreljme.jdwp.JDWPValue;
import cc.squirreljme.jdwp.views.JDWPViewType;
import cc.squirreljme.runtime.cldc.debug.Debugging;

/**
 * This class is responsible for being a filter on any events that occur.
 *
 * @since 2021/04/17
 */
public final class EventFilter
{
    /** Static field. */
    private static final byte _STATIC_FIELD =
        0x08;
    
    /** The execution location. */
    public final JDWPLocation location;
    
    /** The call stack stepping. */
    public final CallStackStepping callStackStepping;
    
    /** Which exceptions does this fire on? */
    protected final ExceptionOnly exception;
    
    /** Exclude the given class? */
    protected final ClassPatternMatcher excludeClass;
    
    /** Only on a specific field? */
    public final FieldOnly fieldOnly;
    
    /** Include the given class? */
    protected final ClassPatternMatcher includeClass;
    
    /** The instance to check on, may be {@code null}. */
    protected final Object thisInstance;
    
    /** Is this instance set? */
    protected final boolean thisInstanceSet;
    
    /** The thread to check on. */
    public final Object thread;
    
    /** The type of class to check on. */
    protected final Object type;
    
    /**
     * Initializes the event filter.
     * 
     * @param __thread The thread.
     * @param __type The type.
     * @param __includeClass The class to include.
     * @param __excludeClass The class to exclude.
     * @param __fieldOnly Only on this field.
     * @param __location Only at this location.
     * @param __thisInstanceSet Is this instance set?
     * @param __thisInstance Only for the given instance.
     * @param __exception Only for the given exception.
     * @param __callStackStepping Call stepping.
     * @since 2021/04/17
     */
    public EventFilter(Object __thread, Object __type,
        ClassPatternMatcher __includeClass, ClassPatternMatcher __excludeClass,
        FieldOnly __fieldOnly, JDWPLocation __location,
        boolean __thisInstanceSet, Object __thisInstance,
        ExceptionOnly __exception, CallStackStepping __callStackStepping)
    {
        this.thread = __thread;
        this.type = __type;
        this.includeClass = __includeClass;
        this.excludeClass = __excludeClass;
        this.fieldOnly = __fieldOnly;
        this.location = __location;
        this.thisInstance = (__thisInstanceSet ? __thisInstance : null);
        this.thisInstanceSet = __thisInstanceSet;
        this.exception = __exception;
        this.callStackStepping = __callStackStepping;
    }
    
    /**
     * Does this have type matching?
     * 
     * @return If this has type matching?
     * @since 2021/04/25
     */
    public boolean hasTypeMatch()
    {
        return this.type != null ||
            this.includeClass != null ||
            this.excludeClass != null;
    }
    
    /**
     * Checks if this filter meets the given criteria for the event.
     * 
     * @param __controller The controller used.
     * @param __thread The thread where this is coming from.
     * @param __kind The kind of event.
     * @param __args The arguments to the event.
     * @return If this meets or not.
     * @since 2021/04/18
     */
    public boolean meets(JDWPController __controller, Object __thread,
        EventKind __kind, Object... __args)
        throws NullPointerException
    {
        if (__controller == null || __kind == null)
            throw new NullPointerException("NARG");
        
        // Check the general context for mis-matches
        for (EventModContext context : __kind.contextGeneral())
            if (!this.__context(__controller, __thread, context, null))
                return false;
        
        // Handle each argument and find mismatches
        for (int i = 0, n = __args.length; i < n; i++)
        {
            // Check that there is context here 
            EventModContext context = __kind.contextArgument(i);
            if (context == null)
                continue;
            
            // Check the context if it is valid
            if (!this.__context(__controller, __thread, context, __args[i]))
                return false;
        }
        
        // If this was reached then would have all been matched
        return true;
    }
    
    /**
     * Checks if this meets the given type.
     * 
     * @param __viewType The type viewer.
     * @param __arg The argument.
     * @return If this meets the given type.
     * @throws NullPointerException On null arguments.
     * @since 2021/04/18
     */
    public boolean meetsType(JDWPViewType __viewType, Object __arg)
        throws NullPointerException
    {
        if (__viewType == null)
            throw new NullPointerException("NARG");
        
        // Mismatched type?
        Object type = this.type;
        if (type != null && type != __arg)
            return false;
        
        // Not an included class?
        ClassPatternMatcher includeClass = this.includeClass;
        ClassPatternMatcher excludeClass = this.excludeClass;
        if (includeClass != null || excludeClass != null)
        {
            // Get the runtime name of the class
            String runtimeName = JDWPUtils.signatureToRuntime(
                __viewType.signature(__arg));
            
            // Is not an included class?
            if (includeClass != null && !includeClass.meets(runtimeName))
                return false;
            
            // Is an excluded class?
            return excludeClass == null || !excludeClass.meets(runtimeName);
        }
        
        // If not failed, this meets!
        return true;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/04/18
     */
    @Override
    public String toString()
    {
        return String.format(
            "EventFilter{callStackStepping=%s, " +
            "exception=%s, excludeClass=%s, " +
            "fieldOnly=%s, " +
            "includeClass=%s, " +
            "location=%s, " +
            "thisInstance=%s, " +
            "thread=%s, " +
            "type=%s}", this.callStackStepping, this.exception,
            this.excludeClass, this.fieldOnly, this.includeClass,
            this.location, this.thisInstance, this.thread, this.type);
    }
    
    /**
     * Checks the context.
     * 
     * @param __controller The controller used.
     * @param __thread The current thread.
     * @param __context The context to check for.
     * @param __on The object to test on.
     * @return If this is mismatched.
     * @throws NullPointerException On null arguments.
     * @since 2021/04/25
     */
    private boolean __context(JDWPController __controller, Object __thread,
        EventModContext __context, Object __on)
        throws NullPointerException
    {
        if (__controller == null || __context == null)
            throw new NullPointerException("NARG");
        
        // Viewers
        JDWPViewType viewType = __controller.viewType();
        
        // Depends on the context
        switch (__context)
        {
                // The current thread
            case CURRENT_THREAD:
                // Thread is only valid if it is set
                if (this.thread != null && __thread != this.thread)
                    return false;
                break;
                
                // Current location in code
            case CURRENT_LOCATION:
                if (this.location != null)
                {
                    JDWPLocation location = this.location;
                    
                    // With no current thread, we have no idea where we are
                    // even located
                    if (__thread == null)
                        return false;
                    
                    // Is not the same location?
                    if (!location.equals(__controller.locationOf(__thread)))
                        return false;
                }
                break;
                
                // Current type being called in the class
            case CURRENT_TYPE:
                if (this.hasTypeMatch())
                {
                    // If no thread is available, we have no idea where we are
                    if (__thread == null)
                        return false;
                    
                    // We are at the wrong location for this?
                    Object type = __controller.locationOf(__thread).type;
                    if (!viewType.isValid(type) ||
                        !this.meetsType(viewType, type))
                        return false;
                }
                break;
                
                // Current this type
            case CURRENT_INSTANCE:
                if (this.thisInstanceSet)
                {
                    // If no thread is available, we have no idea where we are
                    if (__thread == null)
                        return false;
                    
                    // Is this method static? Determines if we match the
                    // instance or not
                    JDWPLocation location = __controller.locationOf(
                        __thread);
                    boolean isStatic = (__controller.viewType()
                        .methodFlags(location.type, location.methodDx) &
                            EventFilter._STATIC_FIELD) != 0;
                        
                    // Is this a static method?
                    Object thisInstance = this.thisInstance;
                    if (isStatic)
                    {
                        // If this instance is not-null then we can never match
                        // a static method
                        if (null != thisInstance)
                            return false;
                    }
                    
                    // Otherwise for instance methods we need to do much more
                    // work to get the this object
                    else
                    {
                        // No available frames?
                        Object[] frames = __controller.viewThread().frames(
                            __thread);
                        if (frames == null || frames.length == 0)
                            return false;
                        
                        // Get the this from the current frame
                        Object frame = frames[0];
                        try (JDWPValue val = __controller.value())
                        {
                            // Read in the value
                            if (!__controller.viewFrame()
                                .readValue(frame, 0, val))
                                val.set(null);
                            
                            // Is this the wrong object?
                            if (val.get() != thisInstance)
                                return false;
                        }
                    }
                }
                break;
                
                // A field parameter
            case PARAMETER_FIELD:
                if (this.fieldOnly != null)
                {
                    // Only works on number parameters
                    if (!(__on instanceof Number))
                        return false;
                        
                    // Is this the wrong field?
                    FieldOnly fieldOnly = this.fieldOnly;
                    if (fieldOnly.fieldDx != ((Number)__on).intValue())
                        return false;
                }
                break;
                
                // Parameter based on a type or field
            case PARAMETER_TYPE_OR_FIELD:
                if (this.fieldOnly != null)
                {
                    // Is this the wrong field?
                    FieldOnly fieldOnly = this.fieldOnly;
                    if (fieldOnly.type != __on)
                        return false;
                }
                
                // Forward to type check and see if that is used
                if (!this.__context(__controller, __thread,
                    EventModContext.PARAMETER_TYPE, __on))
                    return false;
                break;
            
                // A parameter based type
            case PARAMETER_TYPE:
                if (this.hasTypeMatch())
                {
                    if (!viewType.isValid(__on) ||
                        !this.meetsType(viewType, __on))
                        return false;
                }
                break;
                
                // Stepping for a given thread
            case PARAMETER_STEPPING:
                {
                    // Not a tracker? Do nothing
                    if (!(__on instanceof JDWPStepTracker))
                        return false;
                    
                    // Get the tracker and the stepping
                    JDWPStepTracker stepTracker = (JDWPStepTracker)__on;
                    CallStackStepping stepping = this.callStackStepping;
                    
                    // No stepping here? Cannot be a match
                    if (stepping == null)
                        return false;
                    
                    // We can only reliably check on the thread and the depth
                    // requested.
                    if (__thread != stepping.thread ||
                        stepping.depth != stepTracker.depth())
                        return false;
                }
                break;
            
            default:
                throw Debugging.oops(__context);
        }
        
        // Is okay
        return true;
    }
}