/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
implements ThreadReference, VMListener {
/*
* Some objects can only be created while a thread is suspended and are valid
* only while the thread remains suspended. Examples are StackFrameImpl
* and MonitorInfoImpl. When the thread resumes, these objects have to be
* marked as invalid so that their methods can throw
* InvalidStackFrameException if they are called. To do this, such objects
* register themselves as listeners of the associated thread. When the
* thread is resumed, its listeners are notified and mark themselves
* invalid.
* Also, note that ThreadReferenceImpl itself caches some info that
* is valid only as long as the thread is suspended. When the thread
* is resumed, that cache must be purged.
* Lastly, note that ThreadReferenceImpl and its super, ObjectReferenceImpl
* cache some info that is only valid as long as the entire VM is suspended.
* If _any_ thread is resumed, this cache must be purged. To handle this,
* both ThreadReferenceImpl and ObjectReferenceImpl register themselves as
* VMListeners so that they get notified when all threads are suspended and
* when any thread is resumed.
*/
// This is cached for the life of the thread
// This is cached only while this one thread is suspended. Each time
// the thread is resumed, we abandon the current cache object and
// create a new intialized one.
private static class LocalCache {
boolean triedCurrentContended = false;
}
/*
* The localCache instance var is set by resetLocalCache to an initialized
* object as shown above. This occurs when the ThreadReference
* object is created, and when the mirrored thread is resumed.
* The fields are then filled in by the relevant methods as they
* are called. A problem can occur if resetLocalCache is called
* (ie, a resume() is executed) at certain points in the execution
* of some of these methods - see 6751643. To avoid this, each
* method that wants to use this cache must make a local copy of
* this variable and use that. This means that each invocation of
* these methods will use a copy of the cache object that was in
* effect at the point that the copy was made; if a racy resume
* occurs, it won't affect the method's local copy. This means that
* the values returned by these calls may not match the state of
* the debuggee at the time the caller gets the values. EG,
* frameCount() is called and comes up with 5 frames. But before
* it returns this, a resume of the debuggee thread is executed in a
* different debugger thread. The thread is resumed and running at
* the time that the value 5 is returned. Or even worse, the thread
* could be suspended again and have a different number of frames, eg, 24,
* but this call will still return 5.
*/
private void resetLocalCache() {
localCache = new LocalCache();
}
// This is cached only while all threads in the VM are suspended
// Yes, someone could change the name of a thread while it is suspended.
}
return new Cache();
}
// Listeners - synchronized on vm.state()
private List<WeakReference<ThreadListener>> listeners = new ArrayList<WeakReference<ThreadListener>>();
}
return "ThreadReference " + uniqueID();
}
/*
* VMListener implementation
*/
// all threads are being resumed
processThreadAction(new ThreadAction(this,
}
}
/*
* Othewise, only one thread is being resumed:
* if it is us,
* we have already done our processThreadAction to notify our
* listeners when we processed the resume.
* if it is not us,
* we don't want to notify our listeners
* because we are not being resumed.
*/
return super.vmNotSuspended(action);
}
/**
* Note that we only cache the name string while the entire VM is suspended
* because the name can change via Thread.setName arbitrarily while this
* thread is running.
*/
try {
}
}
}
} catch (JDWPException exc) {
throw exc.toJDIException();
}
return name;
}
/*
* Sends a command to the back end which is defined to do an
* implicit vm-wide resume.
*/
processThreadAction(new ThreadAction(this,
}
}
public void suspend() {
try {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
// Don't consider the thread suspended yet. On reply, notifySuspend()
// will be called.
}
public void resume() {
/*
* If it's a zombie, we can just update internal state without
* going to back end.
*/
if (suspendedZombieCount > 0) {
return;
}
processThreadAction(new ThreadAction(this,
}
try {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
public int suspendCount() {
/*
* If it's a zombie, we maintain the count in the front end.
*/
if (suspendedZombieCount > 0) {
return suspendedZombieCount;
}
try {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
// Verify that the given object is a Throwable instance
throw new InvalidTypeException("Not an instance of Throwable");
}
try {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
public void interrupt() {
try {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
try {
// thread is suspended, we can cache the status.
}
}
} catch (JDWPException exc) {
throw exc.toJDIException();
}
return myStatus;
}
public int status() {
return jdwpStatus().threadStatus;
}
public boolean isSuspended() {
return ((suspendedZombieCount > 0) ||
}
public boolean isAtBreakpoint() {
/*
* TO DO: This fails to take filters into account.
*/
try {
return true;
}
}
return false;
} catch (IndexOutOfBoundsException iobe) {
return false; // no frames on stack => not at breakpoint
} catch (IncompatibleThreadStateException itse) {
// Per the javadoc, not suspended => return false
return false;
}
}
/*
* Thread group can't change, so it's cached once and for all.
*/
if (threadGroup == null) {
try {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
return threadGroup;
}
try {
}
} catch (JDWPException exc) {
throw new IncompatibleThreadStateException();
default:
throw exc.toJDIException();
}
}
return snapshot.frameCount;
}
}
}
/**
* Is the requested subrange within what has been retrieved?
* local is known to be non-null. Should only be called from
* a sync method.
*/
return false;
}
if (length == -1) {
}
throw new IndexOutOfBoundsException();
}
return true;
}
}
throws IncompatibleThreadStateException {
if (length < 0) {
throw new IndexOutOfBoundsException(
"length must be greater than or equal to zero");
}
}
/**
* Private version of frames() allows "-1" to specify all
* remaining frames.
*/
throws IncompatibleThreadStateException {
// Lock must be held while creating stack frames so if that two threads
// do this at the same time, one won't clobber the subset created by the other.
try {
for (int i = 0; i<count; i++) {
throw new InternalException("Invalid frame location");
}
jdwpFrames[i].frameID,
jdwpFrames[i].location);
// Add to the frame list
}
} else {
int toIndex;
if (length == -1) {
} else {
}
}
} catch (JDWPException exc) {
throw new IncompatibleThreadStateException();
default:
throw exc.toJDIException();
}
}
}
try {
" temporarily caching owned monitors"+
}
}
} catch (JDWPException exc) {
throw new IncompatibleThreadStateException();
default:
throw exc.toJDIException();
}
}
return snapshot.ownedMonitors;
}
throws IncompatibleThreadStateException {
try {
snapshot.triedCurrentContended = true;
" temporarily caching contended monitor"+
}
}
} catch (JDWPException exc) {
throw new IncompatibleThreadStateException();
default:
throw exc.toJDIException();
}
}
return snapshot.contendedMonitor;
}
try {
minfo[i];
}
" temporarily caching owned monitors"+
}
}
} catch (JDWPException exc) {
throw new IncompatibleThreadStateException();
default:
throw exc.toJDIException();
}
}
return snapshot.ownedMonitorsInfo;
}
// Note that interface-wise this functionality belongs
// here in ThreadReference, but implementation-wise it
// belongs in StackFrame, so we just forward it.
throw new IllegalArgumentException("frame does not belong to this thread");
}
if (!vm.canPopFrames()) {
throw new UnsupportedOperationException(
"target does not support popping frames");
}
}
if (!vm.canForceEarlyReturn()) {
throw new UnsupportedOperationException(
"target does not support the forcing of a method to return early");
}
try {
} catch (IndexOutOfBoundsException exc) {
throw new InvalidStackFrameException("No more frames on the stack");
}
try {
} catch (JDWPException exc) {
throw new NativeMethodException();
throw new IncompatibleThreadStateException(
"Thread not suspended");
throw new IncompatibleThreadStateException(
"Thread has not started or has finished");
throw new InvalidStackFrameException(
"No more frames on the stack");
default:
throw exc.toJDIException();
}
}
}
}
byte typeValueKey() {
}
}
}
break;
}
}
}
}
/**
* Propagate the the thread state change information
* to registered listeners.
* Must be entered while synchronized on vm.state()
*/
case ThreadAction.THREAD_RESUMABLE:
}
break;
}
} else {
// Listener is unreachable; clean up
}
}
// Discard our local cache
}
}
}