8067377: My hobby: caning, then then canning, the the can-can
Summary: Fix ALL the stutters!
Reviewed-by: rriggs, mchung, lancea
/*
* Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.
*/
package com.sun.tools.jdi;
import com.sun.jdi.*;
import com.sun.jdi.connect.spi.Connection;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.event.EventQueue;
import java.util.*;
import java.text.MessageFormat;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
class VirtualMachineImpl extends MirrorImpl
implements PathSearchingVirtualMachine, ThreadListener {
// VM Level exported variables, these
// are unique to a given vm
public final int sizeofFieldRef;
public final int sizeofMethodRef;
public final int sizeofObjectRef;
public final int sizeofClassRef;
public final int sizeofFrameRef;
final int sequenceNumber;
private final TargetVM target;
private final EventQueueImpl eventQueue;
private final EventRequestManagerImpl internalEventRequestManager;
private final EventRequestManagerImpl eventRequestManager;
final VirtualMachineManagerImpl vmManager;
private final ThreadGroup threadGroupForJDI;
// Allow direct access to this field so that that tracing code slows down
// JDI as little as possible when not enabled.
int traceFlags = TRACE_NONE;
static int TRACE_RAW_SENDS = 0x01000000;
static int TRACE_RAW_RECEIVES = 0x02000000;
boolean traceReceives = false; // pre-compute because of frequency
// ReferenceType access - updated with class prepare and unload events
// Protected by "synchronized(this)". "retrievedAllTypes" may be
// tested unsynchronized (since once true, it stays true), but must
// be set synchronously
private Map<Long, ReferenceType> typesByID;
private TreeSet<ReferenceType> typesBySignature;
private boolean retrievedAllTypes = false;
// For other languages support
private String defaultStratum = null;
// ObjectReference cache
// "objectsByID" protected by "synchronized(this)".
private final Map<Long, SoftObjectReference> objectsByID = new HashMap<Long, SoftObjectReference>();
private final ReferenceQueue<ObjectReferenceImpl> referenceQueue = new ReferenceQueue<ObjectReferenceImpl>();
static private final int DISPOSE_THRESHOLD = 50;
private final List<SoftObjectReference> batchedDisposeRequests =
Collections.synchronizedList(new ArrayList<SoftObjectReference>(DISPOSE_THRESHOLD + 10));
// These are cached once for the life of the VM
private JDWP.VirtualMachine.Version versionInfo;
private JDWP.VirtualMachine.ClassPaths pathInfo;
private JDWP.VirtualMachine.Capabilities capabilities = null;
private JDWP.VirtualMachine.CapabilitiesNew capabilitiesNew = null;
// Per-vm singletons for primitive types and for void.
// singleton-ness protected by "synchronized(this)".
private BooleanType theBooleanType;
private ByteType theByteType;
private CharType theCharType;
private ShortType theShortType;
private IntegerType theIntegerType;
private LongType theLongType;
private FloatType theFloatType;
private DoubleType theDoubleType;
private VoidType theVoidType;
private VoidValue voidVal;
// Launched debuggee process
private Process process;
// coordinates state changes and corresponding listener notifications
private VMState state = new VMState(this);
private Object initMonitor = new Object();
private boolean initComplete = false;
private boolean shutdown = false;
private void notifyInitCompletion() {
synchronized(initMonitor) {
initComplete = true;
initMonitor.notifyAll();
}
}
void waitInitCompletion() {
synchronized(initMonitor) {
while (!initComplete) {
try {
initMonitor.wait();
} catch (InterruptedException e) {
// ignore
}
}
}
}
VMState state() {
return state;
}
/*
* ThreadListener implementation
*/
public boolean threadResumable(ThreadAction action) {
/*
* If any thread is resumed, the VM is considered not suspended.
* Just one thread is being resumed so pass it to thaw.
*/
state.thaw(action.thread());
return true;
}
VirtualMachineImpl(VirtualMachineManager manager,
Connection connection, Process process,
int sequenceNumber) {
super(null); // Can't use super(this)
vm = this;
this.vmManager = (VirtualMachineManagerImpl)manager;
this.process = process;
this.sequenceNumber = sequenceNumber;
/* Create ThreadGroup to be used by all threads servicing
* this VM.
*/
threadGroupForJDI = new ThreadGroup(vmManager.mainGroupForJDI(),
"JDI [" +
this.hashCode() + "]");
/*
* Set up a thread to communicate with the target VM over
* the specified transport.
*/
target = new TargetVM(this, connection);
/*
* Set up a thread to handle events processed internally
* the JDI implementation.
*/
EventQueueImpl internalEventQueue = new EventQueueImpl(this, target);
new InternalEventHandler(this, internalEventQueue);
/*
* Initialize client access to event setting and handling
*/
eventQueue = new EventQueueImpl(this, target);
eventRequestManager = new EventRequestManagerImpl(this);
target.start();
/*
* Many ids are variably sized, depending on target VM.
* Find out the sizes right away.
*/
JDWP.VirtualMachine.IDSizes idSizes;
try {
idSizes = JDWP.VirtualMachine.IDSizes.process(vm);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
sizeofFieldRef = idSizes.fieldIDSize;
sizeofMethodRef = idSizes.methodIDSize;
sizeofObjectRef = idSizes.objectIDSize;
sizeofClassRef = idSizes.referenceTypeIDSize;
sizeofFrameRef = idSizes.frameIDSize;
/**
* Set up requests needed by internal event handler.
* Make sure they are distinguished by creating them with
* an internal event request manager.
*
* Warning: create events only with SUSPEND_NONE policy.
* In the current implementation other policies will not
* be handled correctly when the event comes in. (notfiySuspend()
* will not be properly called, and if the event is combined
* with external events in the same set, suspend policy is not
* correctly determined for the internal vs. external event sets)
*/
internalEventRequestManager = new EventRequestManagerImpl(this);
EventRequest er = internalEventRequestManager.createClassPrepareRequest();
er.setSuspendPolicy(EventRequest.SUSPEND_NONE);
er.enable();
er = internalEventRequestManager.createClassUnloadRequest();
er.setSuspendPolicy(EventRequest.SUSPEND_NONE);
er.enable();
/*
* Tell other threads, notably TargetVM, that initialization
* is complete.
*/
notifyInitCompletion();
}
EventRequestManagerImpl getInternalEventRequestManager() {
return internalEventRequestManager;
}
void validateVM() {
/*
* We no longer need to do this. The spec now says
* that a VMDisconnected _may_ be thrown in these
* cases, not that it _will_ be thrown.
* So, to simplify things we will just let the
* caller's of this method proceed with their business.
* If the debuggee is disconnected, either because it
* crashed or finished or something, or because the
* debugger called exit() or dispose(), then if
* we end up trying to communicate with the debuggee,
* code in TargetVM will throw a VMDisconnectedException.
* This means that if we can satisfy a request without
* talking to the debuggee, (eg, with cached data) then
* VMDisconnectedException will _not_ be thrown.
* if (shutdown) {
* throw new VMDisconnectedException();
* }
*/
}
public boolean equals(Object obj) {
return this == obj;
}
public int hashCode() {
return System.identityHashCode(this);
}
public List<ReferenceType> classesByName(String className) {
validateVM();
String signature = JNITypeParser.typeNameToSignature(className);
List<ReferenceType> list;
if (retrievedAllTypes) {
list = findReferenceTypes(signature);
} else {
list = retrieveClassesBySignature(signature);
}
return Collections.unmodifiableList(list);
}
public List<ReferenceType> allClasses() {
validateVM();
if (!retrievedAllTypes) {
retrieveAllClasses();
}
ArrayList<ReferenceType> a;
synchronized (this) {
a = new ArrayList<ReferenceType>(typesBySignature);
}
return Collections.unmodifiableList(a);
}
public void
redefineClasses(Map<? extends ReferenceType,byte[]> classToBytes)
{
int cnt = classToBytes.size();
JDWP.VirtualMachine.RedefineClasses.ClassDef[] defs =
new JDWP.VirtualMachine.RedefineClasses.ClassDef[cnt];
validateVM();
if (!canRedefineClasses()) {
throw new UnsupportedOperationException();
}
Iterator<?> it = classToBytes.entrySet().iterator();
for (int i = 0; it.hasNext(); i++) {
Map.Entry<?,?> entry = (Map.Entry)it.next();
ReferenceTypeImpl refType = (ReferenceTypeImpl)entry.getKey();
validateMirror(refType);
defs[i] = new JDWP.VirtualMachine.RedefineClasses
.ClassDef(refType, (byte[])entry.getValue());
}
// flush caches and disable caching until the next suspend
vm.state().thaw();
try {
JDWP.VirtualMachine.RedefineClasses.
process(vm, defs);
} catch (JDWPException exc) {
switch (exc.errorCode()) {
case JDWP.Error.INVALID_CLASS_FORMAT :
throw new ClassFormatError(
"class not in class file format");
case JDWP.Error.CIRCULAR_CLASS_DEFINITION :
throw new ClassCircularityError(
"circularity has been detected while initializing a class");
case JDWP.Error.FAILS_VERIFICATION :
throw new VerifyError(
"verifier detected internal inconsistency or security problem");
case JDWP.Error.UNSUPPORTED_VERSION :
throw new UnsupportedClassVersionError(
"version numbers of class are not supported");
case JDWP.Error.ADD_METHOD_NOT_IMPLEMENTED:
throw new UnsupportedOperationException(
"add method not implemented");
case JDWP.Error.SCHEMA_CHANGE_NOT_IMPLEMENTED :
throw new UnsupportedOperationException(
"schema change not implemented");
case JDWP.Error.HIERARCHY_CHANGE_NOT_IMPLEMENTED:
throw new UnsupportedOperationException(
"hierarchy change not implemented");
case JDWP.Error.DELETE_METHOD_NOT_IMPLEMENTED :
throw new UnsupportedOperationException(
"delete method not implemented");
case JDWP.Error.CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED:
throw new UnsupportedOperationException(
"changes to class modifiers not implemented");
case JDWP.Error.METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED :
throw new UnsupportedOperationException(
"changes to method modifiers not implemented");
case JDWP.Error.NAMES_DONT_MATCH :
throw new NoClassDefFoundError(
"class names do not match");
default:
throw exc.toJDIException();
}
}
// Delete any record of the breakpoints
List<BreakpointRequest> toDelete = new ArrayList<BreakpointRequest>();
EventRequestManager erm = eventRequestManager();
it = erm.breakpointRequests().iterator();
while (it.hasNext()) {
BreakpointRequest req = (BreakpointRequest)it.next();
if (classToBytes.containsKey(req.location().declaringType())) {
toDelete.add(req);
}
}
erm.deleteEventRequests(toDelete);
// Invalidate any information cached for the classes just redefined.
it = classToBytes.keySet().iterator();
while (it.hasNext()) {
ReferenceTypeImpl rti = (ReferenceTypeImpl)it.next();
rti.noticeRedefineClass();
}
}
public List<ThreadReference> allThreads() {
validateVM();
return state.allThreads();
}
public List<ThreadGroupReference> topLevelThreadGroups() {
validateVM();
return state.topLevelThreadGroups();
}
/*
* Sends a command to the back end which is defined to do an
* implicit vm-wide resume. The VM can no longer be considered
* suspended, so certain cached data must be invalidated.
*/
PacketStream sendResumingCommand(CommandSender sender) {
return state.thawCommand(sender);
}
/*
* The VM has been suspended. Additional caching can be done
* as long as there are no pending resumes.
*/
void notifySuspend() {
state.freeze();
}
public void suspend() {
validateVM();
try {
JDWP.VirtualMachine.Suspend.process(vm);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
notifySuspend();
}
public void resume() {
validateVM();
CommandSender sender =
new CommandSender() {
public PacketStream send() {
return JDWP.VirtualMachine.Resume.enqueueCommand(vm);
}
};
try {
PacketStream stream = state.thawCommand(sender);
JDWP.VirtualMachine.Resume.waitForReply(vm, stream);
} catch (VMDisconnectedException exc) {
/*
* If the debugger makes a VMDeathRequest with SUSPEND_ALL,
* then when it does an EventSet.resume after getting the
* VMDeathEvent, the normal flow of events is that the
* BE shuts down, but the waitForReply comes back ok. In this
* case, the run loop in TargetVM that is waiting for a packet
* gets an EOF because the socket closes. It generates a
* VMDisconnectedEvent and everyone is happy.
* However, sometimes, the BE gets shutdown before this
* waitForReply completes. In this case, TargetVM.waitForReply
* gets awakened with no reply and so gens a VMDisconnectedException
* which is not what we want. It might be possible to fix this
* in the BE, but it is ok to just ignore the VMDisconnectedException
* here. This will allow the VMDisconnectedEvent to be generated
* correctly. And, if the debugger should happen to make another
* request, it will get a VMDisconnectedException at that time.
*/
} catch (JDWPException exc) {
switch (exc.errorCode()) {
case JDWP.Error.VM_DEAD:
return;
default:
throw exc.toJDIException();
}
}
}
public EventQueue eventQueue() {
/*
* No VM validation here. We allow access to the event queue
* after disconnection, so that there is access to the terminating
* events.
*/
return eventQueue;
}
public EventRequestManager eventRequestManager() {
validateVM();
return eventRequestManager;
}
EventRequestManagerImpl eventRequestManagerImpl() {
return eventRequestManager;
}
public BooleanValue mirrorOf(boolean value) {
validateVM();
return new BooleanValueImpl(this,value);
}
public ByteValue mirrorOf(byte value) {
validateVM();
return new ByteValueImpl(this,value);
}
public CharValue mirrorOf(char value) {
validateVM();
return new CharValueImpl(this,value);
}
public ShortValue mirrorOf(short value) {
validateVM();
return new ShortValueImpl(this,value);
}
public IntegerValue mirrorOf(int value) {
validateVM();
return new IntegerValueImpl(this,value);
}
public LongValue mirrorOf(long value) {
validateVM();
return new LongValueImpl(this,value);
}
public FloatValue mirrorOf(float value) {
validateVM();
return new FloatValueImpl(this,value);
}
public DoubleValue mirrorOf(double value) {
validateVM();
return new DoubleValueImpl(this,value);
}
public StringReference mirrorOf(String value) {
validateVM();
try {
return (StringReference)JDWP.VirtualMachine.CreateString.
process(vm, value).stringObject;
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
public VoidValue mirrorOfVoid() {
if (voidVal == null) {
voidVal = new VoidValueImpl(this);
}
return voidVal;
}
public long[] instanceCounts(List<? extends ReferenceType> classes) {
if (!canGetInstanceInfo()) {
throw new UnsupportedOperationException(
"target does not support getting instances");
}
long[] retValue ;
ReferenceTypeImpl[] rtArray = new ReferenceTypeImpl[classes.size()];
int ii = 0;
for (ReferenceType rti: classes) {
validateMirror(rti);
rtArray[ii++] = (ReferenceTypeImpl)rti;
}
try {
retValue = JDWP.VirtualMachine.InstanceCounts.
process(vm, rtArray).counts;
} catch (JDWPException exc) {
throw exc.toJDIException();
}
return retValue;
}
public void dispose() {
validateVM();
shutdown = true;
try {
JDWP.VirtualMachine.Dispose.process(vm);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
target.stopListening();
}
public void exit(int exitCode) {
validateVM();
shutdown = true;
try {
JDWP.VirtualMachine.Exit.process(vm, exitCode);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
target.stopListening();
}
public Process process() {
validateVM();
return process;
}
private JDWP.VirtualMachine.Version versionInfo() {
try {
if (versionInfo == null) {
// Need not be synchronized since it is static information
versionInfo = JDWP.VirtualMachine.Version.process(vm);
}
return versionInfo;
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
public String description() {
validateVM();
return MessageFormat.format(vmManager.getString("version_format"),
"" + vmManager.majorInterfaceVersion(),
"" + vmManager.minorInterfaceVersion(),
versionInfo().description);
}
public String version() {
validateVM();
return versionInfo().vmVersion;
}
public String name() {
validateVM();
return versionInfo().vmName;
}
public boolean canWatchFieldModification() {
validateVM();
return capabilities().canWatchFieldModification;
}
public boolean canWatchFieldAccess() {
validateVM();
return capabilities().canWatchFieldAccess;
}
public boolean canGetBytecodes() {
validateVM();
return capabilities().canGetBytecodes;
}
public boolean canGetSyntheticAttribute() {
validateVM();
return capabilities().canGetSyntheticAttribute;
}
public boolean canGetOwnedMonitorInfo() {
validateVM();
return capabilities().canGetOwnedMonitorInfo;
}
public boolean canGetCurrentContendedMonitor() {
validateVM();
return capabilities().canGetCurrentContendedMonitor;
}
public boolean canGetMonitorInfo() {
validateVM();
return capabilities().canGetMonitorInfo;
}
private boolean hasNewCapabilities() {
return versionInfo().jdwpMajor > 1 ||
versionInfo().jdwpMinor >= 4;
}
boolean canGet1_5LanguageFeatures() {
return versionInfo().jdwpMajor > 1 ||
versionInfo().jdwpMinor >= 5;
}
public boolean canUseInstanceFilters() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canUseInstanceFilters;
}
public boolean canRedefineClasses() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canRedefineClasses;
}
public boolean canAddMethod() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canAddMethod;
}
public boolean canUnrestrictedlyRedefineClasses() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canUnrestrictedlyRedefineClasses;
}
public boolean canPopFrames() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canPopFrames;
}
public boolean canGetMethodReturnValues() {
return versionInfo().jdwpMajor > 1 ||
versionInfo().jdwpMinor >= 6;
}
public boolean canGetInstanceInfo() {
if (versionInfo().jdwpMajor < 1 ||
versionInfo().jdwpMinor < 6) {
return false;
}
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canGetInstanceInfo;
}
public boolean canUseSourceNameFilters() {
if (versionInfo().jdwpMajor < 1 ||
versionInfo().jdwpMinor < 6) {
return false;
}
return true;
}
public boolean canForceEarlyReturn() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canForceEarlyReturn;
}
public boolean canBeModified() {
return true;
}
public boolean canGetSourceDebugExtension() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canGetSourceDebugExtension;
}
public boolean canGetClassFileVersion() {
if ( versionInfo().jdwpMajor < 1 &&
versionInfo().jdwpMinor < 6) {
return false;
} else {
return true;
}
}
public boolean canGetConstantPool() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canGetConstantPool;
}
public boolean canRequestVMDeathEvent() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canRequestVMDeathEvent;
}
public boolean canRequestMonitorEvents() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canRequestMonitorEvents;
}
public boolean canGetMonitorFrameInfo() {
validateVM();
return hasNewCapabilities() &&
capabilitiesNew().canGetMonitorFrameInfo;
}
public void setDebugTraceMode(int traceFlags) {
validateVM();
this.traceFlags = traceFlags;
this.traceReceives = (traceFlags & TRACE_RECEIVES) != 0;
}
void printTrace(String string) {
System.err.println("[JDI: " + string + "]");
}
void printReceiveTrace(int depth, String string) {
StringBuilder sb = new StringBuilder("Receiving:");
for (int i = depth; i > 0; --i) {
sb.append(" ");
}
sb.append(string);
printTrace(sb.toString());
}
private synchronized ReferenceTypeImpl addReferenceType(long id,
int tag,
String signature) {
if (typesByID == null) {
initReferenceTypes();
}
ReferenceTypeImpl type = null;
switch(tag) {
case JDWP.TypeTag.CLASS:
type = new ClassTypeImpl(vm, id);
break;
case JDWP.TypeTag.INTERFACE:
type = new InterfaceTypeImpl(vm, id);
break;
case JDWP.TypeTag.ARRAY:
type = new ArrayTypeImpl(vm, id);
break;
default:
throw new InternalException("Invalid reference type tag");
}
/*
* If a signature was specified, make sure to set it ASAP, to
* prevent any needless JDWP command to retrieve it. (for example,
* typesBySignature.add needs the signature, to maintain proper
* ordering.
*/
if (signature != null) {
type.setSignature(signature);
}
typesByID.put(id, type);
typesBySignature.add(type);
if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) {
vm.printTrace("Caching new ReferenceType, sig=" + signature +
", id=" + id);
}
return type;
}
synchronized void removeReferenceType(String signature) {
if (typesByID == null) {
return;
}
/*
* There can be multiple classes with the same name. Since
* we can't differentiate here, we first remove all
* matching classes from our cache...
*/
Iterator<ReferenceType> iter = typesBySignature.iterator();
int matches = 0;
while (iter.hasNext()) {
ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next();
int comp = signature.compareTo(type.signature());
if (comp == 0) {
matches++;
iter.remove();
typesByID.remove(type.ref());
if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) {
vm.printTrace("Uncaching ReferenceType, sig=" + signature +
", id=" + type.ref());
}
/* fix for 4359077 , don't break out. list is no longer sorted
in the order we think
*/
}
}
/*
* ...and if there was more than one, re-retrieve the classes
* with that name
*/
if (matches > 1) {
retrieveClassesBySignature(signature);
}
}
private synchronized List<ReferenceType> findReferenceTypes(String signature) {
if (typesByID == null) {
return new ArrayList<ReferenceType>(0);
}
Iterator<ReferenceType> iter = typesBySignature.iterator();
List<ReferenceType> list = new ArrayList<ReferenceType>();
while (iter.hasNext()) {
ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next();
int comp = signature.compareTo(type.signature());
if (comp == 0) {
list.add(type);
/* fix for 4359077 , don't break out. list is no longer sorted
in the order we think
*/
}
}
return list;
}
private void initReferenceTypes() {
typesByID = new HashMap<Long, ReferenceType>(300);
typesBySignature = new TreeSet<ReferenceType>();
}
ReferenceTypeImpl referenceType(long ref, byte tag) {
return referenceType(ref, tag, null);
}
ClassTypeImpl classType(long ref) {
return (ClassTypeImpl)referenceType(ref, JDWP.TypeTag.CLASS, null);
}
InterfaceTypeImpl interfaceType(long ref) {
return (InterfaceTypeImpl)referenceType(ref, JDWP.TypeTag.INTERFACE, null);
}
ArrayTypeImpl arrayType(long ref) {
return (ArrayTypeImpl)referenceType(ref, JDWP.TypeTag.ARRAY, null);
}
ReferenceTypeImpl referenceType(long id, int tag,
String signature) {
if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) {
StringBuilder sb = new StringBuilder();
sb.append("Looking up ");
if (tag == JDWP.TypeTag.CLASS) {
sb.append("Class");
} else if (tag == JDWP.TypeTag.INTERFACE) {
sb.append("Interface");
} else if (tag == JDWP.TypeTag.ARRAY) {
sb.append("ArrayType");
} else {
sb.append("UNKNOWN TAG: ").append(tag);
}
if (signature != null) {
sb.append(", signature='").append(signature).append('\'');
}
sb.append(", id=").append(id);
vm.printTrace(sb.toString());
}
if (id == 0) {
return null;
} else {
ReferenceTypeImpl retType = null;
synchronized (this) {
if (typesByID != null) {
retType = (ReferenceTypeImpl)typesByID.get(id);
}
if (retType == null) {
retType = addReferenceType(id, tag, signature);
}
}
return retType;
}
}
private JDWP.VirtualMachine.Capabilities capabilities() {
if (capabilities == null) {
try {
capabilities = JDWP.VirtualMachine
.Capabilities.process(vm);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
return capabilities;
}
private JDWP.VirtualMachine.CapabilitiesNew capabilitiesNew() {
if (capabilitiesNew == null) {
try {
capabilitiesNew = JDWP.VirtualMachine
.CapabilitiesNew.process(vm);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
return capabilitiesNew;
}
private List<ReferenceType> retrieveClassesBySignature(String signature) {
if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) {
vm.printTrace("Retrieving matching ReferenceTypes, sig=" + signature);
}
JDWP.VirtualMachine.ClassesBySignature.ClassInfo[] cinfos;
try {
cinfos = JDWP.VirtualMachine.ClassesBySignature.
process(vm, signature).classes;
} catch (JDWPException exc) {
throw exc.toJDIException();
}
int count = cinfos.length;
List<ReferenceType> list = new ArrayList<ReferenceType>(count);
// Hold lock during processing to improve performance
synchronized (this) {
for (int i = 0; i < count; i++) {
JDWP.VirtualMachine.ClassesBySignature.ClassInfo ci =
cinfos[i];
ReferenceTypeImpl type = referenceType(ci.typeID,
ci.refTypeTag,
signature);
type.setStatus(ci.status);
list.add(type);
}
}
return list;
}
private void retrieveAllClasses1_4() {
JDWP.VirtualMachine.AllClasses.ClassInfo[] cinfos;
try {
cinfos = JDWP.VirtualMachine.AllClasses.process(vm).classes;
} catch (JDWPException exc) {
throw exc.toJDIException();
}
// Hold lock during processing to improve performance
// and to have safe check/set of retrievedAllTypes
synchronized (this) {
if (!retrievedAllTypes) {
// Number of classes
int count = cinfos.length;
for (int i=0; i<count; i++) {
JDWP.VirtualMachine.AllClasses.ClassInfo ci =
cinfos[i];
ReferenceTypeImpl type = referenceType(ci.typeID,
ci.refTypeTag,
ci.signature);
type.setStatus(ci.status);
}
retrievedAllTypes = true;
}
}
}
private void retrieveAllClasses() {
if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) {
vm.printTrace("Retrieving all ReferenceTypes");
}
if (!vm.canGet1_5LanguageFeatures()) {
retrieveAllClasses1_4();
return;
}
/*
* To save time (assuming the caller will be
* using then) we will get the generic sigs too.
*/
JDWP.VirtualMachine.AllClassesWithGeneric.ClassInfo[] cinfos;
try {
cinfos = JDWP.VirtualMachine.AllClassesWithGeneric.process(vm).classes;
} catch (JDWPException exc) {
throw exc.toJDIException();
}
// Hold lock during processing to improve performance
// and to have safe check/set of retrievedAllTypes
synchronized (this) {
if (!retrievedAllTypes) {
// Number of classes
int count = cinfos.length;
for (int i=0; i<count; i++) {
JDWP.VirtualMachine.AllClassesWithGeneric.ClassInfo ci =
cinfos[i];
ReferenceTypeImpl type = referenceType(ci.typeID,
ci.refTypeTag,
ci.signature);
type.setGenericSignature(ci.genericSignature);
type.setStatus(ci.status);
}
retrievedAllTypes = true;
}
}
}
void sendToTarget(Packet packet) {
target.send(packet);
}
void waitForTargetReply(Packet packet) {
target.waitForReply(packet);
/*
* If any object disposes have been batched up, send them now.
*/
processBatchedDisposes();
}
Type findBootType(String signature) throws ClassNotLoadedException {
List<ReferenceType> types = allClasses();
Iterator<ReferenceType> iter = types.iterator();
while (iter.hasNext()) {
ReferenceType type = iter.next();
if ((type.classLoader() == null) &&
(type.signature().equals(signature))) {
return type;
}
}
JNITypeParser parser = new JNITypeParser(signature);
throw new ClassNotLoadedException(parser.typeName(),
"Type " + parser.typeName() + " not loaded");
}
BooleanType theBooleanType() {
if (theBooleanType == null) {
synchronized(this) {
if (theBooleanType == null) {
theBooleanType = new BooleanTypeImpl(this);
}
}
}
return theBooleanType;
}
ByteType theByteType() {
if (theByteType == null) {
synchronized(this) {
if (theByteType == null) {
theByteType = new ByteTypeImpl(this);
}
}
}
return theByteType;
}
CharType theCharType() {
if (theCharType == null) {
synchronized(this) {
if (theCharType == null) {
theCharType = new CharTypeImpl(this);
}
}
}
return theCharType;
}
ShortType theShortType() {
if (theShortType == null) {
synchronized(this) {
if (theShortType == null) {
theShortType = new ShortTypeImpl(this);
}
}
}
return theShortType;
}
IntegerType theIntegerType() {
if (theIntegerType == null) {
synchronized(this) {
if (theIntegerType == null) {
theIntegerType = new IntegerTypeImpl(this);
}
}
}
return theIntegerType;
}
LongType theLongType() {
if (theLongType == null) {
synchronized(this) {
if (theLongType == null) {
theLongType = new LongTypeImpl(this);
}
}
}
return theLongType;
}
FloatType theFloatType() {
if (theFloatType == null) {
synchronized(this) {
if (theFloatType == null) {
theFloatType = new FloatTypeImpl(this);
}
}
}
return theFloatType;
}
DoubleType theDoubleType() {
if (theDoubleType == null) {
synchronized(this) {
if (theDoubleType == null) {
theDoubleType = new DoubleTypeImpl(this);
}
}
}
return theDoubleType;
}
VoidType theVoidType() {
if (theVoidType == null) {
synchronized(this) {
if (theVoidType == null) {
theVoidType = new VoidTypeImpl(this);
}
}
}
return theVoidType;
}
PrimitiveType primitiveTypeMirror(byte tag) {
switch (tag) {
case JDWP.Tag.BOOLEAN:
return theBooleanType();
case JDWP.Tag.BYTE:
return theByteType();
case JDWP.Tag.CHAR:
return theCharType();
case JDWP.Tag.SHORT:
return theShortType();
case JDWP.Tag.INT:
return theIntegerType();
case JDWP.Tag.LONG:
return theLongType();
case JDWP.Tag.FLOAT:
return theFloatType();
case JDWP.Tag.DOUBLE:
return theDoubleType();
default:
throw new IllegalArgumentException("Unrecognized primitive tag " + tag);
}
}
private void processBatchedDisposes() {
if (shutdown) {
return;
}
JDWP.VirtualMachine.DisposeObjects.Request[] requests = null;
synchronized(batchedDisposeRequests) {
int size = batchedDisposeRequests.size();
if (size >= DISPOSE_THRESHOLD) {
if ((traceFlags & TRACE_OBJREFS) != 0) {
printTrace("Dispose threashold reached. Will dispose "
+ size + " object references...");
}
requests = new JDWP.VirtualMachine.DisposeObjects.Request[size];
for (int i = 0; i < requests.length; i++) {
SoftObjectReference ref = batchedDisposeRequests.get(i);
if ((traceFlags & TRACE_OBJREFS) != 0) {
printTrace("Disposing object " + ref.key().longValue() +
" (ref count = " + ref.count() + ")");
}
// This is kludgy. We temporarily re-create an object
// reference so that we can correctly pass its id to the
// JDWP command.
requests[i] =
new JDWP.VirtualMachine.DisposeObjects.Request(
new ObjectReferenceImpl(this, ref.key().longValue()),
ref.count());
}
batchedDisposeRequests.clear();
}
}
if (requests != null) {
try {
JDWP.VirtualMachine.DisposeObjects.process(vm, requests);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
}
private void batchForDispose(SoftObjectReference ref) {
if ((traceFlags & TRACE_OBJREFS) != 0) {
printTrace("Batching object " + ref.key().longValue() +
" for dispose (ref count = " + ref.count() + ")");
}
batchedDisposeRequests.add(ref);
}
private void processQueue() {
Reference<?> ref;
//if ((traceFlags & TRACE_OBJREFS) != 0) {
// printTrace("Checking for softly reachable objects");
//}
while ((ref = referenceQueue.poll()) != null) {
SoftObjectReference softRef = (SoftObjectReference)ref;
removeObjectMirror(softRef);
batchForDispose(softRef);
}
}
synchronized ObjectReferenceImpl objectMirror(long id, int tag) {
// Handle any queue elements that are not strongly reachable
processQueue();
if (id == 0) {
return null;
}
ObjectReferenceImpl object = null;
Long key = id;
/*
* Attempt to retrieve an existing object reference
*/
SoftObjectReference ref = objectsByID.get(key);
if (ref != null) {
object = ref.object();
}
/*
* If the object wasn't in the table, or it's soft reference was
* cleared, create a new instance.
*/
if (object == null) {
switch (tag) {
case JDWP.Tag.OBJECT:
object = new ObjectReferenceImpl(vm, id);
break;
case JDWP.Tag.STRING:
object = new StringReferenceImpl(vm, id);
break;
case JDWP.Tag.ARRAY:
object = new ArrayReferenceImpl(vm, id);
break;
case JDWP.Tag.THREAD:
ThreadReferenceImpl thread =
new ThreadReferenceImpl(vm, id);
thread.addListener(this);
object = thread;
break;
case JDWP.Tag.THREAD_GROUP:
object = new ThreadGroupReferenceImpl(vm, id);
break;
case JDWP.Tag.CLASS_LOADER:
object = new ClassLoaderReferenceImpl(vm, id);
break;
case JDWP.Tag.CLASS_OBJECT:
object = new ClassObjectReferenceImpl(vm, id);
break;
default:
throw new IllegalArgumentException("Invalid object tag: " + tag);
}
ref = new SoftObjectReference(key, object, referenceQueue);
/*
* If there was no previous entry in the table, we add one here
* If the previous entry was cleared, we replace it here.
*/
objectsByID.put(key, ref);
if ((traceFlags & TRACE_OBJREFS) != 0) {
printTrace("Creating new " +
object.getClass().getName() + " (id = " + id + ")");
}
} else {
ref.incrementCount();
}
return object;
}
synchronized void removeObjectMirror(ObjectReferenceImpl object) {
// Handle any queue elements that are not strongly reachable
processQueue();
SoftObjectReference ref = objectsByID.remove(object.ref());
if (ref != null) {
batchForDispose(ref);
} else {
/*
* If there's a live ObjectReference about, it better be part
* of the cache.
*/
throw new InternalException("ObjectReference " + object.ref() +
" not found in object cache");
}
}
synchronized void removeObjectMirror(SoftObjectReference ref) {
/*
* This will remove the soft reference if it has not been
* replaced in the cache.
*/
objectsByID.remove(ref.key());
}
ObjectReferenceImpl objectMirror(long id) {
return objectMirror(id, JDWP.Tag.OBJECT);
}
StringReferenceImpl stringMirror(long id) {
return (StringReferenceImpl)objectMirror(id, JDWP.Tag.STRING);
}
ArrayReferenceImpl arrayMirror(long id) {
return (ArrayReferenceImpl)objectMirror(id, JDWP.Tag.ARRAY);
}
ThreadReferenceImpl threadMirror(long id) {
return (ThreadReferenceImpl)objectMirror(id, JDWP.Tag.THREAD);
}
ThreadGroupReferenceImpl threadGroupMirror(long id) {
return (ThreadGroupReferenceImpl)objectMirror(id,
JDWP.Tag.THREAD_GROUP);
}
ClassLoaderReferenceImpl classLoaderMirror(long id) {
return (ClassLoaderReferenceImpl)objectMirror(id,
JDWP.Tag.CLASS_LOADER);
}
ClassObjectReferenceImpl classObjectMirror(long id) {
return (ClassObjectReferenceImpl)objectMirror(id,
JDWP.Tag.CLASS_OBJECT);
}
/*
* Implementation of PathSearchingVirtualMachine
*/
private JDWP.VirtualMachine.ClassPaths getClasspath() {
if (pathInfo == null) {
try {
pathInfo = JDWP.VirtualMachine.ClassPaths.process(vm);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
return pathInfo;
}
public List<String> classPath() {
return Arrays.asList(getClasspath().classpaths);
}
public List<String> bootClassPath() {
return Arrays.asList(getClasspath().bootclasspaths);
}
public String baseDirectory() {
return getClasspath().baseDir;
}
public void setDefaultStratum(String stratum) {
defaultStratum = stratum;
if (stratum == null) {
stratum = "";
}
try {
JDWP.VirtualMachine.SetDefaultStratum.process(vm,
stratum);
} catch (JDWPException exc) {
throw exc.toJDIException();
}
}
public String getDefaultStratum() {
return defaultStratum;
}
ThreadGroup threadGroupForJDI() {
return threadGroupForJDI;
}
static private class SoftObjectReference extends SoftReference<ObjectReferenceImpl> {
int count;
Long key;
SoftObjectReference(Long key, ObjectReferenceImpl mirror,
ReferenceQueue<ObjectReferenceImpl> queue) {
super(mirror, queue);
this.count = 1;
this.key = key;
}
int count() {
return count;
}
void incrementCount() {
count++;
}
Long key() {
return key;
}
ObjectReferenceImpl object() {
return get();
}
}
}