blob: 55ad817e843c2e4349b45efe366636af0ada7233 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @author Eugene Zhuravlev
*/
package com.intellij.debugger.jdi;
import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.jdi.VirtualMachineProxy;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.HashMap;
import com.sun.jdi.*;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.request.EventRequestManager;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.jdi.VirtualMachineProxyImpl");
private final DebugProcessImpl myDebugProcess;
private final VirtualMachine myVirtualMachine;
private int myTimeStamp = 0;
private int myPausePressedCount = 0;
// cached data
private final Map<ObjectReference, ObjectReferenceProxyImpl> myObjectReferenceProxies = new HashMap<ObjectReference, ObjectReferenceProxyImpl>();
@NotNull
private Map<ThreadReference, ThreadReferenceProxyImpl> myAllThreads = new HashMap<ThreadReference, ThreadReferenceProxyImpl>();
private final Map<ThreadGroupReference, ThreadGroupReferenceProxyImpl> myThreadGroups = new HashMap<ThreadGroupReference, ThreadGroupReferenceProxyImpl>();
private boolean myAllThreadsDirty = true;
private List<ReferenceType> myAllClasses;
private Map<ReferenceType, List<ReferenceType>> myNestedClassesCache = new HashMap<ReferenceType, List<ReferenceType>>();
public Throwable mySuspendLogger = new Throwable();
private final boolean myVersionHigher_15;
private final boolean myVersionHigher_14;
public VirtualMachineProxyImpl(DebugProcessImpl debugProcess, @NotNull VirtualMachine virtualMachine) {
myVirtualMachine = virtualMachine;
myDebugProcess = debugProcess;
myVersionHigher_15 = versionHigher("1.5");
myVersionHigher_14 = myVersionHigher_15 || versionHigher("1.4");
// avoid lazy-init for some properties: the following will pre-calculate values
canRedefineClasses();
canWatchFieldModification();
canPopFrames();
try {
// this will cache classes inside JDI and enable faster search of classes later
virtualMachine.allClasses();
}
catch (Throwable e) {
// catch all exceptions in order not to break vm attach process
// Example:
// java.lang.IllegalArgumentException: Invalid JNI signature character ';'
// caused by some bytecode "optimizers" which break type signatures as a side effect.
// solution if you are using JAX-WS: add -Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true to JVM args
LOG.info(e);
}
List<ThreadGroupReference> groups = virtualMachine.topLevelThreadGroups();
for (ThreadGroupReference threadGroupReference : groups) {
threadGroupCreated(threadGroupReference);
}
}
public VirtualMachine getVirtualMachine() {
return myVirtualMachine;
}
public List<ReferenceType> classesByName(String s) {
return myVirtualMachine.classesByName(s);
}
public List<ReferenceType> nestedTypes(ReferenceType refType) {
List<ReferenceType> nestedTypes = myNestedClassesCache.get(refType);
if (nestedTypes == null) {
List<ReferenceType> list = Collections.emptyList();
try {
list = refType.nestedTypes();
}
catch (Throwable e) {
// sometimes some strange errors are thrown from JDI. Do not crash debugger because of this.
// Example:
//java.lang.StringIndexOutOfBoundsException: String index out of range: 487700285
// at java.lang.String.checkBounds(String.java:375)
// at java.lang.String.<init>(String.java:415)
// at com.sun.tools.jdi.PacketStream.readString(PacketStream.java:392)
// at com.sun.tools.jdi.JDWP$VirtualMachine$AllClassesWithGeneric$ClassInfo.<init>(JDWP.java:1644)
LOG.info(e);
}
if (!list.isEmpty()) {
final Set<ReferenceType> candidates = new HashSet<ReferenceType>();
final ClassLoaderReference outerLoader = refType.classLoader();
for (ReferenceType nested : list) {
try {
if (outerLoader == null? nested.classLoader() == null : outerLoader.equals(nested.classLoader())) {
candidates.add(nested);
}
}
catch (ObjectCollectedException ignored) {
}
}
if (!candidates.isEmpty()) {
// keep only direct nested types
final Set<ReferenceType> nested2 = new HashSet<ReferenceType>();
for (final ReferenceType candidate : candidates) {
nested2.addAll(nestedTypes(candidate));
}
candidates.removeAll(nested2);
}
nestedTypes = candidates.isEmpty()? Collections.<ReferenceType>emptyList() : new ArrayList<ReferenceType>(candidates);
}
else {
nestedTypes = Collections.emptyList();
}
myNestedClassesCache.put(refType, nestedTypes);
}
return nestedTypes;
}
public List<ReferenceType> allClasses() {
List<ReferenceType> allClasses = myAllClasses;
if (allClasses == null) {
myAllClasses = allClasses = myVirtualMachine.allClasses();
}
return allClasses;
}
public String toString() {
return myVirtualMachine.toString();
}
public void redefineClasses(Map<ReferenceType, byte[]> map) {
DebuggerManagerThreadImpl.assertIsManagerThread();
try {
myVirtualMachine.redefineClasses(map);
}
finally {
clearCaches();
}
}
/**
* @return a list of all ThreadReferenceProxies
*/
public Collection<ThreadReferenceProxyImpl> allThreads() {
DebuggerManagerThreadImpl.assertIsManagerThread();
if(myAllThreadsDirty) {
myAllThreadsDirty = false;
final List<ThreadReference> currentThreads = myVirtualMachine.allThreads();
final Map<ThreadReference, ThreadReferenceProxyImpl> result = new HashMap<ThreadReference, ThreadReferenceProxyImpl>();
for (final ThreadReference threadReference : currentThreads) {
ThreadReferenceProxyImpl proxy = myAllThreads.get(threadReference);
if(proxy == null) {
proxy = new ThreadReferenceProxyImpl(this, threadReference);
}
result.put(threadReference, proxy);
}
myAllThreads = result;
}
return myAllThreads.values();
}
public void threadStarted(ThreadReference thread) {
DebuggerManagerThreadImpl.assertIsManagerThread();
final Map<ThreadReference, ThreadReferenceProxyImpl> allThreads = myAllThreads;
if (!allThreads.containsKey(thread)) {
allThreads.put(thread, new ThreadReferenceProxyImpl(this, thread));
}
}
public void threadStopped(ThreadReference thread) {
DebuggerManagerThreadImpl.assertIsManagerThread();
myAllThreads.remove(thread);
}
public void suspend() {
DebuggerManagerThreadImpl.assertIsManagerThread();
myPausePressedCount++;
myVirtualMachine.suspend();
clearCaches();
}
public void resume() {
DebuggerManagerThreadImpl.assertIsManagerThread();
if (myPausePressedCount > 0) {
myPausePressedCount--;
}
clearCaches();
if (LOG.isDebugEnabled()) {
LOG.debug("before resume VM");
}
try {
myVirtualMachine.resume();
}
catch (InternalException e) {
// ok to ignore. Although documentation says it is safe to invoke resume() on running VM,
// sometimes this leads to com.sun.jdi.InternalException: Unexpected JDWP Error: 13 (THREAD_NOT_SUSPENDED)
LOG.info(e);
}
if (LOG.isDebugEnabled()) {
LOG.debug("VM resumed");
}
//logThreads();
}
/**
* @return a list of threadGroupProxies
*/
public List<ThreadGroupReferenceProxyImpl> topLevelThreadGroups() {
List<ThreadGroupReference> list = getVirtualMachine().topLevelThreadGroups();
List<ThreadGroupReferenceProxyImpl> result = new ArrayList<ThreadGroupReferenceProxyImpl>(list.size());
for (ThreadGroupReference threadGroup : list) {
result.add(getThreadGroupReferenceProxy(threadGroup));
}
return result;
}
public void threadGroupCreated(ThreadGroupReference threadGroupReference){
DebuggerManagerThreadImpl.assertIsManagerThread();
if(!isJ2ME()) {
ThreadGroupReferenceProxyImpl proxy = new ThreadGroupReferenceProxyImpl(this, threadGroupReference);
myThreadGroups.put(threadGroupReference, proxy);
}
}
public boolean isJ2ME() {
return isJ2ME(getVirtualMachine());
}
private static boolean isJ2ME(VirtualMachine virtualMachine) {
return virtualMachine.version().startsWith("1.0");
}
public void threadGroupRemoved(ThreadGroupReference threadGroupReference){
DebuggerManagerThreadImpl.assertIsManagerThread();
myThreadGroups.remove(threadGroupReference);
}
public EventQueue eventQueue() {
return myVirtualMachine.eventQueue();
}
public EventRequestManager eventRequestManager() {
return myVirtualMachine.eventRequestManager();
}
public VoidValue mirrorOf() throws EvaluateException {
return myVirtualMachine.mirrorOfVoid();
}
public BooleanValue mirrorOf(boolean b) {
return myVirtualMachine.mirrorOf(b);
}
public ByteValue mirrorOf(byte b) {
return myVirtualMachine.mirrorOf(b);
}
public CharValue mirrorOf(char c) {
return myVirtualMachine.mirrorOf(c);
}
public ShortValue mirrorOf(short i) {
return myVirtualMachine.mirrorOf(i);
}
public IntegerValue mirrorOf(int i) {
return myVirtualMachine.mirrorOf(i);
}
public LongValue mirrorOf(long l) {
return myVirtualMachine.mirrorOf(l);
}
public FloatValue mirrorOf(float v) {
return myVirtualMachine.mirrorOf(v);
}
public DoubleValue mirrorOf(double v) {
return myVirtualMachine.mirrorOf(v);
}
public StringReference mirrorOf(String s) {
return myVirtualMachine.mirrorOf(s);
}
public Process process() {
return myVirtualMachine.process();
}
public void dispose() {
myVirtualMachine.dispose();
}
public void exit(int i) {
myVirtualMachine.exit(i);
}
private final Capability myWatchFielsModification = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canWatchFieldModification();
}
};
public boolean canWatchFieldModification() {
return myWatchFielsModification.isAvailable();
}
private final Capability myWatchFieldAccess = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canWatchFieldAccess();
}
};
public boolean canWatchFieldAccess() {
return myWatchFieldAccess.isAvailable();
}
private final Capability myIsJ2ME = new Capability() {
protected boolean calcValue() {
return isJ2ME();
}
};
public boolean canInvokeMethods() {
return !myIsJ2ME.isAvailable();
}
private final Capability myGetBytecodes = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canGetBytecodes();
}
};
public boolean canGetBytecodes() {
return myGetBytecodes.isAvailable();
}
private final Capability myGetSyntheticAttribute = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canGetSyntheticAttribute();
}
};
public boolean canGetSyntheticAttribute() {
return myGetSyntheticAttribute.isAvailable();
}
private final Capability myGetOwnedMonitorInfo = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canGetOwnedMonitorInfo();
}
};
public boolean canGetOwnedMonitorInfo() {
return myGetOwnedMonitorInfo.isAvailable();
}
private final Capability myGetMonitorFrameInfo = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canGetMonitorFrameInfo();
}
};
public boolean canGetMonitorFrameInfo() {
return myGetMonitorFrameInfo.isAvailable();
}
private final Capability myGetCurrentContendedMonitor = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canGetCurrentContendedMonitor();
}
};
public boolean canGetCurrentContendedMonitor() {
return myGetCurrentContendedMonitor.isAvailable();
}
private final Capability myGetMonitorInfo = new Capability() {
protected boolean calcValue() {
return myVirtualMachine.canGetMonitorInfo();
}
};
public boolean canGetMonitorInfo() {
return myGetMonitorInfo.isAvailable();
}
private final Capability myUseInstanceFilters = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canUseInstanceFilters();
}
};
public boolean canUseInstanceFilters() {
return myUseInstanceFilters.isAvailable();
}
private final Capability myRedefineClasses = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canRedefineClasses();
}
};
public boolean canRedefineClasses() {
return myRedefineClasses.isAvailable();
}
private final Capability myAddMethod = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canAddMethod();
}
};
public boolean canAddMethod() {
return myAddMethod.isAvailable();
}
private final Capability myUnrestrictedlyRedefineClasses = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canUnrestrictedlyRedefineClasses();
}
};
public boolean canUnrestrictedlyRedefineClasses() {
return myUnrestrictedlyRedefineClasses.isAvailable();
}
private final Capability myPopFrames = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canPopFrames();
}
};
public boolean canPopFrames() {
return myPopFrames.isAvailable();
}
private final Capability myCanGetInstanceInfo = new Capability() {
protected boolean calcValue() {
if (!myVersionHigher_15) {
return false;
}
try {
final Method method = VirtualMachine.class.getMethod("canGetInstanceInfo");
return (Boolean)method.invoke(myVirtualMachine);
}
catch (NoSuchMethodException ignored) {
}
catch (IllegalAccessException e) {
LOG.error(e);
}
catch (InvocationTargetException e) {
LOG.error(e);
}
return false;
}
};
public boolean canGetInstanceInfo() {
return myCanGetInstanceInfo.isAvailable();
}
public final boolean versionHigher(String version) {
return myVirtualMachine.version().compareTo(version) >= 0;
}
private final Capability myGetSourceDebugExtension = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canGetSourceDebugExtension();
}
};
public boolean canGetSourceDebugExtension() {
return myGetSourceDebugExtension.isAvailable();
}
private final Capability myRequestVMDeathEvent = new Capability() {
protected boolean calcValue() {
return myVersionHigher_14 && myVirtualMachine.canRequestVMDeathEvent();
}
};
public boolean canRequestVMDeathEvent() {
return myRequestVMDeathEvent.isAvailable();
}
private final Capability myGetMethodReturnValues = new Capability() {
protected boolean calcValue() {
if (myVersionHigher_15) {
//return myVirtualMachine.canGetMethodReturnValues();
try {
//noinspection HardCodedStringLiteral
final Method method = VirtualMachine.class.getDeclaredMethod("canGetMethodReturnValues");
final Boolean rv = (Boolean)method.invoke(myVirtualMachine);
return rv.booleanValue();
}
catch (NoSuchMethodException ignored) {
}
catch (IllegalAccessException ignored) {
}
catch (InvocationTargetException ignored) {
}
}
return false;
}
};
public boolean canGetMethodReturnValues() {
return myGetMethodReturnValues.isAvailable();
}
public String getDefaultStratum() {
return myVersionHigher_14 ? myVirtualMachine.getDefaultStratum() : null;
}
public String description() {
return myVirtualMachine.description();
}
public String version() {
return myVirtualMachine.version();
}
public String name() {
return myVirtualMachine.name();
}
public void setDebugTraceMode(int i) {
myVirtualMachine.setDebugTraceMode(i);
}
public ThreadReferenceProxyImpl getThreadReferenceProxy(ThreadReference thread) {
DebuggerManagerThreadImpl.assertIsManagerThread();
if(thread == null) {
return null;
}
ThreadReferenceProxyImpl proxy = myAllThreads.get(thread);
if(proxy == null) {
proxy = new ThreadReferenceProxyImpl(this, thread);
myAllThreads.put(thread, proxy);
}
return proxy;
}
public ThreadGroupReferenceProxyImpl getThreadGroupReferenceProxy(ThreadGroupReference group) {
DebuggerManagerThreadImpl.assertIsManagerThread();
if(group == null) {
return null;
}
ThreadGroupReferenceProxyImpl proxy = myThreadGroups.get(group);
if(proxy == null) {
if(!myIsJ2ME.isAvailable()) {
proxy = new ThreadGroupReferenceProxyImpl(this, group);
myThreadGroups.put(group, proxy);
}
}
return proxy;
}
public ObjectReferenceProxyImpl getObjectReferenceProxy(ObjectReference objectReference) {
if (objectReference != null) {
if (objectReference instanceof ThreadReference) {
return getThreadReferenceProxy((ThreadReference)objectReference);
}
else if (objectReference instanceof ThreadGroupReference) {
return getThreadGroupReferenceProxy((ThreadGroupReference)objectReference);
}
else {
ObjectReferenceProxyImpl proxy = myObjectReferenceProxies.get(objectReference);
if (proxy == null) {
if (objectReference instanceof StringReference) {
proxy = new StringReferenceProxy(this, (StringReference)objectReference);
}
else {
proxy = new ObjectReferenceProxyImpl(this, objectReference);
}
myObjectReferenceProxies.put(objectReference, proxy);
}
return proxy;
}
}
return null;
}
public boolean equals(Object obj) {
LOG.assertTrue(obj instanceof VirtualMachineProxyImpl);
return myVirtualMachine.equals(((VirtualMachineProxyImpl)obj).getVirtualMachine());
}
public int hashCode() {
return myVirtualMachine.hashCode();
}
public void clearCaches() {
if (LOG.isDebugEnabled()) {
LOG.debug("VM cleared");
}
myAllClasses = null;
if (!myNestedClassesCache.isEmpty()) {
myNestedClassesCache = new HashMap<ReferenceType, List<ReferenceType>>(myNestedClassesCache.size());
}
//myAllThreadsDirty = true;
myTimeStamp++;
}
public int getCurrentTime() {
return myTimeStamp;
}
public DebugProcess getDebugProcess() {
return myDebugProcess;
}
public static boolean isCollected(ObjectReference reference) {
return !isJ2ME(reference.virtualMachine()) && reference.isCollected();
}
public String getResumeStack() {
return StringUtil.getThrowableText(mySuspendLogger);
}
public boolean isPausePressed() {
return myPausePressedCount > 0;
}
public boolean isSuspended() {
for (ThreadReferenceProxyImpl thread : allThreads()) {
if (thread.getSuspendCount() != 0) {
return true;
}
}
return false;
}
public void logThreads() {
if(LOG.isDebugEnabled()) {
for (ThreadReferenceProxyImpl thread : allThreads()) {
if (!thread.isCollected()) {
LOG.debug("suspends " + thread + " " + thread.getSuspendCount() + " " + thread.isSuspended());
}
}
}
}
private abstract static class Capability {
private Boolean myValue = null;
public final boolean isAvailable() {
if (myValue == null) {
try {
myValue = Boolean.valueOf(calcValue());
}
catch (VMDisconnectedException e) {
LOG.info(e);
myValue = Boolean.FALSE;
}
}
return myValue.booleanValue();
}
protected abstract boolean calcValue();
}
}