b2af9fd6a7e6fc0e84d454b3d1c398dfc185ba24
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / jdi / VirtualMachineProxyImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * @author Eugene Zhuravlev
19  */
20 package com.intellij.debugger.jdi;
21
22 import com.intellij.Patches;
23 import com.intellij.debugger.engine.DebugProcess;
24 import com.intellij.debugger.engine.DebugProcessImpl;
25 import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
26 import com.intellij.debugger.engine.evaluation.EvaluateException;
27 import com.intellij.debugger.engine.jdi.VirtualMachineProxy;
28 import com.intellij.debugger.impl.DebuggerUtilsImpl;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.util.ReflectionUtil;
32 import com.intellij.util.ThreeState;
33 import com.intellij.util.containers.HashMap;
34 import com.intellij.util.containers.MultiMap;
35 import com.sun.jdi.*;
36 import com.sun.jdi.event.EventQueue;
37 import com.sun.jdi.request.EventRequestManager;
38 import com.sun.tools.jdi.JNITypeParser;
39 import com.sun.tools.jdi.TargetVM;
40 import org.jetbrains.annotations.Contract;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import java.lang.reflect.InvocationTargetException;
45 import java.lang.reflect.Method;
46 import java.util.*;
47
48 public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy {
49   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.jdi.VirtualMachineProxyImpl");
50   private final DebugProcessImpl myDebugProcess;
51   private final VirtualMachine myVirtualMachine;
52   private int myTimeStamp = 0;
53   private int myPausePressedCount = 0;
54
55   // cached data
56   private final Map<ObjectReference, ObjectReferenceProxyImpl>  myObjectReferenceProxies = new HashMap<>();
57   private final Map<String, StringReference> myStringLiteralCache = new HashMap<>();
58
59   @NotNull
60   private Map<ThreadReference, ThreadReferenceProxyImpl>  myAllThreads = new HashMap<>();
61   private final Map<ThreadGroupReference, ThreadGroupReferenceProxyImpl> myThreadGroups = new HashMap<>();
62   private boolean myAllThreadsDirty = true;
63   private List<ReferenceType> myAllClasses;
64   private MultiMap<String, ReferenceType> myAllClassesByName;
65   private Map<ReferenceType, List<ReferenceType>> myNestedClassesCache = new HashMap<>();
66
67   public final Throwable mySuspendLogger = new Throwable();
68   private final boolean myVersionHigher_15;
69   private final boolean myVersionHigher_14;
70
71   public VirtualMachineProxyImpl(DebugProcessImpl debugProcess, @NotNull VirtualMachine virtualMachine) {
72     myVirtualMachine = virtualMachine;
73     myDebugProcess = debugProcess;
74
75     myVersionHigher_15 = versionHigher("1.5");
76     myVersionHigher_14 = myVersionHigher_15 || versionHigher("1.4");
77
78     // avoid lazy-init for some properties: the following will pre-calculate values
79     canRedefineClasses();
80     canWatchFieldModification();
81     canPopFrames();
82
83     try {
84       // this will cache classes inside JDI and enable faster search of classes later
85       virtualMachine.allClasses();
86     }
87     catch (Throwable e) {
88       // catch all exceptions in order not to break vm attach process
89       // Example:
90       // java.lang.IllegalArgumentException: Invalid JNI signature character ';'
91       //  caused by some bytecode "optimizers" which break type signatures as a side effect.
92       //  solution if you are using JAX-WS: add -Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true to JVM args
93       LOG.info(e);
94     }
95
96     virtualMachine.topLevelThreadGroups().forEach(this::threadGroupCreated);
97   }
98
99   @NotNull
100   public VirtualMachine getVirtualMachine() {
101     return myVirtualMachine;
102   }
103
104   static final class JNITypeParserReflect {
105     static final Method typeNameToSignatureMethod;
106
107     static {
108       typeNameToSignatureMethod = ReflectionUtil.getDeclaredMethod(JNITypeParser.class, "typeNameToSignature", String.class);
109       if (typeNameToSignatureMethod == null) {
110         LOG.warn("Unable to find JNITypeParser.typeNameToSignature method");
111       }
112     }
113
114     @Nullable
115     static String typeNameToSignature(@NotNull String name) {
116       if (typeNameToSignatureMethod != null) {
117         try {
118           return (String)typeNameToSignatureMethod.invoke(null, name);
119         }
120         catch (Exception ignored) {
121         }
122       }
123       return null;
124     }
125   }
126
127   public List<ReferenceType> classesByName(String s) {
128     String signature = JNITypeParserReflect.typeNameToSignature(s);
129     if (signature != null) {
130       if (myAllClassesByName == null) {
131         myAllClassesByName = new MultiMap<>();
132         allClasses().forEach(t -> myAllClassesByName.putValue(t.signature(), t));
133       }
134       return (List<ReferenceType>)myAllClassesByName.get(signature);
135     }
136     else {
137       return myVirtualMachine.classesByName(s);
138     }
139   }
140
141   public List<ReferenceType> nestedTypes(ReferenceType refType) {
142     List<ReferenceType> nestedTypes = myNestedClassesCache.get(refType);
143     if (nestedTypes == null) {
144       List<ReferenceType> list = Collections.emptyList();
145       try {
146         list = refType.nestedTypes();
147       }
148       catch (Throwable e) {
149         // sometimes some strange errors are thrown from JDI. Do not crash debugger because of this.
150         // Example:
151         //java.lang.StringIndexOutOfBoundsException: String index out of range: 487700285
152         //      at java.lang.String.checkBounds(String.java:375)
153         //      at java.lang.String.<init>(String.java:415)
154         //      at com.sun.tools.jdi.PacketStream.readString(PacketStream.java:392)
155         //      at com.sun.tools.jdi.JDWP$VirtualMachine$AllClassesWithGeneric$ClassInfo.<init>(JDWP.java:1644)
156         LOG.info(e);
157       }
158       if (!list.isEmpty()) {
159         final Set<ReferenceType> candidates = new HashSet<>();
160         final ClassLoaderReference outerLoader = refType.classLoader();
161         for (ReferenceType nested : list) {
162           try {
163             if (outerLoader == null? nested.classLoader() == null : outerLoader.equals(nested.classLoader())) {
164               candidates.add(nested);
165             }
166           }
167           catch (ObjectCollectedException ignored) {
168           }
169         }
170
171         if (!candidates.isEmpty()) {
172           // keep only direct nested types
173           final Set<ReferenceType> nested2 = new HashSet<>();
174           for (final ReferenceType candidate : candidates) {
175             nested2.addAll(nestedTypes(candidate));
176           }
177           candidates.removeAll(nested2);
178         }
179         
180         nestedTypes = candidates.isEmpty() ? Collections.emptyList() : new ArrayList<>(candidates);
181       }
182       else {
183         nestedTypes = Collections.emptyList();
184       }
185       myNestedClassesCache.put(refType, nestedTypes);
186     }
187     return nestedTypes;
188   }
189
190   public List<ReferenceType> allClasses() {
191     List<ReferenceType> allClasses = myAllClasses;
192     if (allClasses == null) {
193       myAllClasses = allClasses = myVirtualMachine.allClasses();
194     }
195     return allClasses;
196   }
197
198   public String toString() {
199     return myVirtualMachine.toString();
200   }
201
202   public void redefineClasses(Map<ReferenceType, byte[]> map) {
203     DebuggerManagerThreadImpl.assertIsManagerThread();
204     try {
205       myVirtualMachine.redefineClasses(map);
206     }
207     finally {
208       clearCaches();
209     }
210   }
211
212   /**
213    * @return a list of all ThreadReferenceProxies
214    */
215   public Collection<ThreadReferenceProxyImpl> allThreads() {
216     DebuggerManagerThreadImpl.assertIsManagerThread();
217     if(myAllThreadsDirty) {
218       myAllThreadsDirty = false;
219
220       final List<ThreadReference> currentThreads = myVirtualMachine.allThreads();
221       final Map<ThreadReference, ThreadReferenceProxyImpl> result = new HashMap<>();
222
223       for (final ThreadReference threadReference : currentThreads) {
224         ThreadReferenceProxyImpl proxy = myAllThreads.get(threadReference);
225         if(proxy == null) {
226           proxy = new ThreadReferenceProxyImpl(this, threadReference);
227         }
228         result.put(threadReference, proxy);
229       }
230       myAllThreads = result;
231     }
232
233     return myAllThreads.values();
234   }
235
236   public void threadStarted(ThreadReference thread) {
237     DebuggerManagerThreadImpl.assertIsManagerThread();
238     getThreadReferenceProxy(thread); // add a proxy
239   }
240
241   public void threadStopped(ThreadReference thread) {
242     DebuggerManagerThreadImpl.assertIsManagerThread();
243     myAllThreads.remove(thread);
244   }
245
246   public void suspend() {
247     DebuggerManagerThreadImpl.assertIsManagerThread();
248     myPausePressedCount++;
249     myVirtualMachine.suspend();
250     clearCaches();
251   }
252
253   public void resume() {
254     DebuggerManagerThreadImpl.assertIsManagerThread();
255     if (myPausePressedCount > 0) {
256       myPausePressedCount--;
257     }
258     clearCaches();
259     LOG.debug("before resume VM");
260     try {
261       myVirtualMachine.resume();
262     }
263     catch (InternalException e) {
264       // ok to ignore. Although documentation says it is safe to invoke resume() on running VM,
265       // sometimes this leads to com.sun.jdi.InternalException: Unexpected JDWP Error: 13 (THREAD_NOT_SUSPENDED)
266       LOG.info(e);
267     }
268     LOG.debug("VM resumed");
269     //logThreads();
270   }
271
272   /**
273    * @return a list of threadGroupProxies
274    */
275   public List<ThreadGroupReferenceProxyImpl> topLevelThreadGroups() {
276     List<ThreadGroupReference> list = getVirtualMachine().topLevelThreadGroups();
277
278     List<ThreadGroupReferenceProxyImpl> result = new ArrayList<>(list.size());
279
280     for (ThreadGroupReference threadGroup : list) {
281       result.add(getThreadGroupReferenceProxy(threadGroup));
282     }
283
284     return result;
285   }
286
287   public void threadGroupCreated(ThreadGroupReference threadGroupReference){
288     DebuggerManagerThreadImpl.assertIsManagerThread();
289     if(!isJ2ME()) {
290       ThreadGroupReferenceProxyImpl proxy = new ThreadGroupReferenceProxyImpl(this, threadGroupReference);
291       myThreadGroups.put(threadGroupReference, proxy);
292     }
293   }
294
295   public boolean isJ2ME() {
296     return isJ2ME(getVirtualMachine());
297   }
298
299   private static boolean isJ2ME(VirtualMachine virtualMachine) {
300     return virtualMachine.version().startsWith("1.0");
301   }
302
303   public void threadGroupRemoved(ThreadGroupReference threadGroupReference){
304     DebuggerManagerThreadImpl.assertIsManagerThread();
305     myThreadGroups.remove(threadGroupReference);
306   }
307
308   public EventQueue eventQueue() {
309     return myVirtualMachine.eventQueue();
310   }
311
312   public EventRequestManager eventRequestManager() {
313     return myVirtualMachine.eventRequestManager();
314   }
315
316   /**
317    * @deprecated use {@link #mirrorOfVoid()} instead
318    */
319   @Deprecated
320   public VoidValue mirrorOf() throws EvaluateException {
321     return mirrorOfVoid();
322   }
323
324   public VoidValue mirrorOfVoid() {
325     return myVirtualMachine.mirrorOfVoid();
326   }
327
328   public BooleanValue mirrorOf(boolean b) {
329     return myVirtualMachine.mirrorOf(b);
330   }
331
332   public ByteValue mirrorOf(byte b) {
333     return myVirtualMachine.mirrorOf(b);
334   }
335
336   public CharValue mirrorOf(char c) {
337     return myVirtualMachine.mirrorOf(c);
338   }
339
340   public ShortValue mirrorOf(short i) {
341     return myVirtualMachine.mirrorOf(i);
342   }
343
344   public IntegerValue mirrorOf(int i) {
345     return myVirtualMachine.mirrorOf(i);
346   }
347
348   public LongValue mirrorOf(long l) {
349     return myVirtualMachine.mirrorOf(l);
350   }
351
352   public FloatValue mirrorOf(float v) {
353     return myVirtualMachine.mirrorOf(v);
354   }
355
356   public DoubleValue mirrorOf(double v) {
357     return myVirtualMachine.mirrorOf(v);
358   }
359
360   public StringReference mirrorOf(String s) {
361     return myVirtualMachine.mirrorOf(s);
362   }
363
364   public StringReference mirrorOfStringLiteral(String s, DebuggerUtilsImpl.SupplierThrowing<StringReference, EvaluateException> generator)
365     throws EvaluateException {
366     StringReference reference = myStringLiteralCache.get(s);
367     if (reference != null && !reference.isCollected()) {
368       return reference;
369     }
370     reference = generator.get();
371     myStringLiteralCache.put(s, reference);
372     return reference;
373   }
374
375   public Process process() {
376     return myVirtualMachine.process();
377   }
378
379   public void dispose() {
380     try {
381       myVirtualMachine.dispose();
382     }
383     catch (UnsupportedOperationException e) {
384       LOG.info(e);
385     }
386
387     if (Patches.JDK_BUG_EVENT_CONTROLLER_LEAK) {
388       // Memory leak workaround, see IDEA-163334
389       TargetVM target = ReflectionUtil.getField(myVirtualMachine.getClass(), myVirtualMachine, TargetVM.class, "target");
390       if (target != null) {
391         Thread controller = ReflectionUtil.getField(target.getClass(), target, Thread.class, "eventController");
392         if (controller != null) {
393           controller.stop();
394         }
395       }
396     }
397   }
398
399   public void exit(int i) {
400     myVirtualMachine.exit(i);
401   }
402
403   private final Capability myWatchFielsModification = new Capability() {
404     protected boolean calcValue() {
405       return myVirtualMachine.canWatchFieldModification();
406     }
407   };
408   public boolean canWatchFieldModification() {
409     return myWatchFielsModification.isAvailable();
410   }
411
412   private final Capability myWatchFieldAccess = new Capability() {
413     protected boolean calcValue() {
414       return myVirtualMachine.canWatchFieldAccess();
415     }
416   };
417   public boolean canWatchFieldAccess() {
418     return myWatchFieldAccess.isAvailable();
419   }
420
421   private final Capability myIsJ2ME = new Capability() {
422     protected boolean calcValue() {
423       return isJ2ME();
424     }
425   };
426   public boolean canInvokeMethods() {
427     return !myIsJ2ME.isAvailable();
428   }
429
430   private final Capability myGetBytecodes = new Capability() {
431     protected boolean calcValue() {
432       return myVirtualMachine.canGetBytecodes();
433     }
434   };
435   public boolean canGetBytecodes() {
436     return myGetBytecodes.isAvailable();
437   }
438
439   private final Capability myGetSyntheticAttribute = new Capability() {
440     protected boolean calcValue() {
441       return myVirtualMachine.canGetSyntheticAttribute();
442     }
443   };
444   public boolean canGetSyntheticAttribute() {
445     return myGetSyntheticAttribute.isAvailable();
446   }
447
448   private final Capability myGetOwnedMonitorInfo = new Capability() {
449     protected boolean calcValue() {
450       return myVirtualMachine.canGetOwnedMonitorInfo();
451     }
452   };
453   public boolean canGetOwnedMonitorInfo() {
454     return myGetOwnedMonitorInfo.isAvailable();
455   }
456
457   private final Capability myGetMonitorFrameInfo = new Capability() {
458     protected boolean calcValue() {
459       return myVirtualMachine.canGetMonitorFrameInfo();
460     }
461   };
462   public boolean canGetMonitorFrameInfo() {
463       return myGetMonitorFrameInfo.isAvailable();
464   }
465   
466   private final Capability myGetCurrentContendedMonitor = new Capability() {
467     protected boolean calcValue() {
468       return myVirtualMachine.canGetCurrentContendedMonitor();
469     }
470   };
471   public boolean canGetCurrentContendedMonitor() {
472     return myGetCurrentContendedMonitor.isAvailable();
473   }
474
475   private final Capability myGetMonitorInfo = new Capability() {
476     protected boolean calcValue() {
477       return myVirtualMachine.canGetMonitorInfo();
478     }
479   };
480   public boolean canGetMonitorInfo() {
481     return myGetMonitorInfo.isAvailable();
482   }
483
484   private final Capability myUseInstanceFilters = new Capability() {
485     protected boolean calcValue() {
486       return myVersionHigher_14 && myVirtualMachine.canUseInstanceFilters();
487     }
488   };
489   public boolean canUseInstanceFilters() {
490     return myUseInstanceFilters.isAvailable();
491   }
492
493   private final Capability myRedefineClasses = new Capability() {
494     protected boolean calcValue() {
495       return myVersionHigher_14 && myVirtualMachine.canRedefineClasses();
496     }
497   };
498   public boolean canRedefineClasses() {
499     return myRedefineClasses.isAvailable();
500   }
501
502   private final Capability myAddMethod = new Capability() {
503     protected boolean calcValue() {
504       return myVersionHigher_14 && myVirtualMachine.canAddMethod();
505     }
506   };
507   public boolean canAddMethod() {
508     return myAddMethod.isAvailable();
509   }
510
511   private final Capability myUnrestrictedlyRedefineClasses = new Capability() {
512     protected boolean calcValue() {
513       return myVersionHigher_14 && myVirtualMachine.canUnrestrictedlyRedefineClasses();
514     }
515   };
516   public boolean canUnrestrictedlyRedefineClasses() {
517     return myUnrestrictedlyRedefineClasses.isAvailable();
518   }
519
520   private final Capability myPopFrames = new Capability() {
521     protected boolean calcValue() {
522       return myVersionHigher_14 && myVirtualMachine.canPopFrames();
523     }
524   };
525   public boolean canPopFrames() {
526     return myPopFrames.isAvailable();
527   }
528
529   private final Capability myForceEarlyReturn = new Capability() {
530     protected boolean calcValue() {
531       return myVirtualMachine.canForceEarlyReturn();
532     }
533   };
534   public boolean canForceEarlyReturn() {
535     return myForceEarlyReturn.isAvailable();
536   }
537
538   private final Capability myCanGetInstanceInfo = new Capability() {
539     protected boolean calcValue() {
540       if (!myVersionHigher_15) {
541         return false;
542       }
543       try {
544         final Method method = VirtualMachine.class.getMethod("canGetInstanceInfo");
545         return (Boolean)method.invoke(myVirtualMachine);
546       }
547       catch (NoSuchMethodException ignored) {
548       }
549       catch (IllegalAccessException e) {
550         LOG.error(e);
551       }
552       catch (InvocationTargetException e) {
553         LOG.error(e);
554       }
555       return false;
556     }
557   };
558   public boolean canGetInstanceInfo() {
559     return myCanGetInstanceInfo.isAvailable();
560   }
561
562   public final boolean versionHigher(String version) {
563     return myVirtualMachine.version().compareTo(version) >= 0;
564   }
565
566   private final Capability myGetSourceDebugExtension = new Capability() {
567     protected boolean calcValue() {
568       return myVersionHigher_14 && myVirtualMachine.canGetSourceDebugExtension();
569     }
570   };
571   public boolean canGetSourceDebugExtension() {
572     return myGetSourceDebugExtension.isAvailable();
573   }
574
575   private final Capability myRequestVMDeathEvent = new Capability() {
576     protected boolean calcValue() {
577       return myVersionHigher_14 && myVirtualMachine.canRequestVMDeathEvent();
578     }
579   };
580   public boolean canRequestVMDeathEvent() {
581     return myRequestVMDeathEvent.isAvailable();
582   }
583
584   private final Capability myGetMethodReturnValues = new Capability() {
585     protected boolean calcValue() {
586       if (myVersionHigher_15) {
587         //return myVirtualMachine.canGetMethodReturnValues();
588         try {
589           //noinspection HardCodedStringLiteral
590           final Method method = VirtualMachine.class.getDeclaredMethod("canGetMethodReturnValues");
591           final Boolean rv = (Boolean)method.invoke(myVirtualMachine);
592           return rv.booleanValue();
593         }
594         catch (NoSuchMethodException ignored) {
595         }
596         catch (IllegalAccessException ignored) {
597         }
598         catch (InvocationTargetException ignored) {
599         }
600       }
601       return false;
602     }
603   };
604   public boolean canGetMethodReturnValues() {
605     return myGetMethodReturnValues.isAvailable();
606   }
607
608   public String getDefaultStratum() {
609     return myVersionHigher_14 ? myVirtualMachine.getDefaultStratum() : null;
610   }
611
612   public String description() {
613     return myVirtualMachine.description();
614   }
615
616   public String version() {
617     return myVirtualMachine.version();
618   }
619
620   public String name() {
621     return myVirtualMachine.name();
622   }
623
624   public void setDebugTraceMode(int i) {
625     myVirtualMachine.setDebugTraceMode(i);
626   }
627
628   @Nullable
629   @Contract("null -> null; !null -> !null")
630   public ThreadReferenceProxyImpl getThreadReferenceProxy(@Nullable ThreadReference thread) {
631     DebuggerManagerThreadImpl.assertIsManagerThread();
632     if (thread == null) {
633       return null;
634     }
635
636     return myAllThreads.computeIfAbsent(thread, t -> new ThreadReferenceProxyImpl(this, t));
637   }
638
639   public ThreadGroupReferenceProxyImpl getThreadGroupReferenceProxy(ThreadGroupReference group) {
640     DebuggerManagerThreadImpl.assertIsManagerThread();
641     if(group == null) {
642       return null;
643     }
644
645     ThreadGroupReferenceProxyImpl proxy = myThreadGroups.get(group);
646     if(proxy == null) {
647       if(!myIsJ2ME.isAvailable()) {
648         proxy = new ThreadGroupReferenceProxyImpl(this, group);
649         myThreadGroups.put(group, proxy);
650       }
651     }
652
653     return proxy;
654   }
655
656   public ObjectReferenceProxyImpl getObjectReferenceProxy(ObjectReference objectReference) {
657     if (objectReference != null) {
658       if (objectReference instanceof ThreadReference) {
659         return getThreadReferenceProxy((ThreadReference)objectReference);
660       }
661       else if (objectReference instanceof ThreadGroupReference) {
662         return getThreadGroupReferenceProxy((ThreadGroupReference)objectReference);
663       }
664       else {
665         ObjectReferenceProxyImpl proxy = myObjectReferenceProxies.get(objectReference);
666         if (proxy == null) {
667           if (objectReference instanceof StringReference) {
668             proxy = new StringReferenceProxy(this, (StringReference)objectReference);
669           }
670           else {
671             proxy = new ObjectReferenceProxyImpl(this, objectReference);
672           }
673           myObjectReferenceProxies.put(objectReference, proxy);
674         }
675         return proxy;
676       }
677     }
678     return null;
679   }
680
681   public boolean equals(Object obj) {
682     LOG.assertTrue(obj instanceof VirtualMachineProxyImpl);
683     return myVirtualMachine.equals(((VirtualMachineProxyImpl)obj).getVirtualMachine());
684   }
685
686   public int hashCode() {
687     return myVirtualMachine.hashCode();
688   }
689
690   public void clearCaches() {
691     LOG.debug("VM cleared");
692
693     myAllClasses = null;
694     myAllClassesByName = null;
695
696     if (!myNestedClassesCache.isEmpty()) {
697       myNestedClassesCache = new HashMap<>(myNestedClassesCache.size());
698     }
699     //myAllThreadsDirty = true;
700     myTimeStamp++;
701   }
702
703   public int getCurrentTime() {
704     return myTimeStamp;
705   }
706
707   public DebugProcess getDebugProcess() {
708     return myDebugProcess;
709   }
710
711   public static boolean isCollected(ObjectReference reference) {
712     try {
713       return !isJ2ME(reference.virtualMachine()) && reference.isCollected();
714     }
715     catch (UnsupportedOperationException e) {
716       LOG.info(e);
717     }
718     return false;
719   }
720
721   public String getResumeStack() {
722     return StringUtil.getThrowableText(mySuspendLogger);
723   }
724
725   public boolean isPausePressed() {
726     return myPausePressedCount > 0;
727   }
728
729   public boolean isSuspended() {
730     return allThreads().stream().anyMatch(thread -> thread.getSuspendCount() != 0);
731   }
732
733   public void logThreads() {
734     if (LOG.isDebugEnabled()) {
735       for (ThreadReferenceProxyImpl thread : allThreads()) {
736         if (!thread.isCollected()) {
737           LOG.debug("suspends " + thread + " " + thread.getSuspendCount() + " " + thread.isSuspended());
738         }
739       }
740     }
741   }
742
743
744   private abstract static class Capability {
745     private ThreeState myValue = ThreeState.UNSURE;
746
747     public final boolean isAvailable() {
748       if (myValue == ThreeState.UNSURE) {
749         try {
750           myValue = ThreeState.fromBoolean(calcValue());
751         }
752         catch (VMDisconnectedException e) {
753           LOG.info(e);
754           myValue = ThreeState.NO;
755         }
756       }
757       return myValue.toBoolean();
758     }
759
760     protected abstract boolean calcValue();
761   }
762
763 }