/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.debug.internal.core.java; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.core.model.IThread; import org.eclipse.edt.debug.core.IEGLStackFrame; import org.eclipse.edt.debug.core.IEGLThread; import org.eclipse.edt.debug.core.java.IEGLJavaStackFrame; import org.eclipse.edt.debug.core.java.IEGLJavaThread; import org.eclipse.edt.debug.core.java.SMAPLineInfo; import org.eclipse.edt.debug.core.java.SMAPUtil; import org.eclipse.edt.debug.core.java.filters.FilterStepType; import org.eclipse.edt.debug.core.java.filters.ITypeFilter; import org.eclipse.edt.debug.core.java.filters.TypeFilterUtil; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; /** * Wraps an IJavaThread. */ public class EGLJavaThread extends EGLJavaDebugElement implements IEGLJavaThread { /** * Set this to true to enable debug output when steps are forced due to filtering. */ private static final boolean TRACE_FILTERS = false; private static final EGLJavaStackFrame[] EMPTY_FRAMES = {}; /** * The underlying Java thread. */ private final IJavaThread javaThread; /** * The EGL-wrapped stack frames. */ private EGLJavaStackFrame[] eglFrames; /** * The previous Java frames. */ private IStackFrame[] previousJavaFrames; /** * The current stack frames. */ private Map<IStackFrame, EGLJavaStackFrame> currentStackFrames; /** * The previous stack frames. */ private Map<IStackFrame, EGLJavaStackFrame> previousStackFrames; /** * The evaluation lock. */ private final Object evaluationLock; /** * The most recent location from where the user issued a step request (i.e. an unfiltered frame) */ private IJavaStackFrame stepStartFrame; /** * Constructor. * * @param target The debug target. * @param thread The Java thread. */ public EGLJavaThread( EGLJavaDebugTarget target, IJavaThread thread ) { super( target ); evaluationLock = new Object(); javaThread = thread; disposeStackFrames(); } protected void disposeStackFrames() { if ( currentStackFrames != null && currentStackFrames != Collections.EMPTY_MAP ) { previousStackFrames = currentStackFrames; } currentStackFrames = null; eglFrames = null; } @Override public Object getAdapter( Class adapter ) { if ( adapter == IThread.class || adapter == EGLJavaThread.class || adapter == IEGLThread.class || adapter == IEGLJavaThread.class ) { return this; } if ( adapter == IStackFrame.class || adapter == EGLJavaStackFrame.class || adapter == IEGLStackFrame.class || adapter == IEGLJavaStackFrame.class ) { try { return getTopStackFrame(); } catch ( DebugException e ) { } } if ( adapter == IJavaThread.class ) { return javaThread; } return super.getAdapter( adapter ); } @Override public boolean canResume() { return javaThread.canResume(); } @Override public boolean canSuspend() { return javaThread.canSuspend(); } @Override public boolean isSuspended() { return javaThread.isSuspended(); } @Override public void resume() throws DebugException { javaThread.resume(); } @Override public void suspend() throws DebugException { javaThread.suspend(); } @Override public boolean canStepInto() { return javaThread.canStepInto(); } @Override public boolean canStepOver() { return javaThread.canStepOver(); } @Override public boolean canStepReturn() { return javaThread.canStepReturn(); } @Override public boolean isStepping() { return javaThread.isStepping(); } @Override public void stepInto() throws DebugException { setStepStart( null ); javaThread.stepInto(); } @Override public void stepOver() throws DebugException { setStepStart( null ); javaThread.stepOver(); } @Override public void stepReturn() throws DebugException { setStepStart( null ); javaThread.stepReturn(); } public void setStepStart( IJavaStackFrame frame ) { if ( frame == null ) { frame = eglFrames == null || eglFrames.length == 0 ? null : eglFrames[ 0 ].getJavaStackFrame(); } stepStartFrame = frame; } @Override public boolean canTerminate() { return javaThread.canTerminate(); } @Override public boolean isTerminated() { return javaThread.isTerminated(); } @Override public void terminate() throws DebugException { javaThread.terminate(); } @Override public IStackFrame[] getStackFrames() throws DebugException { refreshFrames(); return eglFrames; } protected synchronized void refreshFrames() throws DebugException { if ( eglFrames == null ) { IStackFrame[] javaFrames = javaThread.getStackFrames(); if ( javaFrames.length > 0 ) { int size = 0; EGLJavaStackFrame[] newEGLFrames = new EGLJavaStackFrame[ javaFrames.length ]; currentStackFrames = new HashMap<IStackFrame, EGLJavaStackFrame>( javaFrames.length ); int indexOfTopEGLFrame = -1; for ( int i = 0; i < javaFrames.length; i++ ) { if ( SMAPUtil.isEGLStratum( (IJavaStackFrame)javaFrames[ i ], getEGLJavaDebugTarget() ) ) { indexOfTopEGLFrame = i; break; } } boolean doNotFilterRuntimes = !getEGLJavaDebugTarget().filterRuntimes() || indexOfTopEGLFrame == -1; for ( int i = 0; i < javaFrames.length; i++ ) { // Filtering rules: // 1. don't filter anything if the preference is disabled (see doNotFilterRuntimes flag) // 2. don't filter if there are no frames with the EGL stratum (see doNotFilterRuntimes flag) // 3. don't filter the top frame // 4. filter the initial main method (if there is one - there won't be if running on a server) // 5. if we're disabling filtering due to a breakpoint in the runtime, don't filter frames above the topmost frame w/EGL stratum if ( doNotFilterRuntimes || i == 0 || indexOfTopEGLFrame >= i || (!filterFromStack( (IJavaStackFrame)javaFrames[ i ] ) && (i + 1 < javaFrames.length || !isMainMethod( (IJavaStackFrame)javaFrames[ i ] ))) ) { EGLJavaStackFrame frame = previousStackFrames == null ? null : previousStackFrames.get( javaFrames[ i ] ); if ( frame == null ) { frame = new EGLJavaStackFrame( (IJavaStackFrame)javaFrames[ i ], this ); } else { frame.bind( (IJavaStackFrame)javaFrames[ i ] ); } currentStackFrames.put( javaFrames[ i ], frame ); newEGLFrames[ size++ ] = frame; } } if ( size == newEGLFrames.length ) { eglFrames = newEGLFrames; } else { eglFrames = new EGLJavaStackFrame[ size ]; System.arraycopy( newEGLFrames, 0, eglFrames, 0, size ); } } else { currentStackFrames = Collections.emptyMap(); eglFrames = EMPTY_FRAMES; } previousJavaFrames = javaFrames; } } @Override public boolean hasStackFrames() throws DebugException { return javaThread.hasStackFrames(); } @Override public int getPriority() throws DebugException { return javaThread.getPriority(); } @Override public IStackFrame getTopStackFrame() throws DebugException { IStackFrame[] frames = getStackFrames(); if ( frames.length > 0 ) { return frames[ 0 ]; } return null; } @Override public String getName() throws DebugException { return javaThread.getName(); } @Override public IBreakpoint[] getBreakpoints() { return javaThread.getBreakpoints(); } @Override public void handleDebugEvents( DebugEvent[] events ) { if ( events == null || events.length == 0 ) { return; } List<DebugEvent> savedEvents = new ArrayList<DebugEvent>(); Object src = events[ 0 ].getSource(); if ( src == javaThread ) { // While looping over the events, it's possible we have both a STEP_END and a BREAKPOINT details. If there's // a BREAKPOINT suspend event then we do not want to do any filtering. Check it first since it usually (always?) is listed second. boolean bpHit = false; for ( int i = 0; i < events.length; i++ ) { if ( events[ i ].getDetail() == DebugEvent.BREAKPOINT && events[ i ].getKind() == DebugEvent.SUSPEND ) { bpHit = true; break; } } for ( int i = 0; i < events.length; i++ ) { switch ( events[ i ].getKind() ) { case DebugEvent.CREATE: fireCreationEvent(); break; case DebugEvent.TERMINATE: getEGLJavaDebugTarget().removeThread( javaThread ); fireTerminateEvent(); break; case DebugEvent.RESUME: if ( getEGLJavaDebugTarget().filterRuntimes() && events[ i ].getDetail() == DebugEvent.STEP_INTO ) { IStackFrame topJavaFrame = previousJavaFrames == null || previousJavaFrames.length == 0 ? null : previousJavaFrames[ 0 ]; EGLJavaStackFrame topEGLFrame = eglFrames == null || eglFrames.length == 0 ? null : eglFrames[ 0 ]; if ( topEGLFrame != null && topEGLFrame.getJavaStackFrame() == topJavaFrame ) { try { topEGLFrame.setLineBeforeStepInto( topEGLFrame.getLineNumber() ); } catch ( DebugException e ) { topEGLFrame.setLineBeforeStepInto( -1 ); } } } savedEvents.add( events[ i ] ); break; case DebugEvent.SUSPEND: if ( !bpHit && getEGLJavaDebugTarget().filterRuntimes() && events[ i ].getDetail() == DebugEvent.STEP_END ) { try { IJavaStackFrame topJavaFrame = (IJavaStackFrame)javaThread.getTopStackFrame(); if ( topJavaFrame != null ) { int frameCount = javaThread.getFrameCount(); if ( frameCount == 1 && isMainMethod( topJavaFrame ) ) { // Don't step into the initial main frame - there's no EGL source for it, users will be confused. // Note: stepReturn isn't supported on a bottom frame in JDT so we must use resume. topJavaFrame.resume(); break; } else { // Filter strategy: If the type is EGL and its line number is -1, force a step into. If it's not EGL then // check if it should be filtered. If it should be filtered and the original location where the user issued // the (unfiltered) step request is still in the stack, filter back to that point and then stop. If the // original location is no longer in the stack (user explicitly stepped out of that frame) then: if the // location is not filtered, we stop at it; if it is filtered and there are no other EGL stratum frames then // we will perform a resume instead of a step; otherwise if there is a valid EGL stratum we step to it. // This is not ideal but this avoids cases where we infinitely run steps due to some loop like an event loop // that sits around forever; the infinite stepping causes flickering and poor performance. FilterStepType filterType = FilterStepType.NO_STEP; if ( SMAPUtil.isEGLStratum( topJavaFrame, getEGLJavaDebugTarget() ) ) { // Can be some internal method like ezeSetEmpty. if ( getLineNumber( topJavaFrame ) == -1 ) { filterType = FilterStepType.STEP_INTO; } } else if ( stepStartFrame != topJavaFrame ) { filterType = filter( topJavaFrame ); } if ( filterType == FilterStepType.STEP_INTO || filterType == FilterStepType.STEP_RETURN ) { boolean forceResume = true; IStackFrame[] frames = javaThread.getStackFrames(); for ( int j = 0; j < frames.length; j++ ) { // If we find the starting step frame OR we have a valid EGL stratum, perform a step. if ( frames[ j ] == stepStartFrame || (SMAPUtil.isEGLStratum( (IJavaStackFrame)frames[ j ], getEGLJavaDebugTarget() ) && getLineNumber( (IJavaStackFrame)frames[ j ] ) != -1) ) { forceResume = false; break; } } if ( forceResume ) { if ( TRACE_FILTERS ) { System.out .println( "EDT DEBUG: Forcing resume for frame " + topJavaFrame.getReferenceType().getName() //$NON-NLS-1$ + "." + topJavaFrame.getName() ); //$NON-NLS-1$ } topJavaFrame.resume(); } else if ( filterType == FilterStepType.STEP_INTO ) { if ( TRACE_FILTERS ) { System.out.println( "EDT DEBUG: Forcing stepInto for frame " //$NON-NLS-1$ + topJavaFrame.getReferenceType().getName() + "." + topJavaFrame.getName() ); //$NON-NLS-1$ } topJavaFrame.stepInto(); } else if ( topJavaFrame.canStepReturn() ) { if ( TRACE_FILTERS ) { System.out.println( "EDT DEBUG: Forcing stepReturn for frame " //$NON-NLS-1$ + topJavaFrame.getReferenceType().getName() + "." + topJavaFrame.getName() ); //$NON-NLS-1$ } topJavaFrame.stepReturn(); } else { // Resume when we can't step return (e.g. bottom frame, or above obsolete frame) if ( TRACE_FILTERS ) { System.out.println( "EDT DEBUG: Forcing resume (stepReturn not supported) for frame " //$NON-NLS-1$ + topJavaFrame.getReferenceType().getName() + "." + topJavaFrame.getName() ); //$NON-NLS-1$ } topJavaFrame.resume(); } break; } else { // If we forced a return after a step into, we might be at the same line as before. // If we're at the same line, do another step into so the user isn't confused. EGLJavaStackFrame topEGLFrame = (EGLJavaStackFrame)getTopStackFrame(); if ( topEGLFrame != null && topEGLFrame.getJavaStackFrame() == topJavaFrame && topEGLFrame.getLineBeforeStepInto() != -1 && topEGLFrame.getLineBeforeStepInto() == topEGLFrame.getLineNumber() ) { if ( TRACE_FILTERS ) { System.out.println( "EDT DEBUG: Forcing stepInto for EGL frame " //$NON-NLS-1$ + topEGLFrame.getJavaStackFrame().getReferenceType().getName() + "." //$NON-NLS-1$ + topEGLFrame.getJavaStackFrame().getName() ); } stepInto(); break; } } } } } catch ( DebugException de ) { // We tried, we failed, but we carry on... } } disposeStackFrames(); savedEvents.add( events[ i ] ); break; default: savedEvents.add( events[ i ] ); break; } } } if ( savedEvents.size() > 0 ) { super.handleDebugEvents( savedEvents.toArray( new DebugEvent[ savedEvents.size() ] ) ); } } private boolean filterFromStack( IJavaStackFrame frame ) throws DebugException { if ( SMAPUtil.isEGLStratum( frame, getEGLJavaDebugTarget() ) ) { return getLineNumber( frame ) == -1; } return filter( frame ) != FilterStepType.NO_STEP; } private int getLineNumber( IJavaStackFrame frame ) throws DebugException { if ( !getEGLJavaDebugTarget().supportsSourceDebugExtension() ) { // Try our own JSR-45 support. String smap = SMAPUtil.getSMAP( getEGLJavaDebugTarget(), frame.getReferenceType().getName() ); if ( SMAPUtil.isEGLStratum( smap ) ) { SMAPLineInfo lineInfo = SMAPUtil.getSMAPLineInfo( smap, getEGLJavaDebugTarget().getSMAPLineCache() ); if ( lineInfo != null ) { return lineInfo.getEGLLine( frame.getLineNumber() ); } } } return frame.getLineNumber(); } private FilterStepType filter( IJavaStackFrame frame ) throws DebugException { EGLJavaDebugTarget target = getEGLJavaDebugTarget(); for ( ITypeFilter filter : TypeFilterUtil.INSTANCE.getActiveFilters() ) { if ( filter.filter( frame, target ) ) { return filter.getCategory().getStepType( frame ); } } return FilterStepType.NO_STEP; } private boolean isMainMethod( IJavaStackFrame frame ) throws DebugException { return frame.isStatic() && "main".equals( frame.getName() ) && "([Ljava/lang/String;)V".equals( frame.getSignature() ); //$NON-NLS-1$ //$NON-NLS-2$ } @Override public IJavaThread getJavaThread() { return javaThread; } @Override public Object getJavaDebugElement() { return getJavaThread(); } @Override public Object getEvaluationLock() { return evaluationLock; } }