blob: 19216a913ef285c6c33af276038c0ad68d91853a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 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
* BEA - Daniel R Somerfield - Bug 89643
* Jesper Steen Moller - Enhancement 254677 - filter getters/setters
* Jesper Steen Møller <jesper@selskabet.org> - Bug 430839
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IStep;
import org.eclipse.debug.core.model.IStepFilter;
import org.eclipse.debug.core.model.ISuspendResume;
import org.eclipse.debug.core.model.ITerminate;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.debug.core.IEvaluationRunnable;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaBreakpointListener;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaThreadGroup;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.breakpoints.ConditionalBreakpointHandler;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint;
import com.ibm.icu.text.MessageFormat;
import com.sun.jdi.BooleanValue;
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.InterfaceType;
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.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
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.event.Event;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.MethodExitEvent;
import com.sun.jdi.event.StepEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ExceptionRequest;
import com.sun.jdi.request.MethodEntryRequest;
import com.sun.jdi.request.MethodExitRequest;
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 default Java stratum
*/
private static final String JAVA_STRATUM_CONSTANT = "Java"; //$NON-NLS-1$
/**
* Constant for the name of the main thread group.
*/
private static final String MAIN_THREAD_GROUP = "main"; //$NON-NLS-1$
/**
* @since 3.5
*/
public static final int RESUME_QUIET = 500;
/**
* @since 3.5
*/
public static final int SUSPEND_QUIET = 501;
/**
* Status code indicating that a request to suspend this thread has timed
* out
*/
public static final int SUSPEND_TIMEOUT = 161;
/**
* Underlying thread.
*/
private ThreadReference fThread;
/**
* Cache of previous name, used in case thread is garbage collected.
*/
private String fPreviousName;
/**
* Collection of stack frames
*/
private List<IJavaStackFrame> 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;
/**
* Whether terminated.
*/
private boolean fTerminated;
/**
* Whether this thread is a system thread.
*/
private boolean fIsSystemThread;
/**
* Whether this thread is a daemon thread
*
* @since 3.3
*/
private boolean fIsDaemon = false;
/**
* The collection of breakpoints that caused the last suspend, or an empty
* collection if the thread is not suspended or was not suspended by any
* breakpoint(s).
*/
private List<IBreakpoint> fCurrentBreakpoints = new ArrayList<IBreakpoint>(2);
/**
* Non-null when this thread is executing an evaluation runnable. An
* evaluation may involve a series of method invocations.
*/
private IEvaluationRunnable fEvaluationRunnable = null;
/**
* Whether this thread was manually suspended during an evaluation.
*/
private boolean fEvaluationInterrupted = false;
/**
* <code>true</code> when there has been a request to suspend this thread
* via {@link #suspend()}. Remains <code>true</code> until there is a
* request to resume this thread via {@link #resume()}.
*/
private boolean fClientSuspendRequest = false;
/**
* Whether this thread is currently invoking a method. Nested method
* invocations cannot be performed.
*/
private boolean fIsInvokingMethod = false;
/**
* Lock used to wait for method invocations to complete.
*/
private Object fInvocationLock = new Object();
/**
* Lock used to wait for evaluations to complete.
*/
private Object fEvaluationLock = new Object();
/**
* Whether or not this thread is currently honoring breakpoints. This flag
* allows breakpoints to be disabled during evaluations.
*/
private boolean fHonorBreakpoints = true;
/**
* Whether a suspend vote is currently in progress. While voting this thread
* does not allow other breakpoints to be hit.
*
* @since 3.5
*/
private boolean fSuspendVoteInProgress = 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;
/**
* Whether or not this thread is currently suspending (user-requested).
*/
private boolean fIsSuspending = false;
private ThreadJob fAsyncJob;
private ThreadJob fRunningAsyncJob;
/**
* The current MethodExitRequest if a step-return or step-over is in progress.
*/
private MethodExitRequest fCurrentMethodExitRequest;
/**
* The current ExceptionRequest if a step-return or step-over is in progress.
*/
private ExceptionRequest fCurrentExceptionRequest;
/**
* The current MethodEntryRequest if a step-over is in progress.
*/
private MethodEntryRequest fCurrentMethodEntryRequest;
/**
* Method for which a result value is expected
*/
private Method fStepResultMethod;
/**
* The location if a step-over is in progress.
*/
private Location fStepOverLocation;
/**
* The depth if a step-over is in progress.
*/
private int fStepOverFrameCount;
/**
* Candidate for depth of stack that will be returned values belong to. Is copied to fStepReturnTargetDepth only when step-return is actually
* observed
*/
private int fStepReturnTargetFrameCount;
private StepResult fStepResultCandidate;
StepResult fStepResult;
/**
* 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
* @exception ObjectCollectedException
* if the underlying thread has been garbage collected and
* cannot be properly initialized
*/
public JDIThread(JDIDebugTarget target, ThreadReference thread)
throws ObjectCollectedException {
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>
*
* @exception ObjectCollectedException
* if the thread has been garbage collected and cannot be
* initialized
*/
protected void initialize() throws ObjectCollectedException {
fStackFrames = new ArrayList<IJavaStackFrame>();
// 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;
}
if (underlyingException instanceof ObjectCollectedException) {
throw (ObjectCollectedException) underlyingException;
}
logError(e);
}
try {
determineIfDaemonThread();
} 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);
}
try {
ThreadGroupReference group = getUnderlyingThreadGroup();
// might already be terminated
if (group != null) {
getJavaDebugTarget().addThreadGroup(group);
}
} catch (DebugException e1) {
}
// state
setTerminated(false);
setRunning(false);
try {
// see bug 30816
if (fThread.status() == ThreadReference.THREAD_STATUS_UNKNOWN) {
setRunning(true);
return;
}
} catch (VMDisconnectedException e) {
disconnected();
return;
} catch (ObjectCollectedException e) {
throw e;
} catch (RuntimeException e) {
logError(e);
}
try {
// This may be a transient suspend state (for example, a thread is
// handling a
// class prepare event quietly). The class prepare event handler
// will notify
// this thread when it resumes
setRunning(!fThread.isSuspended());
} catch (VMDisconnectedException e) {
disconnected();
return;
} catch (ObjectCollectedException e) {
throw e;
} catch (RuntimeException e) {
logError(e);
}
}
/**
* Adds the given breakpoint to the list of breakpoints this thread is
* suspended at
*
* @param bp
* the breakpoint to add to the listing
*/
protected void addCurrentBreakpoint(IBreakpoint bp) {
fCurrentBreakpoints.add(bp);
}
/**
* Removes the given breakpoint from the list of breakpoints this thread is
* suspended at (called when a breakpoint is deleted, in case we are
* suspended at that breakpoint)
*
* @param bp
* the breakpoint to remove from the listing
*/
protected void removeCurrentBreakpoint(IBreakpoint bp) {
fCurrentBreakpoints.remove(bp);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IThread#getBreakpoints()
*/
@Override
public synchronized IBreakpoint[] getBreakpoints() {
return fCurrentBreakpoints
.toArray(new IBreakpoint[fCurrentBreakpoints.size()]);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#canResume()
*/
@Override
public boolean canResume() {
return isSuspended()
&& (!isPerformingEvaluation() || isInvokingMethod())
&& !isSuspendVoteInProgress()
|| getDebugTarget().isSuspended();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#canSuspend()
*/
@Override
public boolean canSuspend() {
return !isSuspended()
|| (isPerformingEvaluation() && !isInvokingMethod())
|| isSuspendVoteInProgress();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ITerminate#canTerminate()
*/
@Override
public boolean canTerminate() {
return getDebugTarget().canTerminate();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#canStepInto()
*/
@Override
public boolean canStepInto() {
return canStep();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#canStepOver()
*/
@Override
public boolean canStepOver() {
return canStep();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#canStepReturn()
*/
@Override
public boolean canStepReturn() {
return canStep();
}
/**
* Returns whether this thread is in a valid state to step.
*
* @return whether this thread is in a valid state to step
*/
protected boolean canStep() {
try {
return isSuspended()
&& (!isPerformingEvaluation() || isInvokingMethod())
&& !isSuspendVoteInProgress() && !isStepping()
&& getTopStackFrame() != null
&& !getJavaDebugTarget().isPerformingHotCodeReplace();
} catch (DebugException e) {
return false;
}
}
/**
* 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.JDIThread_exception_determining_if_system_thread,
e.toString()), e);
// execution will not reach this line, as
// #targetRequestFailed will throw an exception
return;
}
if (tgn != null && tgn.equals(MAIN_THREAD_GROUP)) {
fIsSystemThread = false;
break;
}
}
}
/**
* Determines whether this is a daemon thread.
*
* @throws DebugException
* on failure
*/
protected void determineIfDaemonThread() throws DebugException {
fIsDaemon = false;
try {
ReferenceType referenceType = getUnderlyingThread().referenceType();
Field field = referenceType.fieldByName("daemon"); //$NON-NLS-1$
if (field == null) {
field = referenceType.fieldByName("isDaemon"); //$NON-NLS-1$
}
if (field != null) {
if (field.signature().equals(Signature.SIG_BOOLEAN)) {
Value value = getUnderlyingThread().getValue(field);
if (value instanceof BooleanValue) {
fIsDaemon = ((BooleanValue) value).booleanValue();
}
}
}
} catch (ObjectCollectedException oce) {/*
* do nothing thread does not
* exist
*/
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_47, e);
}
}
/**
* NOTE: this method returns a copy of this thread's stack frames.
*
* @see IThread#getStackFrames()
*/
@Override
public synchronized IStackFrame[] getStackFrames() throws DebugException {
List<IJavaStackFrame> list = computeStackFrames();
return list.toArray(new IStackFrame[list.size()]);
}
/**
* @see #computeStackFrames()
*
* @param refreshChildren
* whether or not this method should request new stack frames
* from the VM
* @return the list of stackframes
* @throws DebugException
* if an exception occurs retrieving frames
*/
protected synchronized List<IJavaStackFrame> computeStackFrames(boolean refreshChildren)
throws DebugException {
if (isSuspended()) {
if (isTerminated()) {
fStackFrames.clear();
} else if (refreshChildren) {
List<StackFrame> frames = getUnderlyingFrames();
int oldSize = fStackFrames.size();
if (oldSize > 0) {
((JDIStackFrame) fStackFrames.get(0)).setIsTop(false);
}
int newSize = frames.size();
int discard = oldSize - newSize; // number of old frames to
// discard, if any
for (int i = 0; i < discard; i++) {
JDIStackFrame invalid = (JDIStackFrame) fStackFrames
.remove(0);
invalid.bind(null, -1);
}
int newFrames = newSize - oldSize; // number of frames to
// create, if any
int depth = oldSize;
for (int i = newFrames - 1; i >= 0; i--) {
fStackFrames.add(0, new JDIStackFrame(this,
frames.get(i), depth));
depth++;
}
int numToRebind = Math.min(newSize, oldSize); // number of
// frames to
// attempt to
// re-bind
int offset = newSize - 1;
for (depth = 0; depth < numToRebind; depth++) {
JDIStackFrame oldFrame = (JDIStackFrame) fStackFrames
.get(offset);
StackFrame frame = frames.get(offset);
JDIStackFrame newFrame = oldFrame.bind(frame, depth);
if (newFrame != oldFrame) {
fStackFrames.set(offset, newFrame);
}
offset--;
}
if (newSize > 0) {
((JDIStackFrame) fStackFrames.get(0)).setIsTop(true);
}
}
fRefreshChildren = false;
} else {
return Collections.EMPTY_LIST;
}
return fStackFrames;
}
/**
* 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>
*/
public synchronized List<IJavaStackFrame> computeStackFrames() throws DebugException {
return computeStackFrames(fRefreshChildren);
}
/**
* This method differs from computeStackFrames() in that it always requests
* new stack frames from the VM. As this is an expensive operation, this
* method should only be used by clients who know for certain that the stack
* frames on the VM have changed.
*
* @see JDIThread#computeStackFrames()
* @return the listing of stackframes or an empty list, never
* <code>null</code>
* @throws DebugException
* if an exception occurs retrieving the stackframes
*/
public List<IJavaStackFrame> computeNewStackFrames() throws DebugException {
return computeStackFrames(true);
}
private List<StackFrame> getUnderlyingFrames() throws DebugException {
if (!isSuspended()) {
// Checking isSuspended here eliminates a race condition in resume
// between the time stack frames are preserved and the time the
// underlying thread is actually resumed.
requestFailed(
JDIDebugModelMessages.JDIThread_Unable_to_retrieve_stack_frame___thread_not_suspended__1,
null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
try {
return fThread.frames();
} catch (IncompatibleThreadStateException e) {
requestFailed(
JDIDebugModelMessages.JDIThread_Unable_to_retrieve_stack_frame___thread_not_suspended__1,
e, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_stack_frames_2,
e.toString()), e);
} catch (InternalError e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_stack_frames_2,
e.toString()), e);
}
// 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 fThread.frameCount();
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_frame_count,
e.toString()), e);
} catch (IncompatibleThreadStateException e) {
requestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_frame_count,
e.toString()), e,
IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
// execution will not reach here - try block will either
// return or exception will be thrown
return -1;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaThread#runEvaluation(org.eclipse.jdt.
* debug.core.IEvaluationRunnable,
* org.eclipse.core.runtime.IProgressMonitor, int, boolean)
*/
@Override
public void runEvaluation(IEvaluationRunnable evaluation,
IProgressMonitor monitor, int evaluationDetail,
boolean hitBreakpoints) throws DebugException {
if (isPerformingEvaluation()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations,
null, IJavaThread.ERR_NESTED_METHOD_INVOCATION); //
}
if (!canRunEvaluation()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Evaluation_failed___thread_not_suspended,
null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
synchronized (fEvaluationLock) {
fEvaluationRunnable = evaluation;
fHonorBreakpoints = hitBreakpoints;
}
boolean quiet = isSuspendVoteInProgress();
if (quiet) {
// evaluations are quiet when a suspend vote is in progress
// (conditional breakpoints, etc.).
fireEvent(new DebugEvent(this, DebugEvent.MODEL_SPECIFIC,
RESUME_QUIET));
} else {
fireResumeEvent(evaluationDetail);
}
// save and restore current breakpoint information - bug 30837
IBreakpoint[] breakpoints = getBreakpoints();
ISchedulingRule rule = null;
if (evaluationDetail == DebugEvent.EVALUATION_IMPLICIT) {
rule = getThreadRule();
}
try {
if (rule != null) {
Job.getJobManager().beginRule(rule, monitor);
}
if (monitor == null || !monitor.isCanceled()) {
evaluation.run(this, monitor);
}
} catch (DebugException e) {
throw e;
} finally {
if (rule != null) {
Job.getJobManager().endRule(rule);
}
synchronized (fEvaluationLock) {
fEvaluationRunnable = null;
fHonorBreakpoints = true;
fEvaluationLock.notifyAll();
}
if (getBreakpoints().length == 0 && breakpoints.length > 0) {
for (IBreakpoint breakpoint : breakpoints) {
addCurrentBreakpoint(breakpoint);
}
}
if (quiet) {
fireEvent(new DebugEvent(this, DebugEvent.MODEL_SPECIFIC,
SUSPEND_QUIET));
} else {
fireSuspendEvent(evaluationDetail);
}
if (fEvaluationInterrupted
&& (fAsyncJob == null || fAsyncJob.isEmpty())
&& (fRunningAsyncJob == null || fRunningAsyncJob.isEmpty())) {
// @see bug 31585:
// When an evaluation was interrupted & resumed, the launch view
// does
// not update properly. It cannot know when it is safe to
// display frames
// since it does not know about queued evaluations. Thus, when
// the queue
// is empty, we fire a change event to force the view to update.
fEvaluationInterrupted = false;
fireChangeEvent(DebugEvent.CONTENT);
}
}
}
/**
* Returns whether this thread is in a valid state to run an evaluation.
*
* @return whether this thread is in a valid state to run an evaluation
*/
protected boolean canRunEvaluation() {
// NOTE similar to #canStep, except an evaluation can be run when in the
// middle of
// a step (conditional breakpoint, breakpoint listener, etc.)
try {
return isSuspended()
&& !(isPerformingEvaluation() || isInvokingMethod())
&& getTopStackFrame() != null
&& !getJavaDebugTarget().isPerformingHotCodeReplace();
} catch (DebugException e) {
return false;
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaThread#queueRunnable(java.lang.Runnable)
*/
@Override
public void queueRunnable(Runnable evaluation) {
if (fAsyncJob == null) {
fAsyncJob = new ThreadJob(this);
}
fAsyncJob.addRunnable(evaluation);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#terminateEvaluation()
*/
@Override
public void terminateEvaluation() throws DebugException {
synchronized (fEvaluationLock) {
if (canTerminateEvaluation()) {
fEvaluationInterrupted = true;
((ITerminate) fEvaluationRunnable).terminate();
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#canTerminateEvaluation()
*/
@Override
public boolean canTerminateEvaluation() {
synchronized (fEvaluationLock) {
return fEvaluationRunnable instanceof ITerminate;
}
}
/**
* 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)
* @param invokeNonvirtual
* if the super-class method should be invoked
* @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>
* <li>This thread is not suspended (status code
* <code>IJavaThread.ERR_THREAD_NOT_SUSPENDED</code>)</li>
* <li>This thread is already invoking a method (status code
* <code>IJavaThread.ERR_NESTED_METHOD_INVOCATION</code>)</li>
* <li>This thread is not suspended by a JDI request (status
* code
* <code>IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE</code>)</li>
* </ul>
*/
protected Value invokeMethod(ClassType receiverClass,
ObjectReference receiverObject, Method method, List<? extends Value> args,
boolean invokeNonvirtual) throws DebugException {
if (receiverClass != null && receiverObject != null) {
throw new IllegalArgumentException(
JDIDebugModelMessages.JDIThread_can_only_specify_one_receiver_for_a_method_invocation);
}
Value result = null;
int timeout = getRequestTimeout();
try {
// this is synchronized such that any other operation that
// might be resuming this thread has a chance to complete before
// we determine if it is safe to continue with a method invocation.
// See bugs 6518, 14069
synchronized (this) {
if (!isSuspended()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Evaluation_failed___thread_not_suspended,
null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
if (isInvokingMethod()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations,
null, IJavaThread.ERR_NESTED_METHOD_INVOCATION);
}
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
setRunning(true);
setInvokingMethod(true);
}
preserveStackFrames();
int flags = ClassType.INVOKE_SINGLE_THREADED;
if (invokeNonvirtual) {
// Superclass method invocation must be performed non-virtual.
flags |= ObjectReference.INVOKE_NONVIRTUAL;
}
if (receiverClass == null) {
result = receiverObject.invokeMethod(fThread, method, args,
flags);
} else {
result = receiverClass.invokeMethod(fThread, method, args,
flags);
}
} catch (InvalidTypeException e) {
invokeFailed(e, timeout);
} catch (ClassNotLoadedException e) {
invokeFailed(e, timeout);
} catch (IncompatibleThreadStateException e) {
invokeFailed(
JDIDebugModelMessages.JDIThread_Thread_must_be_suspended_by_step_or_breakpoint_to_perform_method_invocation_1,
IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE, e, timeout);
} catch (InvocationException e) {
invokeFailed(e, timeout);
} catch (RuntimeException e) {
invokeFailed(e, timeout);
}
invokeComplete(timeout);
return result;
}
/**
* Invokes a method on the target, in this thread, and returns the result.
* 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 receiverInterface
* the class in the target representing the receiver of a static
* 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>
* <li>This thread is not suspended (status code
* <code>IJavaThread.ERR_THREAD_NOT_SUSPENDED</code>)</li>
* <li>This thread is already invoking a method (status code
* <code>IJavaThread.ERR_NESTED_METHOD_INVOCATION</code>)</li>
* <li>This thread is not suspended by a JDI request (status
* code
* <code>IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE</code>)</li>
* </ul>
*/
protected Value invokeMethod(InterfaceType receiverInterface,
Method method, List<? extends Value> args) throws DebugException {
Value result = null;
int timeout = getRequestTimeout();
try {
// this is synchronized such that any other operation that
// might be resuming this thread has a chance to complete before
// we determine if it is safe to continue with a method invocation.
// See bugs 6518, 14069
synchronized (this) {
if (!isSuspended()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Evaluation_failed___thread_not_suspended,
null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
if (isInvokingMethod()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations,
null, IJavaThread.ERR_NESTED_METHOD_INVOCATION);
}
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
setRunning(true);
setInvokingMethod(true);
}
preserveStackFrames();
int flags = ClassType.INVOKE_SINGLE_THREADED;
result = receiverInterface.invokeMethod(fThread, method, args,
flags);
} catch (InvalidTypeException e) {
invokeFailed(e, timeout);
} catch (ClassNotLoadedException e) {
invokeFailed(e, timeout);
} catch (IncompatibleThreadStateException e) {
invokeFailed(
JDIDebugModelMessages.JDIThread_Thread_must_be_suspended_by_step_or_breakpoint_to_perform_method_invocation_1,
IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE, e, timeout);
} catch (InvocationException e) {
invokeFailed(e, timeout);
} catch (RuntimeException e) {
invokeFailed(e, timeout);
}
invokeComplete(timeout);
return result;
}
/**
* 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<? extends Value> args) throws DebugException {
if (isInvokingMethod()) {
requestFailed(
JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations_2,
null);
}
ObjectReference result = null;
int timeout = getRequestTimeout();
try {
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
setRunning(true);
setInvokingMethod(true);
preserveStackFrames();
result = receiverClass.newInstance(fThread, 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 {
invokeFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_invoking_method,
e.toString()),
DebugException.TARGET_REQUEST_FAILED, e, restoreTimeout);
}
/**
* Called when an invocation fails. Performs cleanup and throws an
* exception.
*
* @param message
* error message
* @param code
* status code
* @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(String message, int code, Throwable e,
int restoreTimeout) throws DebugException {
invokeComplete(restoreTimeout);
requestFailed(message, e, code);
}
/**
* 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>
* </ul>
*
* @param restoreTimeout
* the communication timeout value, in milliseconds, that should
* be reset
* @see #invokeMethod(ClassType, ObjectReference, Method, List, boolean)
* @see #newInstance(ClassType, Method, List)
*/
protected synchronized void invokeComplete(int restoreTimeout) {
setInvokingMethod(false);
setRunning(false);
setRequestTimeout(restoreTimeout);
// update preserved stack frames
try {
computeStackFrames();
} catch (DebugException e) {
logError(e);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IThread#getName()
*/
@Override
public String getName() throws DebugException {
try {
fPreviousName = fThread.name();
} catch (RuntimeException e) {
// Don't bother reporting the exception when retrieving the name
// (bug 30785 & bug 33276)
if (e instanceof ObjectCollectedException) {
if (fPreviousName == null) {
fPreviousName = JDIDebugModelMessages.JDIThread_garbage_collected_1;
}
} else if (e instanceof VMDisconnectedException) {
if (fPreviousName == null) {
fPreviousName = JDIDebugModelMessages.JDIThread_42;
}
} else {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_thread_name,
e.toString()), e);
}
}
return fPreviousName;
}
/**
* Returns the priority from the underlying {@link ReferenceType}, failing
* that the backing {@link Value} for the underlying {@link ThreadReference}
* is consulted
*
* @return the priority from the backing {@link ReferenceType} or
* {@link Value}
* @throws DebugException
* if an exception occurs retrieving the priority
* @see IThread#getPriority
*/
@Override
public int getPriority() throws DebugException {
// to get the priority, we must get the value from the "priority" field
Field p = null;
try {
p = fThread.referenceType().fieldByName("priority"); //$NON-NLS-1$
if (p == null) {
requestFailed(
JDIDebugModelMessages.JDIThread_no_priority_field, null);
}
Value v = fThread.getValue(p);
if (v instanceof IntegerValue) {
return ((IntegerValue) v).value();
}
requestFailed(
JDIDebugModelMessages.JDIThread_priority_not_an_integer,
null);
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_thread_priority,
e.toString()), e);
}
// execution will not fall through to this line, as
// #targetRequestFailed or #requestFailed will throw
// an exception
return -1;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IThread#getTopStackFrame()
*/
@Override
public synchronized IStackFrame getTopStackFrame() throws DebugException {
List<IJavaStackFrame> c = computeStackFrames();
if (c.isEmpty()) {
return null;
}
return c.get(0);
}
/**
* A breakpoint has suspended execution of this thread. Aborts any step
* currently in process and notifies listeners of the breakpoint to allow a
* vote to determine if the thread should suspend.
*
* @param breakpoint
* the breakpoint that caused the suspend
* @param suspendVote
* current vote before listeners are notified (for example, if a
* step request happens at the same location as a breakpoint, the
* step may have voted to suspend already - this allows a
* conditional breakpoint to avoid evaluation)
* @return whether this thread suspended
*/
public boolean handleSuspendForBreakpoint(JavaBreakpoint breakpoint,
boolean suspendVote) {
int policy = IJavaBreakpoint.SUSPEND_THREAD;
synchronized (this) {
if (fClientSuspendRequest) {
// a request to suspend has overridden the breakpoint request -
// suspend and
// ignore the breakpoint
return true;
}
fSuspendVoteInProgress = true;
addCurrentBreakpoint(breakpoint);
try {
policy = breakpoint.getSuspendPolicy();
} catch (CoreException e) {
logError(e);
setRunning(true);
return false;
}
// update state to suspended but don't actually
// suspend unless a registered listener agrees
if (policy == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget) getDebugTarget())
.prepareToSuspendByBreakpoint(breakpoint);
} else {
setRunning(false);
}
}
try {
if (!(breakpoint.isTriggerPoint())) {
if (DebugPlugin.getDefault().getBreakpointManager().hasActiveTriggerPoints()){
fSuspendVoteInProgress = false;
return false;
}
}
}
catch (CoreException e) {
e.printStackTrace();
}
// Evaluate breakpoint condition (if any). The condition is evaluated
// regardless of the current suspend vote status, since breakpoint
// listeners
// should only be notified when a condition is true (i.e. when the
// breakpoint
// is really hit).
if (breakpoint instanceof JavaLineBreakpoint) {
JavaLineBreakpoint lbp = (JavaLineBreakpoint) breakpoint;
// evaluate condition unless we're in an evaluation already (bug
// 284022)
if (lbp.hasCondition() && !isPerformingEvaluation()) {
ConditionalBreakpointHandler handler = new ConditionalBreakpointHandler();
int vote = handler.breakpointHit(this, breakpoint);
if (vote == IJavaBreakpointListener.DONT_SUSPEND) {
// condition is false, breakpoint is not hit
synchronized (this) {
fSuspendVoteInProgress = false;
return false;
}
}
if (handler.hasErrors()) {
// there were errors, suspend and do not notify listeners of
// hit since
// they were already notified of compilation/runtime errors
synchronized (this) {
fSuspendVoteInProgress = false;
return true;
}
}
}
}
// poll listeners without holding lock on thread
boolean suspend = true;
try {
suspend = JDIDebugPlugin.getDefault().fireBreakpointHit(this,
breakpoint);
} finally {
synchronized (this) {
fSuspendVoteInProgress = false;
if (fClientSuspendRequest) {
// if a client has requested a suspend, then override the
// vote to suspend
suspend = true;
}
}
}
return suspend;
}
/**
* Called after an event set with a breakpoint is done being processed.
* Updates thread state based on the result of handling the event set.
* Aborts any step in progress and fires a suspend event is suspending.
*
* @param breakpoint
* the breakpoint that was hit
* @param suspend
* whether to suspend
* @param queue
* whether to queue events or fire immediately
* @param set
* event set handling is associated with
*/
public void completeBreakpointHandling(JavaBreakpoint breakpoint,
boolean suspend, boolean queue, EventSet set) {
synchronized (this) {
try {
int policy = breakpoint.getSuspendPolicy();
// suspend or resume
if (suspend) {
if (policy == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget) getDebugTarget())
.suspendedByBreakpoint(breakpoint, false, set);
}
abortStep();
if (queue) {
queueSuspendEvent(DebugEvent.BREAKPOINT, set);
} else {
fireSuspendEvent(DebugEvent.BREAKPOINT);
}
} else {
if (policy == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget) getDebugTarget())
.cancelSuspendByBreakpoint(breakpoint);
} else {
setRunning(true);
// dispose cached stack frames so we re-retrieve on the
// next breakpoint
preserveStackFrames();
}
}
} catch (CoreException e) {
logError(e);
setRunning(true);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IStep#isStepping()
*/
@Override
public boolean isStepping() {
return getPendingStepHandler() != null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#isSuspended()
*/
@Override
public boolean isSuspended() {
return !fRunning && !fTerminated;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#isSystemThread()
*/
@Override
public boolean isSystemThread() {
return fIsSystemThread;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#isDaemon()
*/
@Override
public boolean isDaemon() throws DebugException {
return fIsDaemon;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#getThreadGroupName()
*/
@Override
public String getThreadGroupName() throws DebugException {
if (fThreadGroupName == null) {
ThreadGroupReference tgr = getUnderlyingThreadGroup();
// bug# 20370
if (tgr == null) {
return null;
}
try {
fThreadGroupName = tgr.name();
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_thread_group_name,
e.toString()), e);
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
}
return fThreadGroupName;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ITerminate#isTerminated()
*/
@Override
public boolean isTerminated() {
return fTerminated;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#isOutOfSynch()
*/
@Override
public synchronized boolean isOutOfSynch() throws DebugException {
if (isSuspended() && ((JDIDebugTarget) getDebugTarget()).hasHCRFailed()) {
List<IJavaStackFrame> frames = computeStackFrames();
for(IJavaStackFrame frame : frames) {
if(((JDIStackFrame)frame).isOutOfSynch()) {
return true;
}
}
return false;
}
// If the thread is not suspended, there's no way to
// say for certain that it is running out of synch code
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#mayBeOutOfSynch()
*/
@Override
public boolean mayBeOutOfSynch() {
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;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#resume()
*/
@Override
public synchronized void resume() throws DebugException {
if (!isSuspended() && getDebugTarget().isSuspended()) {
getDebugTarget().resume();
} else {
fClientSuspendRequest = false;
resumeThread(true);
}
}
/**
*
* Updates the state of this thread, but only fires notification to
* listeners if <code>fireNotification</code> is <code>true</code>.
*
* @see ISuspendResume#resume()
* @param fireNotification
* if a resume event should be fired
* @throws DebugException
* if an exception occurs trying to resume the thread
*/
private synchronized void resumeThread(boolean fireNotification)
throws DebugException {
if (!isSuspended() || (isPerformingEvaluation() && !isInvokingMethod())) {
return;
}
try {
setRunning(true);
clearStepReturnResult();
if (fireNotification) {
fireResumeEvent(DebugEvent.CLIENT_REQUEST);
}
preserveStackFrames();
fThread.resume();
} catch (VMDisconnectedException e) {
disconnected();
} catch (RuntimeException e) {
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_resuming,
e.toString()), e);
}
}
/**
* Sets whether this thread is currently executing. When set to
* <code>true</code>, this thread's current breakpoints are cleared.
*
* @param running
* whether this thread is executing
*/
protected void setRunning(boolean running) {
fRunning = running;
if (running) {
fCurrentBreakpoints.clear();
}
}
private void clearStepReturnResult() {
fStepResult = null;
}
/**
* 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 synchronized void preserveStackFrames() {
fRefreshChildren = true;
for(IJavaStackFrame frame : fStackFrames) {
((JDIStackFrame)frame).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 synchronized void disposeStackFrames() {
fStackFrames.clear();
fRefreshChildren = true;
}
/**
* This method is synchronized, such that the step request begins before a
* background evaluation can be performed.
*
* @see IStep#stepInto()
*/
@Override
public void stepInto() throws DebugException {
synchronized (this) {
if (!canStepInto()) {
return;
}
}
StepHandler handler = createStepIntoHandler();
handler.step();
}
/**
* This method is synchronized, such that the step request begins before a
* background evaluation can be performed.
*
* @see IStep#stepOver()
*/
@Override
public void stepOver() throws DebugException {
synchronized (this) {
if (!canStepOver()) {
return;
}
}
StepHandler handler = createStepOverHandler();
handler.step();
}
/**
* This method is synchronized, such that the step request begins before a
* background evaluation can be performed.
*
* @see IStep#stepReturn()
*/
@Override
public void stepReturn() throws DebugException {
synchronized (this) {
if (!canStepReturn()) {
return;
}
}
StepHandler handler = createStepReturnHandler();
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.
*
* @param location
* the location to consider
* @return <code>true</code> if we should do an extra step,
* <code>false</code> otherwise
* @throws DebugException
* if an exception occurs
*/
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;
}
/**
* Determines if a user did a step into and stepped through filtered code.
* In this case, do a step return if the user has requested not to step thru
* to an unfiltered location.
*
* @return <code>true</code> if we should do a step return
* @throws DebugException
* if an exception occurs
*/
protected boolean shouldDoStepReturn() throws DebugException {
if (getOriginalStepKind() == StepRequest.STEP_INTO) {
if ((getOriginalStepStackDepth() + 1) < getUnderlyingFrameCount()) {
return true;
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ISuspendResume#suspend()
*/
@Override
public void suspend() throws DebugException {
// prepare for the suspend request
prepareForClientSuspend();
synchronized (this) {
try {
// Abort any pending step request
abortStep();
suspendUnderlyingThread();
} catch (RuntimeException e) {
fClientSuspendRequest = false;
setRunning(true);
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_suspending,
e.toString()), e);
}
}
}
/**
* Prepares to suspend this thread as requested by a client. Terminates any
* current evaluation (to stop after next instruction). Waits for any method
* invocations to complete.
*
* @throws DebugException
* if thread does not suspend before timeout
*/
protected void prepareForClientSuspend() throws DebugException {
// note that a suspend request has started
synchronized (this) {
// this will abort notification to pending breakpoint listeners
fClientSuspendRequest = true;
}
synchronized (fEvaluationLock) {
// terminate active evaluation, if any
if (fEvaluationRunnable != null) {
if (canTerminateEvaluation()) {
fEvaluationInterrupted = true;
((ITerminate) fEvaluationRunnable).terminate();
}
// wait for termination to complete
int timeout = Platform.getPreferencesService().getInt(
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugModel.PREF_REQUEST_TIMEOUT,
JDIDebugModel.DEF_REQUEST_TIMEOUT,
null);
try {
fEvaluationLock.wait(timeout);
} catch (InterruptedException e) {
}
if (fEvaluationRunnable != null) {
fClientSuspendRequest = false;
targetRequestFailed(JDIDebugModelMessages.JDIThread_1, null);
}
}
}
// first wait for any method invocation in progress to complete its
// method invocation
synchronized (fInvocationLock) {
if (isInvokingMethod()) {
int timeout = Platform.getPreferencesService().getInt(
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugModel.PREF_REQUEST_TIMEOUT,
JDIDebugModel.DEF_REQUEST_TIMEOUT,
null);
try {
fInvocationLock.wait(timeout);
} catch (InterruptedException e) {
}
if (isInvokingMethod()) {
// timeout waiting for invocation to complete, abort
fClientSuspendRequest = false;
targetRequestFailed(JDIDebugModelMessages.JDIThread_1, null);
}
}
}
}
/**
* Suspends the underlying thread asynchronously and fires notification when
* the underlying thread is suspended.
*/
protected synchronized void suspendUnderlyingThread() {
if (fIsSuspending) {
return;
}
if (isSuspended()) {
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
return;
}
fIsSuspending = true;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
fThread.suspend();
int timeout = Platform.getPreferencesService().getInt(
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugModel.PREF_REQUEST_TIMEOUT,
JDIDebugModel.DEF_REQUEST_TIMEOUT,
null);
long stop = System.currentTimeMillis() + timeout;
boolean suspended = isUnderlyingThreadSuspended();
while (System.currentTimeMillis() < stop && !suspended) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
suspended = isUnderlyingThreadSuspended();
if (suspended) {
break;
}
}
if (!suspended) {
IStatus status = new Status(
IStatus.ERROR,
JDIDebugPlugin.getUniqueIdentifier(),
SUSPEND_TIMEOUT,
MessageFormat.format(JDIDebugModelMessages.JDIThread_suspend_timeout, new Integer(timeout).toString()),
null);
IStatusHandler handler = DebugPlugin.getDefault()
.getStatusHandler(status);
if (handler != null) {
try {
handler.handleStatus(status, JDIThread.this);
} catch (CoreException e) {
}
}
}
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
} catch (RuntimeException exception) {
} finally {
fIsSuspending = false;
}
}
});
thread.setDaemon(true);
thread.start();
}
public boolean isUnderlyingThreadSuspended() {
return fThread.isSuspended();
}
/**
* Notifies this thread that it has been suspended due to a VM suspend.
*/
protected synchronized void suspendedByVM() {
setRunning(false);
}
/**
* Notifies this thread that is about to be resumed due to a VM resume.
*
* @throws DebugException
* if an exception occurs
*/
protected synchronized void resumedByVM() throws DebugException {
fClientSuspendRequest = false;
setRunning(true);
clearStepReturnResult();
preserveStackFrames();
// This method is called *before* the VM is actually resumed.
// To ensure that all threads will fully resume when the VM
// is resumed, make sure the suspend count of each thread
// is no greater than 1. @see Bugs 23328 and 27622
ThreadReference thread = fThread;
try {
while (thread.suspendCount() > 1) {
thread.resume();
}
} catch (ObjectCollectedException e) {
} catch (VMDisconnectedException e) {
disconnected();
} catch (RuntimeException e) {
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_resuming,
e.toString()), e); //
}
}
/**
* @see ITerminate#terminate()
*/
@Override
public void terminate() throws DebugException {
terminateEvaluation();
getDebugTarget().terminate();
}
/**
* Drops to the given stack frame
*
* @param frame
* the stack frame to try dropping 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 void dropToFrame(IStackFrame frame) throws DebugException {
JDIDebugTarget target = (JDIDebugTarget) getDebugTarget();
if (target.canPopFrames()) {
// JDK 1.4 support
try {
// Pop the drop frame and all frames above it
popFrame(frame);
stepInto();
} catch (RuntimeException exception) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_dropping_to_frame,
exception.toString()),
exception);
}
} else {
// J9 support
// This block is synchronized, such that the step request
// begins before a background evaluation can be performed.
synchronized (this) {
StepHandler handler = createDropToFrameHandler(frame);
handler.step();
}
}
}
protected void popFrame(IStackFrame frame) throws DebugException {
JDIDebugTarget target = (JDIDebugTarget) getDebugTarget();
if (target.canPopFrames()) {
// JDK 1.4 support
try {
// Pop the frame and all frames above it
StackFrame jdiFrame = null;
int desiredSize = fStackFrames.size()
- fStackFrames.indexOf(frame) - 1;
int lastSize = fStackFrames.size() + 1; // Set up to pass the
// first test
int size = fStackFrames.size();
while (size < lastSize && size > desiredSize) {
// Keep popping frames until the stack stops getting smaller
// or popFrame is gone.
// see Bug 8054
jdiFrame = ((JDIStackFrame) frame)
.getUnderlyingStackFrame();
preserveStackFrames();
fThread.popFrames(jdiFrame);
lastSize = size;
size = computeStackFrames().size();
}
} catch (IncompatibleThreadStateException exception) {
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_popping,
exception.toString()), exception);
} catch (InvalidStackFrameException exception) {
// InvalidStackFrameException can be thrown when all but the
// deepest frame were popped. Fire a changed notification
// in case this has occurred.
fireChangeEvent(DebugEvent.CONTENT);
targetRequestFailed(exception.toString(), exception);
} catch (RuntimeException exception) {
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_popping,
exception.toString()), exception);
}
}
}
/**
* 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.
*
* @param frame
* the stack frame to try and 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 void stepToFrame(IStackFrame frame) throws DebugException {
synchronized (this) {
if (!canStepReturn()) {
return;
}
}
StepHandler handler = createStepToFrameHandler(frame);
handler.step();
}
/**
* Aborts the current step, if any.
*/
protected void abortStep() {
StepHandler handler = getPendingStepHandler();
if (handler != null) {
handler.abort();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaThread#findVariable(java.lang.String)
*/
@Override
public IJavaVariable findVariable(String varName) throws DebugException {
if (isSuspended()) {
try {
IStackFrame[] stackFrames = getStackFrames();
for (IStackFrame stackFrame : stackFrames) {
IJavaStackFrame sf = (IJavaStackFrame) stackFrame;
IJavaVariable var = sf.findVariable(varName);
if (var != null) {
return var;
}
}
} catch (DebugException e) {
// if the thread has since resumed, return null (no need to
// report error)
if (e.getStatus().getCode() != IJavaThread.ERR_THREAD_NOT_SUSPENDED) {
throw e;
}
}
}
return null;
}
/**
* 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
*/
public 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 = fThread.threadGroup();
} catch (UnsupportedOperationException e) {
requestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_thread_group,
e.toString()), e);
// execution will not reach this line, as
// #requestFailed will throw an exception
return null;
} catch (VMDisconnectedException e) {
// ignore disconnect
return null;
} catch (ObjectCollectedException e) {
// ignore object collected
return null;
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_retrieving_thread_group,
e.toString()), e);
// execution will not reach this line, as
// #targetRequestFailed will throw an exception
return null;
}
}
return fThreadGroup;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#isPerformingEvaluation()
*/
@Override
public boolean isPerformingEvaluation() {
return fEvaluationRunnable != null;
}
/**
* Returns whether this thread is currently performing a method invocation
*
* @return if the thread is currently invoking a method
*/
public boolean isInvokingMethod() {
return fIsInvokingMethod;
}
/**
* Returns whether this thread is currently ignoring breakpoints.
*
* @return if the thread is currently ignoring breakpoints
*/
public boolean isIgnoringBreakpoints() {
return !fHonorBreakpoints || fSuspendVoteInProgress
|| hasClientRequestedSuspend();
}
/**
* Returns whether a client has requested the target/thread to suspend.
*
* @return whether a client has requested the target/thread to suspend
*/
public boolean hasClientRequestedSuspend() {
return fClientSuspendRequest;
}
/**
* Sets whether this thread is currently invoking a method. Notifies any
* threads waiting for the method invocation lock
*
* @param invoking
* whether this thread is currently invoking a method
*/
protected void setInvokingMethod(boolean invoking) {
synchronized (fInvocationLock) {
fIsInvokingMethod = invoking;
if (!invoking) {
fInvocationLock.notifyAll();
}
}
}
/**
* 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>
* Note this method does nothing if this thread has no 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 void step() throws DebugException {
ISchedulingRule rule = getThreadRule();
try {
Job.getJobManager().beginRule(rule, null);
JDIStackFrame top = (JDIStackFrame) getTopStackFrame();
if (top == null) {
return;
}
setOriginalStepKind(getStepKind());
Location location = top.getUnderlyingStackFrame().location();
setOriginalStepLocation(location);
setOriginalStepStackDepth(computeStackFrames().size());
setStepRequest(createStepRequest());
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
setRunning(true);
clearStepReturnResult();
preserveStackFrames();
fireResumeEvent(getStepDetail());
invokeThread();
} finally {
Job.getJobManager().endRule(rule);
}
}
/**
* 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 {
synchronized (JDIThread.this) {
fClientSuspendRequest = false;
}
fThread.resume();
} catch (RuntimeException e) {
stepEnd(null);
fireSuspendEvent(DebugEvent.STEP_END);
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_stepping,
e.toString()), e);
}
}
/**
* 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 {
return createStepRequest(getStepKind());
}
/**
* Creates and returns a step request of the specified kind.
*
* @param kind
* of <code>StepRequest.STEP_INTO</code>,
* <code>StepRequest.STEP_OVER</code>,
* <code>StepRequest.STEP_OUT</code>
* @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(int kind) throws DebugException {
EventRequestManager manager = getEventRequestManager();
if (manager == null) {
requestFailed(
JDIDebugModelMessages.JDIThread_Unable_to_create_step_request___VM_disconnected__1,
null);
}
try {
StepRequest request = manager.createStepRequest(fThread,
StepRequest.STEP_LINE, kind);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
attachFiltersToStepRequest(request);
request.enable();
if (manager.virtualMachine().canGetMethodReturnValues() && showStepResultIsEnabled()) {
if (fCurrentMethodExitRequest != null) {
removeJDIEventListener(this, fCurrentMethodExitRequest);
manager.deleteEventRequest(fCurrentMethodExitRequest);
fCurrentMethodExitRequest = null;
}
if (fCurrentExceptionRequest != null) {
removeJDIEventListener(this, fCurrentExceptionRequest);
manager.deleteEventRequest(fCurrentExceptionRequest);
fCurrentExceptionRequest = null;
}
if (fCurrentMethodEntryRequest != null) {
removeJDIEventListener(this, fCurrentMethodEntryRequest);
manager.deleteEventRequest(fCurrentMethodEntryRequest);
fCurrentMethodEntryRequest = null;
}
fStepResultCandidate = null;
List<IJavaStackFrame> frames = computeStackFrames();
int frameCount = 0;
StackFrame currentFrame = null;
if (!frames.isEmpty()) {
frameCount = frames.size();
currentFrame = ((JDIStackFrame) frames.get(0)).getUnderlyingStackFrame();
} else {
// can happen, e.g. when step filters are active.
if (fThread.isSuspended()) {
try {
// try to get the required info from the underlying object.
frameCount = fThread.frameCount();
currentFrame = fThread.frame(0);
}
catch (IncompatibleThreadStateException e) {
// cannot not happen because of the enclosing isSuspended() check.
e.printStackTrace();
}
}
}
if (currentFrame != null) {
MethodExitRequest methodExitRequest = manager.createMethodExitRequest();
methodExitRequest.addThreadFilter(fThread);
methodExitRequest.addClassFilter(currentFrame.location().declaringType());
if (manager.virtualMachine().canUseInstanceFilters()) {
ObjectReference thisObject = currentFrame.thisObject();
if (thisObject != null) {
methodExitRequest.addInstanceFilter(thisObject);
}
}
methodExitRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
methodExitRequest.enable();
fCurrentMethodExitRequest = methodExitRequest;
fStepResultMethod = currentFrame.location().method();
fStepReturnTargetFrameCount = frameCount - 1; // depth of the frame that is returned to
addJDIEventListener(this, methodExitRequest);
ExceptionRequest exceptionRequest = manager.createExceptionRequest(null, true, true);
exceptionRequest.addThreadFilter(fThread);
exceptionRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
exceptionRequest.enable();
fCurrentExceptionRequest = exceptionRequest;
addJDIEventListener(this, exceptionRequest);
if (kind == StepRequest.STEP_OVER) {
MethodEntryRequest methodEntryRequest = manager.createMethodEntryRequest();
methodEntryRequest.addThreadFilter(fThread);
methodEntryRequest.enable();
methodEntryRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
fCurrentMethodEntryRequest = methodEntryRequest;
fStepOverLocation = currentFrame.location();
fStepOverFrameCount = frameCount; // depth of the frame where the step-over is being done
addJDIEventListener(this, methodEntryRequest);
}
}
}
return request;
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_creating_step_request,
e.toString()), e);
}
// 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() {
try {
if (fCurrentMethodExitRequest != null) {
removeJDIEventListener(this, fCurrentMethodExitRequest);
EventRequestManager manager = getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(fCurrentMethodExitRequest);
}
fCurrentMethodExitRequest = null;
}
if (fCurrentExceptionRequest != null) {
removeJDIEventListener(this, fCurrentExceptionRequest);
EventRequestManager manager = getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(fCurrentExceptionRequest);
}
fCurrentExceptionRequest = null;
}
if (fCurrentMethodEntryRequest != null) {
removeJDIEventListener(this, fCurrentMethodEntryRequest);
EventRequestManager manager = getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(fCurrentMethodEntryRequest);
}
fCurrentMethodEntryRequest = null;
}
StepRequest req = getStepRequest();
if (req != null) {
removeJDIEventListener(this, req);
EventRequestManager manager = getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(req);
}
}
} catch (RuntimeException e) {
logError(e);
} finally {
setStepRequest(null);
}
}
/**
* If step filters are currently switched on and the current location is
* not a filtered location, set all active filters on the step request.
*
* @param request
* the request to augment
*/
protected void attachFiltersToStepRequest(StepRequest request) {
if (applyStepFilters() && isStepFiltersEnabled()) {
Location currentLocation = getOriginalStepLocation();
if (currentLocation == null
|| !JAVA_STRATUM_CONSTANT.equals(currentLocation
.declaringType().defaultStratum())) {
return;
}
// Removed the fix for bug 5587, to address bug 41510
// //check if the user has already stopped in a filtered
// location
// //is so do not filter @see bug 5587
// ReferenceType type= currentLocation.declaringType();
// String typeName= type.name();
String[] activeFilters = getJavaDebugTarget().getStepFilters();
// for (int i = 0; i < activeFilters.length; i++) {
// StringMatcher matcher = new StringMatcher(activeFilters[i],
// false, false);
// if (matcher.match(typeName)) {
// return;
// }
// }
if (activeFilters != null) {
for (String activeFilter : activeFilters) {
request.addClassExclusionFilter(activeFilter);
}
}
}
}
/**
* Returns whether this step handler should use step filters when
* creating its step request. By default, step filters can be used by
* any step request. Subclasses must override if/when required.
*
* @return whether this step handler should use step filters when
* creating its step request
*/
protected boolean applyStepFilters() {
return true;
}
/**
* 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 IJDIEventListener#handleEvent(Event, JDIDebugTarget, boolean,
* EventSet)
*/
@Override
public boolean handleEvent(Event event, JDIDebugTarget target,
boolean suspendVote, EventSet eventSet) {
try {
if (event instanceof MethodExitEvent) {
Method stepResultMethod = fStepResultMethod;
if (stepResultMethod != null) {
MethodExitEvent methodExitEvent = (MethodExitEvent) event;
if (methodExitEvent.location().method().equals(stepResultMethod)) {
fStepResultCandidate = new StepResult(fStepResultMethod, fStepReturnTargetFrameCount, methodExitEvent.returnValue(), true);
}
return true;
}
}
if (event instanceof ExceptionEvent) {
ExceptionEvent exceptionEvent = (ExceptionEvent) event;
fStepResultCandidate = new StepResult(fStepResultMethod, fStepReturnTargetFrameCount, exceptionEvent.exception(), false);
return true;
}
if (event instanceof MethodEntryEvent) {
removeJDIEventListener(this, fCurrentMethodEntryRequest);
EventRequestManager manager = getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(fCurrentMethodEntryRequest);
}
fCurrentMethodEntryRequest = null;
deleteStepRequest();
createSecondaryStepRequest(StepRequest.STEP_OUT);
return true;
}
StepEvent stepEvent = (StepEvent) event;
Location currentLocation = stepEvent.location();
if (fStepResultCandidate != null) {
fStepResult = fStepResultCandidate;
fStepResultMethod = null;
fStepReturnTargetFrameCount = -1;
fStepResultCandidate = null;
}
if (getStepKind() == StepRequest.STEP_OVER) {
Location stepOverLocation2 = fStepOverLocation;
if (stepOverLocation2 != null && fStepOverFrameCount >= 0) {
int underlyingFrameCount = getUnderlyingFrameCount();
if (underlyingFrameCount > fStepOverFrameCount) {
// sometimes a MethodEntryEvent does not stop the thread but is delivered with another one grouped
// in an event set. in this situation, multiple step-returns must be done.
deleteStepRequest();
createSecondaryStepRequest(StepRequest.STEP_OUT);
return true;
}
if (underlyingFrameCount == fStepOverFrameCount && stepOverLocation2.method().equals(currentLocation.method())) {
int lineNumber = stepOverLocation2.lineNumber();
if (lineNumber != -1 && lineNumber == currentLocation.lineNumber()) {
// line has not changed yet (probably returned from invocation with STEP_OUT)
deleteStepRequest();
createSecondaryStepRequest(StepRequest.STEP_OVER);
return true;
}
}
fStepOverLocation = null;
fStepOverFrameCount = -1;
}
}
if (!target.isStepThruFilters()) {
if (shouldDoStepReturn()) {
deleteStepRequest();
createSecondaryStepRequest(StepRequest.STEP_OUT);
return true;
}
}
// if the ending step location is filtered and we did not start
// from
// a filtered location, or if we're back where
// we started on a step into, do another step of the same kind
if (locationShouldBeFiltered(currentLocation)
|| shouldDoExtraStepInto(currentLocation)) {
setRunning(true);
deleteStepRequest();
createSecondaryStepRequest();
clearStepReturnResult();
return true;
// otherwise, we're done stepping
}
stepEnd(eventSet);
return false;
} catch (DebugException e) {
logError(e);
stepEnd(eventSet);
return false;
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.IJDIEventListener#eventSetComplete
* (com.sun.jdi.event.Event,
* org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, boolean)
*/
@Override
public void eventSetComplete(Event event, JDIDebugTarget target,
boolean suspend, EventSet eventSet) {
// do nothing
}
/**
* Returns <code>true</code> if the StepEvent's Location is a Method
* that the user has indicated (via the step filter preferences) should
* be filtered and the step was not initiated from a filtered location.
* Returns <code>false</code> otherwise.
*
* @param location
* the location to check
* @return if the given {@link Location} should be filtered
* @throws DebugException
* if an exception occurs
*/
protected boolean locationShouldBeFiltered(Location location)
throws DebugException {
if (applyStepFilters()) {
Location origLocation = getOriginalStepLocation();
if (origLocation != null) {
return !locationIsFiltered(origLocation.method(), true) && locationIsFiltered(location.method(), false);
}
}
return false;
}
/**
* Returns <code>true</code> if the StepEvent's Location is a Method
* that the user has indicated (via the step filter preferences) should
* be filtered. Returns <code>false</code> otherwise.
*
* @param method
* the {@link Method} location to check
* @param orig
* <code>true</code> if the {@link Method} {@link Location} is the JDI Location
* from which an original user-requested step began, <code>false</code> otherwise
* @return <code>true</code> if the {@link Method} {@link Location}
* should be filtered, <code>false</code> otherwise
*/
protected boolean locationIsFiltered(Method method, boolean orig) {
if (isStepFiltersEnabled()) {
JDIDebugTarget target = getJavaDebugTarget();
if ((target.isFilterStaticInitializers() && method.isStaticInitializer())
|| (target.isFilterSynthetics() && method.isSynthetic())
|| (target.isFilterConstructors() && method.isConstructor())
|| (target.isFilterGetters() && JDIMethod.isGetterMethod(method))
|| (target.isFilterSetters() && JDIMethod.isSetterMethod(method))) {
return true;
}
if(!orig) {
IStepFilter[] contributedFilters = DebugPlugin.getStepFilters(JDIDebugPlugin.getUniqueIdentifier());
for (int i = 0; i < contributedFilters.length; i++) {
if (contributedFilters[i].isFiltered(method)) {
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>
*
* @param set
* the remaining {@link EventSet} to queue
*/
protected void stepEnd(EventSet set) {
setRunning(false);
deleteStepRequest();
setPendingStepHandler(null);
if (set != null) {
queueSuspendEvent(DebugEvent.STEP_END, set);
}
}
/**
* 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 {
createSecondaryStepRequest(getStepKind());
}
/**
* Creates another step request in the underlying thread of the
* specified 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.
*
* @param kind
* of <code>StepRequest.STEP_INTO</code>,
* <code>StepRequest.STEP_OVER</code>,
* <code>StepRequest.STEP_OUT</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 void createSecondaryStepRequest(int kind)
throws DebugException {
setStepRequest(createStepRequest(kind));
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);
fStepOverLocation = null;
fStepOverFrameCount = -1;
}
}
}
/**
* Handler for step over requests.
*/
class StepOverHandler extends StepHandler {
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* getStepKind()
*/
@Override
protected int getStepKind() {
return StepRequest.STEP_OVER;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* getStepDetail()
*/
@Override
protected int getStepDetail() {
return DebugEvent.STEP_OVER;
}
}
/**
* Handler for step into requests.
*/
protected class StepIntoHandler extends StepHandler {
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* getStepKind()
*/
@Override
protected int getStepKind() {
return StepRequest.STEP_INTO;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* getStepDetail()
*/
@Override
protected int getStepDetail() {
return DebugEvent.STEP_INTO;
}
}
/**
* Handler for step return requests.
*/
protected class StepReturnHandler extends StepHandler {
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* locationShouldBeFiltered(com.sun.jdi.Location)
*/
@Override
protected boolean locationShouldBeFiltered(Location location)
throws DebugException {
// if still at the same depth, do another step return (see bug
// 38744)
if (getOriginalStepStackDepth() == getUnderlyingFrameCount()) {
return true;
}
return super.locationShouldBeFiltered(location);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* getStepKind()
*/
@Override
protected int getStepKind() {
return StepRequest.STEP_OUT;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#
* getStepDetail()
*/
@Override
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).
*/
protected 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<IJavaStackFrame> 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 IJDIEventListener#handleEvent(Event, JDIDebugTarget, boolean,
* EventSet)
*/
@Override
public boolean handleEvent(Event event, JDIDebugTarget target,
boolean suspendVote, EventSet eventSet) {
try {
int numFrames = getUnderlyingFrameCount();
// top of stack should not be null
if (numFrames <= getRemainingFrames()) {
stepEnd(eventSet);
return false;
}
// reset running state and keep going
setRunning(true);
deleteStepRequest();
createSecondaryStepRequest();
clearStepReturnResult();
return true;
} catch (DebugException e) {
logError(e);
stepEnd(eventSet);
return false;
}
}
}
/**
* Handles dropping to a specified frame.
*/
protected 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<IJavaStackFrame> 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.
*
* @throws DebugException
* if an exception occurs
*/
@Override
protected void invokeThread() throws DebugException {
if (getFramesToDrop() < 0) {
super.invokeThread();
} else {
try {
org.eclipse.jdi.hcr.ThreadReference hcrThread = (org.eclipse.jdi.hcr.ThreadReference) fThread;
hcrThread.doReturn(null, true);
} catch (RuntimeException e) {
stepEnd(null);
fireSuspendEvent(DebugEvent.STEP_END);
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_while_popping_stack_frame,
e.toString()), e);
}
}
}
/**
* 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, boolean,
* EventSet)
* @see #invokeThread()
*/
@Override
public boolean handleEvent(Event event, JDIDebugTarget target,
boolean suspendVote, EventSet eventSet) {
// pop is complete, update number of frames to drop
setFramesToDrop(getFramesToDrop() - 1);
try {
if (getFramesToDrop() >= -1) {
deleteStepRequest();
doSecondaryStep();
} else {
stepEnd(eventSet);
}
} catch (DebugException e) {
stepEnd(eventSet);
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>
*/
@Override
protected StepRequest createStepRequest() throws DebugException {
EventRequestManager manager = getEventRequestManager();
if (manager == null) {
requestFailed(
JDIDebugModelMessages.JDIThread_Unable_to_create_step_request___VM_disconnected__2,
null);
}
int num = getFramesToDrop();
if (num > 0) {
return super.createStepRequest();
} else if (num == 0) {
try {
StepRequest request = ((org.eclipse.jdi.hcr.EventRequestManager) manager).createReenterStepRequest(fThread);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_creating_step_request,
e.toString()), e);
}
} else if (num == -1) {
try {
StepRequest request = manager.createStepRequest(fThread, 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.JDIThread_exception_creating_step_request,
e.toString()), e);
}
}
// this line will never be executed, as the try block
// will either return, or the catch block with throw
// an exception
return null;
}
}
/**
* @see IThread#hasStackFrames()
*/
@Override
public boolean hasStackFrames() throws DebugException {
return isSuspended();
}
/**
* @see IAdaptable#getAdapter(Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IJavaThread.class) {
return (T) this;
}
if (adapter == IJavaStackFrame.class) {
try {
return (T) getTopStackFrame();
} catch (DebugException e) {
// do nothing if not able to get frame
}
}
return super.getAdapter(adapter);
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#hasOwnedMonitors()
*/
@Override
public boolean hasOwnedMonitors() throws DebugException {
return isSuspended() && getOwnedMonitors().length > 0;
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#getOwnedMonitors()
*/
@Override
public IJavaObject[] getOwnedMonitors() throws DebugException {
try {
JDIDebugTarget target = (JDIDebugTarget) getDebugTarget();
List<ObjectReference> ownedMonitors = fThread.ownedMonitors();
IJavaObject[] javaOwnedMonitors = new IJavaObject[ownedMonitors
.size()];
Iterator<ObjectReference> itr = ownedMonitors.iterator();
int i = 0;
while (itr.hasNext()) {
ObjectReference element = itr.next();
javaOwnedMonitors[i] = new JDIObjectValue(target, element);
i++;
}
return javaOwnedMonitors;
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_43, e);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_44, e);
}
return null;
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#getContendedMonitor()
*/
@Override
public IJavaObject getContendedMonitor() throws DebugException {
try {
ObjectReference monitor = fThread.currentContendedMonitor();
if (monitor != null) {
return new JDIObjectValue((JDIDebugTarget) getDebugTarget(),
monitor);
}
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_45, e);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_46, e);
}
return null;
}
/**
* @see org.eclipse.debug.core.model.IFilteredStep#canStepWithFilters()
*/
@Override
public boolean canStepWithFilters() {
if (canStepInto()) {
String[] filters = getJavaDebugTarget().getStepFilters();
return filters != null && filters.length > 0;
}
return false;
}
/**
* @see org.eclipse.debug.core.model.IFilteredStep#stepWithFilters()
*/
@Override
public void stepWithFilters() throws DebugException {
if (!canStepWithFilters()) {
return;
}
stepInto();
}
/**
* Class which managed the queue of runnable associated with this thread.
*/
static class ThreadJob extends Job {
private Vector<Runnable> fRunnables;
private JDIThread fJDIThread;
public ThreadJob(JDIThread thread) {
super(JDIDebugModelMessages.JDIThread_39);
fJDIThread = thread;
fRunnables = new Vector<Runnable>(5);
setSystem(true);
}
public void addRunnable(Runnable runnable) {
synchronized (fRunnables) {
fRunnables.add(runnable);
}
schedule();
}
public boolean isEmpty() {
return fRunnables.isEmpty();
}
/*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.
* IProgressMonitor)
*/
@Override
public IStatus run(IProgressMonitor monitor) {
fJDIThread.fRunningAsyncJob = this;
Object[] runnables;
synchronized (fRunnables) {
runnables = fRunnables.toArray();
fRunnables.clear();
}
MultiStatus failed = null;
monitor.beginTask(this.getName(), runnables.length);
int i = 0;
while (i < runnables.length && !fJDIThread.isTerminated()
&& !monitor.isCanceled()) {
try {
((Runnable) runnables[i]).run();
} catch (Exception e) {
if (failed == null) {
failed = new MultiStatus(
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugPlugin.ERROR,
JDIDebugModelMessages.JDIThread_0, null);
}
failed.add(new Status(IStatus.ERROR, JDIDebugPlugin
.getUniqueIdentifier(), JDIDebugPlugin.ERROR,
JDIDebugModelMessages.JDIThread_0, e));
}
i++;
monitor.worked(1);
}
fJDIThread.fRunningAsyncJob = null;
monitor.done();
if (failed == null) {
return Status.OK_STATUS;
}
return failed;
}
/*
* @see org.eclipse.core.runtime.jobs.Job#shouldRun()
*/
@Override
public boolean shouldRun() {
return !fJDIThread.isTerminated() && !fRunnables.isEmpty();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaThread#stop(org.eclipse.jdt.debug.core
* .IJavaObject)
*/
@Override
public void stop(IJavaObject exception) throws DebugException {
try {
fThread.stop(((JDIObjectValue) exception).getUnderlyingObject());
} catch (InvalidTypeException e) {
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIThread_exception_stoping_thread,
e.toString()), e);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#getThreadGroup()
*/
@Override
public IJavaThreadGroup getThreadGroup() throws DebugException {
ThreadGroupReference group = getUnderlyingThreadGroup();
if (group != null) {
return getJavaDebugTarget().findThreadGroup(group);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#getFrameCount()
*/
@Override
public int getFrameCount() throws DebugException {
return getUnderlyingFrameCount();
}
protected void forceReturn(IJavaValue value) throws DebugException {
if (!isSuspended()) {
return;
}
try {
fThread.forceEarlyReturn(((JDIValue) value).getUnderlyingValue());
stepReturn();
} catch (VMDisconnectedException e) {
disconnected();
} catch (InvalidTypeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (ClassNotLoadedException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (UnsupportedOperationException e) {
requestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
}
}
/**
* Implementation of a scheduling rule for this thread, which defines how it
* should behave when a request for content job tries to run while the
* thread is evaluating
*
* @since 3.3.0
*/
class SerialPerObjectRule implements ISchedulingRule {
private Object fObject = null;
public SerialPerObjectRule(Object lock) {
fObject = lock;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse
* .core.runtime.jobs.ISchedulingRule)
*/
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse
* .core.runtime.jobs.ISchedulingRule)
*/
@Override
public boolean isConflicting(ISchedulingRule rule) {
if (rule instanceof SerialPerObjectRule) {
SerialPerObjectRule vup = (SerialPerObjectRule) rule;
return fObject == vup.fObject;
}
return false;
}
}
/**
* returns the scheduling rule for getting content while evaluations are
* running
*
* @return the <code>ISchedulingRule</code> for this thread
*
* @since 3.3.0
*/
public ISchedulingRule getThreadRule() {
return new SerialPerObjectRule(this);
}
/**
* A class prepare has resumed this thread - if the thread was suspended at
* startup then fix up the state to running and fire an event to update UI.
*/
public synchronized void resumedFromClassPrepare() {
if (isSuspended()) {
setRunning(true);
fireResumeEvent(DebugEvent.CLIENT_REQUEST);
}
}
/**
* Returns whether a suspend vote is currently in progress.
*
* @return whether a suspend vote is currently in progress
*/
public synchronized boolean isSuspendVoteInProgress() {
return fSuspendVoteInProgress;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaThread#getThreadObject()
*/
@Override
public IJavaObject getThreadObject() throws DebugException {
return (IJavaObject) JDIValue
.createValue(getJavaDebugTarget(), fThread);
}
protected StepIntoHandler createStepIntoHandler() {
return new StepIntoHandler();
}
protected StepOverHandler createStepOverHandler() {
return new StepOverHandler();
}
protected StepReturnHandler createStepReturnHandler() {
return new StepReturnHandler();
}
protected StepToFrameHandler createStepToFrameHandler(IStackFrame stackFrame) throws DebugException {
return new StepToFrameHandler(stackFrame);
}
protected DropToFrameHandler createDropToFrameHandler(IStackFrame stackFrame) throws DebugException {
return new DropToFrameHandler(stackFrame);
}
public static boolean showStepResultIsEnabled() {
return Platform.getPreferencesService().getBoolean(JDIDebugPlugin.getUniqueIdentifier(), JDIDebugModel.PREF_SHOW_STEP_RESULT, true, null);
}
}