blob: e6a71f5bfbddcc51431fe94b44d4b9a5d62c1f58 [file] [log] [blame]
package org.eclipse.jdt.internal.debug.core;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
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.IVariable;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidStackFrameException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadGroupReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.StepEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.StepRequest;
/**
* Model thread implementation for an underlying
* thread on a VM.
*/
public class JDIThread extends JDIDebugElement implements IJavaThread {
/**
* Constant for the name of the main thread group.
*/
private static final String MAIN_THREAD_GROUP = "main"; //$NON-NLS-1$
/**
* Number of milliseconds used as a timeout before
* 'collapsing' stack frames when performing a step
* or method invocation.
*/
private static final int COLLAPSE_TIMEOUT = 3000;
/**
* Underlying thread.
*/
private ThreadReference fThread;
/**
* Collection of stack frames
*/
private List fStackFrames;
/**
* Underlying thread group, cached on first access.
*/
private ThreadGroupReference fThreadGroup;
/**
* Name of underlying thread group, cached on first access.
*/
private String fThreadGroupName;
/**
* Whether children need to be refreshed. Set to
* <code>true</code> when stack frames are re-used
* on the next suspend.
*/
private boolean fRefreshChildren = true;
/**
* Currently pending step handler, <code>null</code>
* when not performing a step.
*/
private StepHandler fStepHandler= null;
/**
* Whether running.
*/
private boolean fRunning;
/**
* <code>true</code> when suspended by an event in the
* VM such as a breakpoint or step, and <code>false</code>
* when suspended by an explicit user request (i.e. when
* a call is made to <code>#suspend()</code>).
*/
private boolean fEventSuspend = false;
/**
* Whether terminated.
*/
private boolean fTerminated;
/**
* Whether this thread is a system thread.
*/
private boolean fIsSystemThread;
/**
* The breakpoint that caused the last suspend, or
* <code>null</code> if none.
*/
private IJavaBreakpoint fCurrentBreakpoint;
/**
* The cached named of the underlying thread.
*/
private String fName= null;
/**
* Whether this thread is currently performing
* an evaluation (invoke method). Nested method
* invocations cannot be performed.
*/
private boolean fInEvaluation = false;
/**
* When performing an evaluation, a client
* may decide to abort the evaluation, in which
* case this flag gets set to <code>true</code>.
* When an evaluation is aborted, before it completes,
* the evaluation thread is eventually (automatically)
* resumed when the evaluation completes. When
* an evaluation is not aborted, the evaluation thread
* remains suspended when on completion of the
* evaluation.
*/
private boolean fEvaluationAborted = false;
/**
* Whether this thread has been interrupted during
* the last evaluation. That is, was a breakpoint
* hit, did the user suspend the thread, or did
* the evaluation timeout.
*/
private boolean fInterrupted = false;
/**
* The kind of step that was originally requested. Zero or
* more 'secondary steps' may be performed programmatically after
* the original user-requested step, and this field tracks the
* type (step into, over, return) of the original step.
*/
private int fOriginalStepKind;
/**
* The JDI Location from which an original user-requested step began.
*/
private Location fOriginalStepLocation;
/**
* The total stack depth at the time an original (user-requested) step
* is initiated. This is used along with the original step Location
* to determine if a step into comes back to the starting location and
* needs to be 'nudged' forward. Checking the stack depth eliminates
* undesired 'nudging' in recursive methods.
*/
private int fOriginalStepStackDepth;
/**
* Creates a new thread on the underlying thread reference
* in the given debug target.
*
* @param target the debug target in which this thread is contained
* @param thread the underlying thread on the VM
*/
public JDIThread(JDIDebugTarget target, ThreadReference thread) {
super(target);
setUnderlyingThread(thread);
initialize();
}
/**
* Thread initialization:<ul>
* <li>Determines if this thread is a system thread</li>
* <li>Sets terminated state to <code>false</code></li>
* <li>Determines suspended state from underlying thread</li>
* <li>Sets this threads stack frames to an empty collection</li>
* </ul>
*/
protected void initialize() {
fStackFrames= Collections.EMPTY_LIST;
// system thread
try {
determineIfSystemThread();
} catch (DebugException e) {
Throwable underlyingException= e.getStatus().getException();
if (underlyingException instanceof VMDisconnectedException) {
// Threads may be created by the VM at shutdown
// as finalizers. The VM may be disconnected by
// the time we hear about the thread creation.
disconnected();
return;
}
logError(e);
}
// state
setTerminated(false);
setRunning(false);
try {
setRunning(!getUnderlyingThread().isSuspended());
} catch (VMDisconnectedException e) {
disconnected();
return;
} catch (RuntimeException e) {
logError(e);
}
}
/**
* @see IThread#getBreakpoint()
*/
public IBreakpoint getBreakpoint() {
if (fCurrentBreakpoint != null && !fCurrentBreakpoint.getMarker().exists()) {
fCurrentBreakpoint= null;
}
return fCurrentBreakpoint;
}
/**
* @see ISuspendResume#canResume()
*/
public boolean canResume() {
return isSuspended();
}
/**
* @see ISuspendResume#canSuspend()
*/
public boolean canSuspend() {
return !isSuspended();
}
/**
* @see ITerminate#canTerminate()
*/
public boolean canTerminate() {
ObjectReference threadDeath= ((JDIDebugTarget) getDebugTarget()).getThreadDeathInstance();
return threadDeath != null && !isSystemThread() && !isTerminated();
}
/**
* @see IStep@canStepInto()
*/
public boolean canStepInto() {
return isSuspended() && !isStepping();
}
/**
* @see IStep#canStepOver()
*/
public boolean canStepOver() {
return isSuspended() && !isStepping();
}
/**
* @see IStep#canStepReturn()
*/
public boolean canStepReturn() {
return isSuspended() && !isStepping();
}
/**
* Determines and sets whether this thread represents a system thread.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void determineIfSystemThread() throws DebugException {
fIsSystemThread= false;
ThreadGroupReference tgr= getUnderlyingThreadGroup();
fIsSystemThread = tgr != null;
while (tgr != null) {
String tgn= null;
try {
tgn= tgr.name();
tgr= tgr.parent();
} catch (UnsupportedOperationException e) {
fIsSystemThread = false;
break;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_determining_if_system_thread"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will throw an exception
return;
}
if (tgn != null && tgn.equals(MAIN_THREAD_GROUP)) {
fIsSystemThread= false;
break;
}
}
}
/**
* NOTE: this method returns a copy of this thread's stack frames.
*
* @see IThread#getStackFrames()
*/
public IStackFrame[] getStackFrames() throws DebugException {
List list = computeStackFrames();
return (IStackFrame[])list.toArray(new IStackFrame[list.size()]);
}
/**
* Returns this thread's current stack frames as a list, computing
* them if required. Returns an empty collection if this thread is
* not currently suspended, or this thread is terminated. This
* method should be used internally to get the current stack frames,
* instead of calling <code>#getStackFrames()</code>, which makes a
* copy of the current list.
* <p>
* Before a thread is resumed a call must be made to one of:<ul>
* <li><code>preserveStackFrames()</code></li>
* <li><code>disposeStackFrames()</code></li>
* </ul>
* If stack frames are disposed before a thread is resumed, stack frames
* are completely re-computed on the next call to this method. If stack
* frames are to be preserved, this method will attempt to re-use any stack
* frame objects which represent the same stack frame as on the previous
* suspend. Stack frames are cached until a subsequent call to preserve
* or dispose stack frames.
* </p>
*
* @return list of <code>IJavaStackFrame</code>
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected synchronized List computeStackFrames() throws DebugException {
if (isSuspended()) {
if (isTerminated()) {
fStackFrames = Collections.EMPTY_LIST;
} else if (fRefreshChildren) {
if (fStackFrames.isEmpty()) {
fStackFrames = createAllStackFrames();
if (fStackFrames.isEmpty()) {
//leave fRefreshChildren == true
//bug 6393
return fStackFrames;
}
}
List frames= getUnderlyingFrames();
// Stepping can invalidate cached stack frame data. If any of the cached
// stack frame are out of synch with the underlying frames, discard the cached frames.
int numModelFrames= fStackFrames.size();
int numUnderlyingFrames= frames.size();
int modelFramesIndex = 0;
int underlyingFramesIndex = 0;
if (numModelFrames > numUnderlyingFrames) {
modelFramesIndex = numModelFrames - numUnderlyingFrames;
} else {
underlyingFramesIndex = numUnderlyingFrames - numModelFrames;
}
StackFrame underlyingFrame= null;
JDIStackFrame modelFrame= null;
for ( ; modelFramesIndex < numModelFrames; modelFramesIndex++, underlyingFramesIndex++) {
underlyingFrame= (StackFrame) frames.get(underlyingFramesIndex);
modelFrame= (JDIStackFrame) fStackFrames.get(modelFramesIndex);
if (!underlyingFrame.equals(modelFrame.getLastUnderlyingStackFrame())) {
// Replace the out of synch frame
fStackFrames.set(modelFramesIndex, new JDIStackFrame(this, underlyingFrame));
}
}
// compute new or removed stack frames
int offset= 0, length= frames.size();
if (length > fStackFrames.size()) {
// compute new frames
offset= length - fStackFrames.size();
for (int i= offset - 1; i >= 0; i--) {
JDIStackFrame newStackFrame= new JDIStackFrame(this, (StackFrame) frames.get(i));
fStackFrames.add(0, newStackFrame);
}
length= fStackFrames.size() - offset;
} else
if (length < fStackFrames.size()) {
// compute removed children
int removed= fStackFrames.size() - length;
for (int i= 0; i < removed; i++) {
fStackFrames.remove(0);
}
} else {
if (frames.isEmpty()) {
fStackFrames = Collections.EMPTY_LIST;
}
}
// update preserved frames
if (offset < fStackFrames.size()) {
updateStackFrames(frames, offset, fStackFrames, length);
}
}
fRefreshChildren = false;
} else {
return Collections.EMPTY_LIST;
}
return fStackFrames;
}
/**
* Helper method for <code>#computeStackFrames()</code> to create all
* underlying stack frames.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected List createAllStackFrames() throws DebugException {
List frames= getUnderlyingFrames();
List list= new ArrayList(frames.size());
Iterator iter= frames.iterator();
while (iter.hasNext()) {
JDIStackFrame newStackFrame= new JDIStackFrame(this, (StackFrame) iter.next());
list.add(newStackFrame);
}
return list;
}
/**
* Retrieves and returns all underlying stack frames
*
* @return list of <code>StackFrame</code>
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected List getUnderlyingFrames() throws DebugException {
try {
return getUnderlyingThread().frames();
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_stack_frames"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_stack_frames_2"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
} catch (InternalError e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_stack_frames_2"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
}
/**
* Returns the underlying method for the given stack frame
*
* @param frame an underlying JDI stack frame
* @return underlying method
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected Method getUnderlyingMethod(StackFrame frame) throws DebugException {
try {
return frame.location().method();
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_method"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
}
/**
* Returns the number of frames on the stack from the
* underlying thread.
*
* @return number of frames on the stack
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* <li>This thread is not suspended</li>
* </ul>
*/
protected int getUnderlyingFrameCount() throws DebugException {
try {
return getUnderlyingThread().frameCount();
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_frame_count"), new String[] {e.toString()}), e); //$NON-NLS-1$
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_frame_count"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
// execution will not reach here - try block will either
// return or exception will be thrown
return -1;
}
/**
* Invokes a method on the target, in this thread, and returns the result. Only
* one receiver may be specified - either a class or an object, the other must
* be <code>null</code>. This thread is left suspended after the invocation
* is complete, unless a call is made to <code>abortEvaluation<code> while
* performing a method invocation. In that case, this thread is automatically
* resumed when/if this invocation (eventually) completes.
* <p>
* Method invocations cannot be nested. That is, this method must
* return before another call to this method can be made. This
* method does not return until the invocation is complete.
* Breakpoints can suspend a method invocation, and it is possible
* that an invocation will not complete due to an infinite loop
* or deadlock.
* </p>
* <p>
* Stack frames are preserved during method invocations, unless
* a timeout occurs. Although this thread's state is updated to
* running while performing an evaluation, no debug events are
* fired unless this invocation is interrupted by a breakpoint,
* or the invocation times out.
* </p>
* <p>
* When performing an invocation, the communication timeout with
* the target VM is set to infinite, as the invocation may not
* complete in a timely fashion, if at all. The timeout value
* is reset to its original value when the invocation completes.
* </p>
*
* @param receiverClass the class in the target representing the receiver
* of a static message send, or <code>null</code>
* @param receiverObject the object in the target to be the receiver of
* the message send, or <code>null</code>
* @param method the underlying method to be invoked
* @param args the arguments to invoke the method with (an empty list
* if none)
* @return the result of the method, as an underlying value
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected Value invokeMethod(ClassType receiverClass, ObjectReference receiverObject, Method method, List args) throws DebugException {
if (receiverClass != null && receiverObject != null) {
throw new IllegalArgumentException(JDIDebugModelMessages.getString("JDIThread.can_only_specify_one_receiver_for_a_method_invocation")); //$NON-NLS-1$
}
// this is synchronized such that any other operation that
// might be resuming this thread has a chance to complete before
// we test if this thread is already runnnig. See bug 6518.
synchronized (this) {
if (!isSuspended()) {
requestFailed(JDIDebugModelMessages.getString("JDIThread.Evaluation_failed_-_thread_not_suspended"), null); //$NON-NLS-1$
}
}
if (isPerformingEvaluation()) {
requestFailed(JDIDebugModelMessages.getString("JDIThread.Cannot_perform_nested_evaluations"), null); //$NON-NLS-1$
}
Value result= null;
int timeout= getRequestTimeout();
try {
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
resetAbortEvaluation();
setRunning(true);
setPerformingEvaluation(true);
preserveStackFrames();
resetInterrupted();
if (receiverClass == null) {
result= receiverObject.invokeMethod(getUnderlyingThread(), method, args, ClassType.INVOKE_SINGLE_THREADED);
} else {
result= receiverClass.invokeMethod(getUnderlyingThread(), method, args, ClassType.INVOKE_SINGLE_THREADED);
}
} catch (InvalidTypeException e) {
invokeFailed(e, timeout);
} catch (ClassNotLoadedException e) {
invokeFailed(e, timeout);
} catch (IncompatibleThreadStateException e) {
invokeFailed(e, timeout);
} catch (InvocationException e) {
invokeFailed(e, timeout);
} catch (RuntimeException e) {
invokeFailed(e, timeout);
}
invokeComplete(timeout);
if (wasEvaluationAborted()) {
resume();
}
return result;
}
/**
* Called when this thread suspends. If this thread is performing
* a method invocation, a note is made that the invocation was
* interrupted. When an invocation is interrupted, a suspend
* event must be fired when/if the invocation eventually completes.
*
* @see #invokeMethod(ClassType, ObjectReference, Method, List)
*/
protected void interrupted() {
if (isPerformingEvaluation()) {
fInterrupted = true;
}
}
/**
* Resets the interrupted state to <code>false</code>.
*/
protected void resetInterrupted() {
fInterrupted = false;
}
/**
* Returns whether this thread was interrupted during the
* last evaluation.
*/
protected boolean wasInterrupted() {
return fInterrupted;
}
/**
* Causes this thread to be automatically resumed when
* it returns from its current invocation, if any.
* <p>
* For example, this is called by <code>JDIValue</code>
* when a 'to string' evaluation times out. If the invocation
* ever completes, the thread will not suspend when/if the
* 'to string' evaluation completes.
* </p>
*
* @see #invokeMethod(ClassType, ObjectReference, Method, List)
*/
protected void abortEvaluation() {
fEvaluationAborted = true;
}
/**
* Resets the abort flag to <code>false</code> before
* an evaluation.
*/
protected void resetAbortEvaluation() {
fEvaluationAborted = false;
}
/**
* Returns whether a client asked to abort an evaluation
* while performing a method invocation.
*
* @see #invokeMethod(ClassType, ObjectReference, Method, List)
*/
protected boolean wasEvaluationAborted() {
return fEvaluationAborted;
}
/**
* Invokes a constructor in this thread, creating a new instance of the given
* class, and returns the result as an object reference.
* This thread is left suspended after the invocation
* is complete.
* <p>
* Method invocations cannot be nested. That is, this method must
* return before another call to this method can be made. This
* method does not return until the invocation is complete.
* Breakpoints can suspend a method invocation, and it is possible
* that an invocation will not complete due to an infinite loop
* or deadlock.
* </p>
* <p>
* Stack frames are preserved during method invocations, unless
* a timeout occurs. Although this thread's state is updated to
* running while performing an evaluation, no debug events are
* fired unless this invocation is interrupted by a breakpoint,
* or the invocation times out.
* </p>
* <p>
* When performing an invocation, the communication timeout with
* the target VM is set to infinite, as the invocation may not
* complete in a timely fashion, if at all. The timeout value
* is reset to its original value when the invocation completes.
* </p>
*
* @param receiverClass the class in the target representing the receiver
* of the 'new' message send
* @param constructor the underlying constructor to be invoked
* @param args the arguments to invoke the constructor with (an empty list
* if none)
* @return a new object reference
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected ObjectReference newInstance(ClassType receiverClass, Method constructor, List args) throws DebugException {
if (fInEvaluation) {
requestFailed(JDIDebugModelMessages.getString("JDIThread.Cannot_perform_nested_evaluations_2"), null); //$NON-NLS-1$
}
ObjectReference result= null;
int timeout= getRequestTimeout();
try {
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
setRunning(true);
setPerformingEvaluation(true);
preserveStackFrames();
resetInterrupted();
result= receiverClass.newInstance(getUnderlyingThread(), constructor, args, ClassType.INVOKE_SINGLE_THREADED);
} catch (InvalidTypeException e) {
invokeFailed(e, timeout);
} catch (ClassNotLoadedException e) {
invokeFailed(e, timeout);
} catch (IncompatibleThreadStateException e) {
invokeFailed(e, timeout);
} catch (InvocationException e) {
invokeFailed(e, timeout);
} catch (RuntimeException e) {
invokeFailed(e, timeout);
}
invokeComplete(timeout);
return result;
}
/**
* Called when an invocation fails. Performs cleanup
* and throws an exception.
*
* @param e the exception that caused the failure
* @param restoreTimeout the communication timeout value,
* in milliseconds, that should be reset
* @see #invokeComplete(int)
* @exception DebugException. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void invokeFailed(Throwable e, int restoreTimeout) throws DebugException {
invokeComplete(restoreTimeout);
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_invoking_method"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
/**
* Called when a method invocation has returned, successfully
* or not. This method performs cleanup:<ul>
* <li>Resets the state of this thread to suspended</li>
* <li>Restores the communication timeout value</li>
* <li>Computes the new set of stack frames for this thread</code>
* <li>Fires a suspend event, iff the invocation was interrupted</code>
* </ul>
*
* @param restoreTimeout the communication timeout value,
* in milliseconds, that should be reset
* @see #invokeMethod(ClassType, ObjectReference, Method, List)
* @see #newInstance(ClassType, Method, List)
*/
protected void invokeComplete(int restoreTimeout) {
boolean interrupted= wasInterrupted();
setRunning(false);
setPerformingEvaluation(false);
setRequestTimeout(restoreTimeout);
// update preserved stack frames
try {
computeStackFrames();
} catch (DebugException e) {
logError(e);
}
if (interrupted) {
fireSuspendEvent(-1);
} else {
// fire a change event for the top stack frame
// to update any variables that may have changed
// value
try {
JDIStackFrame sf = (JDIStackFrame)getTopStackFrame();
// if the invoke failed, we may not have a top
// stack frame
if (sf != null) {
sf.fireChangeEvent();
}
} catch (DebugException e) {
logError(e);
}
}
}
/**
* Sets the timeout interval for jdi requests in milliseconds
*
* @param timeout the communication timeout, in milliseconds
*/
protected void setRequestTimeout(int timeout) {
VirtualMachine vm = getVM();
if (vm instanceof org.eclipse.jdi.VirtualMachine) {
((org.eclipse.jdi.VirtualMachine) vm).setRequestTimeout(timeout);
}
}
/**
* Returns the timeout interval for JDI requests in milliseconds,
* or -1 if not supported
*
* @return timeout value, in milliseconds, or -1 if not supported
*/
protected int getRequestTimeout() {
VirtualMachine vm = getVM();
if (vm instanceof org.eclipse.jdi.VirtualMachine) {
return ((org.eclipse.jdi.VirtualMachine) vm).getRequestTimeout();
}
return -1;
}
/**
* @see IThread#getName()
*/
public String getName() throws DebugException {
if (fName == null) {
try {
fName = getUnderlyingThread().name();
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_thread_name"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not fall through, as
// #targetRequestFailed will thrown an exception
}
}
return fName;
}
/**
* @see IThread#getPriority
*/
public int getPriority() throws DebugException {
// to get the priority, we must get the value from the "priority" field
Field p= null;
try {
p= getUnderlyingThread().referenceType().fieldByName("priority"); //$NON-NLS-1$
if (p == null) {
requestFailed(JDIDebugModelMessages.getString("JDIThread.no_priority_field"), null); //$NON-NLS-1$
}
Value v= getUnderlyingThread().getValue(p);
if (v instanceof IntegerValue) {
return ((IntegerValue)v).value();
} else {
requestFailed(JDIDebugModelMessages.getString("JDIThread.priority_not_an_integer"), null); //$NON-NLS-1$
}
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_thread_priority"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
// execution will not fall through to this line, as
// #targetRequestFailed or #requestFailed will throw
// an exception
return -1;
}
/**
* @see IThread#getTopStackFrame()
*/
public IStackFrame getTopStackFrame() throws DebugException {
List c= computeStackFrames();
if (c.isEmpty()) {
return null;
} else {
return (IStackFrame) c.get(0);
}
}
/**
* A breakpoint has suspended execution of this thread.
* Aborts any step currently in process and fires a
* suspend event.
*
* @param breakpoint the breakpoint that caused the suspend
*/
protected void handleSuspendForBreakpoint(JavaBreakpoint breakpoint) {
abortStep();
fCurrentBreakpoint= breakpoint;
setRunning(false);
fireSuspendEvent(DebugEvent.BREAKPOINT);
}
/**
* @see IStep#isStepping()
*/
public boolean isStepping() {
return getPendingStepHandler() != null;
}
/**
* @see ISuspendResume#isSuspended()
*/
public boolean isSuspended() {
return !fRunning && !fTerminated;
}
/**
* @see IJavaThread#isSystemThread()
*/
public boolean isSystemThread() {
return fIsSystemThread;
}
/**
* @see IJavaThread#getThreadGroupName()
*/
public String getThreadGroupName() throws DebugException {
if (fThreadGroupName == null) {
ThreadGroupReference tgr= getUnderlyingThreadGroup();
try {
fThreadGroupName = tgr.name();
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_thread_group_name"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
}
return fThreadGroupName;
}
/**
* @see ITerminate#isTerminated()
*/
public boolean isTerminated() {
return fTerminated;
}
public boolean isOutOfSynch() throws DebugException {
if (isSuspended()) {
List frames= computeStackFrames();
Iterator iter= frames.iterator();
while (iter.hasNext()) {
if (((JDIStackFrame) iter.next()).isOutOfSynch()) {
return true;
}
}
return false;
} else {
// If the thread is not suspended, there's no way to
// say for certain that it is running out of synch code
return false;
}
}
public boolean mayBeOutOfSynch() throws DebugException {
if (!isSuspended()) {
return ((JDIDebugTarget)getDebugTarget()).hasHCRFailed();
}
return false;
}
/**
* Sets whether this thread is terminated
*
* @param terminated whether this thread is terminated
*/
protected void setTerminated(boolean terminated) {
fTerminated= terminated;
}
/**
* @see ISuspendResume#resume()
*/
public void resume() throws DebugException {
if (!isSuspended()) {
return;
}
try {
setRunning(true);
disposeStackFrames();
fireResumeEvent(DebugEvent.CLIENT_REQUEST);
getUnderlyingThread().resume();
} catch (VMDisconnectedException e) {
disconnected();
} catch (RuntimeException e) {
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_resuming"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
}
/**
* Sets whether this thread is currently executing.
* When set to <code>true</code>, this thread's current
* breakpoint is cleared. When set to <code>false</code>,
* a note is made that this thread has been interrupted.
*
* @param running whether this thread is executing
*/
protected void setRunning(boolean running) {
fRunning = running;
if (running) {
fCurrentBreakpoint = null;
} else {
interrupted();
}
}
/**
* Preserves stack frames to be used on the next suspend event.
* Iterates through all current stack frames, setting their
* state as invalid. This method should be called before this thread
* is resumed, when stack frames are to be re-used when it later
* suspends.
*
* @see computeStackFrames()
*/
protected void preserveStackFrames() {
fRefreshChildren = true;
Iterator frames = fStackFrames.iterator();
while (frames.hasNext()) {
((JDIStackFrame)frames.next()).setUnderlyingStackFrame(null);
}
}
/**
* Disposes stack frames, to be completely re-computed on
* the next suspend event. This method should be called before
* this thread is resumed when stack frames are not to be re-used
* on the next suspend.
*
* @see computeStackFrames()
*/
protected void disposeStackFrames() {
fStackFrames= Collections.EMPTY_LIST;
fRefreshChildren = true;
}
/**
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @see IStep#stepInto()
*/
public synchronized void stepInto() throws DebugException {
if (!canStepInto()) {
return;
}
StepHandler handler = new StepIntoHandler();
handler.step();
}
/**
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @see IStep#stepOver()
*/
public synchronized void stepOver() throws DebugException {
if (!canStepOver()) {
return;
}
StepHandler handler = new StepOverHandler();
handler.step();
}
/**
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @see IStep#stepReturn()
*/
public synchronized void stepReturn() throws DebugException {
if (!canStepReturn()) {
return;
}
StepHandler handler = new StepReturnHandler();
handler.step();
}
protected void setOriginalStepKind(int stepKind) {
fOriginalStepKind = stepKind;
}
protected int getOriginalStepKind() {
return fOriginalStepKind;
}
protected void setOriginalStepLocation(Location location) {
fOriginalStepLocation = location;
}
protected Location getOriginalStepLocation() {
return fOriginalStepLocation;
}
protected void setOriginalStepStackDepth(int depth) {
fOriginalStepStackDepth = depth;
}
protected int getOriginalStepStackDepth() {
return fOriginalStepStackDepth;
}
/**
* In cases where a user-requested step into encounters nothing but filtered code
* (static initializers, synthetic methods, etc.), the default JDI behavior is to
* put the instruction pointer back where it was before the step into. This requires
* a second step to move forward. Since this is confusing to the user, we do an
* extra step into in such situations. This method determines when such an extra
* step into is necessary. It compares the current Location to the original
* Location when the user step into was initiated. It also makes sure the stack depth
* now is the same as when the step was initiated.
*/
protected boolean shouldDoExtraStepInto(Location location) throws DebugException {
if (getOriginalStepKind() != StepRequest.STEP_INTO) {
return false;
}
if (getOriginalStepStackDepth() != getUnderlyingFrameCount()) {
return false;
}
Location origLocation = getOriginalStepLocation();
if (origLocation == null) {
return false;
}
// We cannot simply check if the two Locations are equal using the equals()
// method, since this checks the code index within the method. Even if the
// code indices are different, the line numbers may be the same, in which case
// we need to do the extra step into.
Method origMethod = origLocation.method();
Method currMethod = location.method();
if (!origMethod.equals(currMethod)) {
return false;
}
if (origLocation.lineNumber() != location.lineNumber()) {
return false;
}
return true;
}
/**
* @see ISuspendResume#suspend()
*/
public void suspend() throws DebugException {
try {
// Abort any pending step request
abortStep();
getUnderlyingThread().suspend();
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
} catch (RuntimeException e) {
setRunning(true);
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_suspending"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
}
/**
* @see ITerminate#terminate()
*/
public void terminate() throws DebugException {
ObjectReference threadDeath= ((JDIDebugTarget) getDebugTarget()).getThreadDeathInstance();
if (threadDeath != null) {
try {
getUnderlyingThread().stop(threadDeath);
} catch (InvalidTypeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_terminating"), new String[] {e.toString()}), e); //$NON-NLS-1$
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_terminating_2"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return;
}
// Resume the thread so that stop will work
resume();
} else {
requestFailed(JDIDebugModelMessages.getString("JDIThread.unable_to_terminate"), null); //$NON-NLS-1$
}
}
/**
* Replaces the underlying stack frame objects in the preserved frames
* list with the current underlying stack frames.
*
* @param newFrames list of current underlying <code>StackFrame</code>s.
* Frames from this list are assigned to the underlying frames in
* the <code>oldFrames</code> list.
* @param offset the offset in the lists at which to start replacing
* the old underlying frames
* @param oldFrames list of preserved frames, of type <code>JDIStackFrame</code>
* @param length the number of frames to replace
*/
protected void updateStackFrames(List newFrames, int offset, List oldFrames, int length) throws DebugException {
for (int i= 0; i < length; i++) {
JDIStackFrame frame= (JDIStackFrame) oldFrames.get(offset);
frame.setUnderlyingStackFrame((StackFrame) newFrames.get(offset));
offset++;
}
}
/**
* Drops to the given stack frame
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void dropToFrame(IStackFrame frame) throws DebugException {
VirtualMachine vm= getVM();
if (vm.canPopFrames()) {
// JDK 1.4 support
try {
// Pop the drop frame and all frames above it
StackFrame jdiFrame = ((JDIStackFrame) frame).getUnderlyingStackFrame();
preserveStackFrames();
fThread.popFrames(jdiFrame);
computeStackFrames();
stepInto();
} catch (IncompatibleThreadStateException exception) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_dropping_to_frame"), new String[] {exception.toString()}),exception); //$NON-NLS-1$
} catch (InvalidStackFrameException exception) {
// InvalidStackFrameException can be thrown when all but the
// deepest frame were popped. Fire a changed notification
// in case this has occured.
fireChangeEvent();
targetRequestFailed(exception.toString(),exception); //$NON-NLS-1$
} catch (RuntimeException exception) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_dropping_to_frame"), new String[] {exception.toString()}),exception); //$NON-NLS-1$
}
} else {
// J9 support
// This block is synchronized, such that the step request
// begins before a background evaluation can be performed.
synchronized (this) {
StepHandler handler = new DropToFrameHandler(frame);
handler.step();
}
}
}
/**
* Steps until the specified stack frame is the top frame. Provides
* ability to step over/return in the non-top stack frame.
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected synchronized void stepToFrame(IStackFrame frame) throws DebugException {
if (!canStepReturn()) {
return;
}
StepHandler handler = new StepToFrameHandler(frame);
handler.step();
}
/**
* Aborts the current step, if any.
*/
protected void abortStep() {
StepHandler handler = getPendingStepHandler();
if (handler != null) {
handler.abort();
}
}
/**
* @see IJavaThread#findVariable(String)
*/
public IVariable findVariable(String varName) throws DebugException {
if (isSuspended()) {
Iterator stackFrames= computeStackFrames().iterator();
while (stackFrames.hasNext()) {
JDIStackFrame sf= (JDIStackFrame)stackFrames.next();
IVariable var= sf.findVariable(varName);
if (var != null) {
return var;
}
}
}
return null;
}
/**
* When a suspend event is fired, a thread keeps track of the
* cause of the suspend. If the suspend is due to a an event
* request in the underlying VM, evaluations may be performed,
* otherwise evaluations are disallowed.
*
* @see JDIDebugElement#fireSuspendEvent(int)
*/
protected void fireSuspendEvent(int detail) {
fEventSuspend = (detail != DebugEvent.CLIENT_REQUEST);
super.fireSuspendEvent(detail);
}
/**
* Notification this thread has terminated - update state
* and fire a terminate event.
*/
protected void terminated() {
setTerminated(true);
setRunning(false);
fireTerminateEvent();
}
/**
* Returns this thread on the underlying VM which this
* model thread is a proxy to.
*
* @return underlying thread
*/
protected ThreadReference getUnderlyingThread() {
return fThread;
}
/**
* Sets the underlying thread that this model object
* is a proxy to.
*
* @param thread underlying thread on target VM
*/
protected void setUnderlyingThread(ThreadReference thread) {
fThread = thread;
}
/**
* Returns this thread's underlying thread group.
*
* @return thread group
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* <li>Retrieving the underlying thread group is not supported
* on the underlying VM</li>
* </ul>
*/
protected ThreadGroupReference getUnderlyingThreadGroup() throws DebugException {
if (fThreadGroup == null) {
try {
fThreadGroup = getUnderlyingThread().threadGroup();
} catch (UnsupportedOperationException e) {
requestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_thread_group"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #requestFailed will throw an exception
return null;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_retrieving_thread_group"), new String[] {e.toString()}), e); //$NON-NLS-1$
// execution will not reach this line, as
// #targetRequestFailed will throw an exception
return null;
}
}
return fThreadGroup;
}
/**
* Returns whether this thread is currently performing
* an evaluation.
*
* @return whether this thread is currently performing
* an evaluation
*/
protected boolean isPerformingEvaluation() {
return fInEvaluation;
}
/**
* Sets whether this thread is currently performing
* an evaluation.
*
* @param evaluating whether this thread is currently
* performing an evaluation
*/
protected void setPerformingEvaluation(boolean evaluating) {
fInEvaluation= evaluating;
}
/**
* Sets the step handler currently handling a step
* request.
*
* @param handler the current step handler, or <code>null</code>
* if none
*/
protected void setPendingStepHandler(StepHandler handler) {
fStepHandler = handler;
}
/**
* Returns the step handler currently handling a step
* request, or <code>null</code> if none.
*
* @return step handler, or <code>null</code> if none
*/
protected StepHandler getPendingStepHandler() {
return fStepHandler;
}
/**
* Helper class to perform stepping an a thread.
*/
abstract class StepHandler implements IJDIEventListener {
/**
* Request for stepping in the underlying VM
*/
private StepRequest fStepRequest;
/**
* Initiates a step in the underlying VM by creating a step
* request of the appropriate kind (over, into, return),
* and resuming this thread. When a step is initiated it
* is registered with its thread as a pending step. A pending
* step could be cancelled if a breakpoint suspends execution
* during the step.
* <p>
* This thread's state is set to running and stepping, and
* stack frames are invalidated (but preserved to be re-used
* when the step completes). A resume event with a step detail
* is fired for this thread.
* </p>
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void step() throws DebugException {
setOriginalStepKind(getStepKind());
setOriginalStepLocation(((JDIStackFrame)getTopStackFrame()).getUnderlyingStackFrame().location());
setOriginalStepStackDepth(computeStackFrames().size());
setStepRequest(createStepRequest());
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
setRunning(true);
preserveStackFrames();
fireResumeEvent(getStepDetail());
invokeThread();
}
/**
* Resumes the underlying thread to initiate the step.
* By default the thread is resumed. Step handlers that
* require other actions can override this method.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void invokeThread() throws DebugException {
try {
getUnderlyingThread().resume();
} catch (RuntimeException e) {
stepEnd();
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_stepping"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
}
/**
* Creates and returns a step request specific to this step
* handler. Subclasses must override <code>getStepKind()</code>
* to return the kind of step it implements.
*
* @return step request
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepRequest createStepRequest() throws DebugException {
try {
StepRequest request = getEventRequestManager().createStepRequest(getUnderlyingThread(), StepRequest.STEP_LINE, getStepKind());
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
attachFiltersToStepRequest(request);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_creating_step_request"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
// this line will never be executed, as the try block
// will either return, or the catch block will throw
// an exception
return null;
}
/**
* Returns the kind of step this handler implements.
*
* @return one of <code>StepRequest.STEP_INTO</code>,
* <code>StepRequest.STEP_OVER</code>, <code>StepRequest.STEP_OUT</code>
*/
protected abstract int getStepKind();
/**
* Returns the detail for this step event.
*
* @return one of <code>DebugEvent.STEP_INTO</code>,
* <code>DebugEvent.STEP_OVER</code>, <code>DebugEvent.STEP_RETURN</code>
*/
protected abstract int getStepDetail();
/**
* Sets the step request created by this handler in
* the underlying VM. Set to <code>null<code> when
* this handler deletes its request.
*
* @param request step request
*/
protected void setStepRequest(StepRequest request) {
fStepRequest = request;
}
/**
* Returns the step request created by this handler in
* the underlying VM.
*
* @return step request
*/
protected StepRequest getStepRequest() {
return fStepRequest;
}
/**
* Deletes this handler's step request from the underlying VM
* and removes this handler as an event listener.
*/
protected void deleteStepRequest() {
removeJDIEventListener(this, getStepRequest());
try {
getEventRequestManager().deleteEventRequest(getStepRequest());
setStepRequest(null);
} catch (RuntimeException e) {
logError(e);
}
}
/**
* If step filters are currently switched on, set all active filters on the step request.
*/
protected void attachFiltersToStepRequest(StepRequest request) {
if (applyStepFilters() && JDIDebugModel.useStepFilters()) {
List activeFilters = JDIDebugModel.getActiveStepFilters();
Iterator iterator = activeFilters.iterator();
while (iterator.hasNext()) {
String filter = (String)iterator.next();
request.addClassExclusionFilter(filter);
}
}
}
/**
* Returns whether this step handler should use step
* filters when creating its step request. By default,
* step filters are not used. Subclasses must override
* if/when required.
*
* @return whether this step handler should use step
* filters when creating its step request
*/
protected boolean applyStepFilters() {
return false;
}
/**
* Notification the step request has completed.
* If the current location matches one of the user-specified
* step filter criteria (e.g., synthetic methods, static initializers),
* then continue stepping.
*
* @see IJDIDebugEventListener#handleEvent(Event, JDIDebugTarget)
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
try {
StepEvent stepEvent = (StepEvent) event;
Location location = stepEvent.location();
Method method = location.method();
// if the ending step location is filtered, or if we're back where
// we started on a step into, do another step of the same kind
if (locationIsFiltered(method) || shouldDoExtraStepInto(location)) {
setRunning(true);
deleteStepRequest();
createSecondaryStepRequest();
return true;
// otherwise, we're done stepping
} else {
stepEnd();
return false;
}
} catch (DebugException e) {
logError(e);
stepEnd();
return false;
}
}
/**
* Returns true if the StepEvent's Location is a Method that the
* user has indicated (via the step filter preferences) should be
* filtered. Return false otherwise.
*/
protected boolean locationIsFiltered(Method method) {
if (applyStepFilters()) {
boolean filterStatics = JDIDebugModel.filterStatics();
boolean filterSynthetics = JDIDebugModel.filterSynthetics();
boolean filterConstructors = JDIDebugModel.filterConstructors();
if (!(filterStatics || filterSynthetics || filterConstructors)) {
return false;
}
if ((filterStatics && method.isStaticInitializer()) ||
(filterSynthetics && method.isSynthetic()) ||
(filterConstructors && method.isConstructor()) ) {
return true;
}
}
return false;
}
/**
* Cleans up when a step completes.<ul>
* <li>Thread state is set to suspended.</li>
* <li>Stepping state is set to false</li>
* <li>Stack frames and variables are incrementally updated</li>
* <li>The step request is deleted and removed as
* and event listener</li>
* <li>A suspend event is fired</li>
* </ul>
*/
protected void stepEnd() {
setRunning(false);
deleteStepRequest();
setPendingStepHandler(null);
fireSuspendEvent(DebugEvent.STEP_END);
}
/**
* Creates another step request in the underlying thread of the
* appropriate kind (over, into, return). This thread will
* be resumed by the event dispatcher as this event handler
* will vote to resume suspended threads. When a step is
* initiated it is registered with its thread as a pending
* step. A pending step could be cancelled if a breakpoint
* suspends execution during the step.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void createSecondaryStepRequest() throws DebugException {
setStepRequest(createStepRequest());
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
}
/**
* Aborts this step request if active. The step event
* request is deleted from the underlying VM.
*/
protected void abort() {
if (getStepRequest() != null) {
deleteStepRequest();
setPendingStepHandler(null);
}
}
}
/**
* Handler for step over requests.
*/
class StepOverHandler extends StepHandler {
/**
* @see StepHandler#getStepKind()
*/
protected int getStepKind() {
return StepRequest.STEP_OVER;
}
/**
* @see StepHandler#getStepDetail()
*/
protected int getStepDetail() {
return DebugEvent.STEP_OVER;
}
}
/**
* Handler for step into requests.
*/
class StepIntoHandler extends StepHandler {
/**
* @see StepHandler#getStepKind()
*/
protected int getStepKind() {
return StepRequest.STEP_INTO;
}
/**
* @see StepHandler#getStepDetail()
*/
protected int getStepDetail() {
return DebugEvent.STEP_INTO;
}
/**
* Returns <code>true</code>. Step filters are applied for
* stepping into new frames.
*
* @see StepHandler#applyStepFilters()
*/
protected boolean applyStepFilters() {
return true;
}
}
/**
* Handler for step return requests.
*/
class StepReturnHandler extends StepHandler {
/**
* @see StepHandler#getStepKind()
*/
protected int getStepKind() {
return StepRequest.STEP_OUT;
}
/**
* @see StepHandler#getStepDetail()
*/
protected int getStepDetail() {
return DebugEvent.STEP_RETURN;
}
}
/**
* Handler for stepping to a specific stack frame
* (stepping in the non-top stack frame). Step returns
* are performed until a specified stack frame is reached
* or the thread is suspended (explicitly, or by a
* breakpoint).
*/
class StepToFrameHandler extends StepReturnHandler {
/**
* The number of frames that should be left on the stack
*/
private int fRemainingFrames;
/**
* Constructs a step handler to step until the specified
* stack frame is reached.
*
* @param frame the stack frame to step to
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepToFrameHandler(IStackFrame frame) throws DebugException {
List frames = computeStackFrames();
setRemainingFrames(frames.size() - frames.indexOf(frame));
}
/**
* Sets the number of frames that should be
* remaining on the stack when done.
*
* @param num number of remaining frames
*/
protected void setRemainingFrames(int num) {
fRemainingFrames = num;
}
/**
* Returns number of frames that should be
* remaining on the stack when done
*
* @return number of frames that should be left
*/
protected int getRemainingFrames() {
return fRemainingFrames;
}
/**
* Notification the step request has completed.
* If in the desired frame, complete the step
* request normally. If not in the desired frame,
* another step request is created and this thread
* is resumed.
*
* @see IJDIDebugEventListener#handleEvent(Event, JDIDebugTarget)
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
try {
int numFrames = getUnderlyingFrameCount();
// tos should not be null
if (numFrames <= getRemainingFrames()) {
stepEnd();
return false;
} else {
// reset running state and keep going
setRunning(true);
deleteStepRequest();
createSecondaryStepRequest();
return true;
}
} catch (DebugException e) {
logError(e);
stepEnd();
return false;
}
}
}
/**
* Handles dropping to a specified frame.
*/
class DropToFrameHandler extends StepReturnHandler {
/**
* The number of frames to drop off the
* stack.
*/
private int fFramesToDrop;
/**
* Constructs a handler to drop to the specified
* stack frame.
*
* @param frame the stack frame to drop to
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected DropToFrameHandler(IStackFrame frame) throws DebugException {
List frames = computeStackFrames();
setFramesToDrop(frames.indexOf(frame));
}
/**
* Sets the number of frames to pop off the stack.
*
* @param num number of frames to pop
*/
protected void setFramesToDrop(int num) {
fFramesToDrop = num;
}
/**
* Returns the number of frames to pop off the stack.
*
* @return remaining number of frames to pop
*/
protected int getFramesToDrop() {
return fFramesToDrop;
}
/**
* To drop a frame or re-enter, the underlying thread is instructed
* to do a return. When the frame count is less than zero, the
* step being performed is a "step return", so a regular invocation
* is performed.
*
* @see StepHandler#invokeThread()
*/
protected void invokeThread() throws DebugException {
if (getFramesToDrop() < 0) {
super.invokeThread();
} else {
try {
org.eclipse.jdi.hcr.ThreadReference hcrThread= (org.eclipse.jdi.hcr.ThreadReference) getUnderlyingThread();
hcrThread.doReturn(null, true);
} catch (RuntimeException e) {
stepEnd();
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString(JDIDebugModelMessages.getString("JDIThread.exception_while_popping_stack_frame")), new String[] {e.toString()}), e); //$NON-NLS-1$
}
}
}
/**
* Notification that the pop has completed. If there are
* more frames to pop, keep going, otherwise re-enter the
* top frame. Returns false, as this handler will resume this
* thread with a special invocation (<code>doReturn</code>).
*
* @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
* @see #invokeThread()
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
// pop is complete, update number of frames to drop
setFramesToDrop(getFramesToDrop() - 1);
try {
if (getFramesToDrop() >= -1) {
deleteStepRequest();
doSecondaryStep();
} else {
stepEnd();
}
} catch (DebugException e) {
stepEnd();
logError(e);
}
return false;
}
/**
* Pops a secondary frame off the stack, does a re-enter,
* or a step-into.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void doSecondaryStep() throws DebugException {
setStepRequest(createStepRequest());
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
invokeThread();
}
/**
* Creates and returns a step request. If there
* are no more frames to drop, a re-enter request
* is made. If the re-enter is complete, a step-into
* request is created.
*
* @return step request
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepRequest createStepRequest() throws DebugException {
int num = getFramesToDrop();
if (num > 0) {
return super.createStepRequest();
} else if (num == 0) {
try {
EventRequestManager erm= getEventRequestManager();
StepRequest request = ((org.eclipse.jdi.hcr.EventRequestManager) erm).createReenterStepRequest(getUnderlyingThread());
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_creating_step_request"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
} else if (num == -1) {
try {
StepRequest request = getEventRequestManager().createStepRequest(getUnderlyingThread(), StepRequest.STEP_LINE, StepRequest.STEP_INTO);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.getString("JDIThread.exception_creating_step_request"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
}
// this line will never be executed, as the try block
// will either return, or the catch block with throw
// an exception
return null;
}
}
}