java debugger cleanup
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / jdi / StackFrameProxyImpl.java
1 /*
2  * Copyright 2000-2015 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.debugger.DebuggerBundle;
23 import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
24 import com.intellij.debugger.engine.evaluation.EvaluateException;
25 import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
26 import com.intellij.debugger.engine.jdi.StackFrameProxy;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.sun.jdi.*;
29 import gnu.trove.THashMap;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Map;
38
39 public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy {
40   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.jdi.StackFrameProxyImpl");
41   private final ThreadReferenceProxyImpl myThreadProxy;
42   private final int myFrameFromBottomIndex; // 1-based
43
44   //caches
45   private int myFrameIndex = -1;
46   private StackFrame myStackFrame;
47   private ObjectReference myThisReference;
48   private ClassLoaderReference myClassLoader;
49   private Boolean myIsObsolete = null;
50   private Map<LocalVariable, Value> myAllValues;
51
52   public StackFrameProxyImpl(ThreadReferenceProxyImpl threadProxy, @NotNull StackFrame frame, int fromBottomIndex /* 1-based */) {
53     super(threadProxy.getVirtualMachine());
54     myThreadProxy = threadProxy;
55     myFrameFromBottomIndex = fromBottomIndex;
56     myStackFrame = frame;
57   }
58
59   public boolean isObsolete() throws EvaluateException {
60     DebuggerManagerThreadImpl.assertIsManagerThread();
61     checkValid();
62     if (myIsObsolete != null) {
63       return myIsObsolete.booleanValue();
64     }
65     InvalidStackFrameException error = null;
66     for (int attempt = 0; attempt < 2; attempt++) {
67       try {
68         boolean isObsolete = (getVirtualMachine().canRedefineClasses() && location().method().isObsolete());
69         myIsObsolete = isObsolete? Boolean.TRUE : Boolean.FALSE;
70         return isObsolete;
71       }
72       catch (InvalidStackFrameException e) {
73         error = e;
74         clearCaches();
75       }
76       catch (InternalException e) {
77         if (e.errorCode() == 23 /*INVALID_METHODID according to JDI sources*/) {
78           myIsObsolete = Boolean.TRUE;
79           return true;
80         }
81         throw e;
82       }
83     }
84     throw new EvaluateException(error.getMessage(), error);
85   }
86
87   @Override
88   public boolean isValid() {
89     if (!super.isValid()) {
90       return false;
91     }
92     try {
93       if (myStackFrame != null) {
94         myStackFrame.location(); //extra check if jdi frame is valid
95       }
96       return true;
97     } catch (InvalidStackFrameException e) {
98       return false;
99     }
100   }
101
102   @Override
103   protected void clearCaches() {
104     DebuggerManagerThreadImpl.assertIsManagerThread();
105     if (LOG.isDebugEnabled()) {
106       LOG.debug("caches cleared " + super.toString());
107     }
108     myFrameIndex = -1;
109     myStackFrame = null;
110     myIsObsolete = null;
111     myThisReference = null;
112     myClassLoader = null;
113     myAllValues = null;
114   }
115
116   /**
117    * Use with caution. Better access stackframe data through the Proxy's methods
118    */
119
120   @Override
121   public StackFrame getStackFrame() throws EvaluateException  {
122     DebuggerManagerThreadImpl.assertIsManagerThread();
123
124     checkValid();
125
126     if (myStackFrame == null) {
127       try {
128         final ThreadReference threadRef = myThreadProxy.getThreadReference();
129         myStackFrame = threadRef.frame(getFrameIndex());
130       }
131       catch (IndexOutOfBoundsException e) {
132         throw new EvaluateException(e.getMessage(), e);
133       }
134       catch (ObjectCollectedException ignored) {
135         throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.thread.collected"));
136       }
137       catch (IncompatibleThreadStateException e) {
138         throw EvaluateExceptionUtil.createEvaluateException(e);
139       }
140     }
141
142     return myStackFrame;
143   }
144
145   @Override
146   public int getFrameIndex() throws EvaluateException {
147     DebuggerManagerThreadImpl.assertIsManagerThread();
148     checkValid();
149     if(myFrameIndex == -1) {
150       int count = myThreadProxy.frameCount();
151
152       if(myFrameFromBottomIndex  > count) {
153         throw EvaluateExceptionUtil.createEvaluateException(new IncompatibleThreadStateException());
154       }
155
156       myFrameIndex = count - myFrameFromBottomIndex;
157     }
158     return myFrameIndex;
159   }
160
161 //  public boolean isProxiedFrameValid() {
162 //    if (myStackFrame != null) {
163 //      try {
164 //        myStackFrame.thread();
165 //        return true;
166 //      }
167 //      catch (InvalidStackFrameException e) {
168 //      }
169 //    }
170 //    return false;
171 //  }
172
173   @Override
174   public VirtualMachineProxyImpl getVirtualMachine() {
175     return (VirtualMachineProxyImpl) myTimer;
176   }
177
178   @Override
179   public Location location() throws EvaluateException {
180     InvalidStackFrameException error = null;
181     for (int attempt = 0; attempt < 2; attempt++) {
182       try {
183         return getStackFrame().location();
184       }
185       catch (InvalidStackFrameException e) {
186         error = e;
187         clearCaches();
188       }
189     }
190     throw new EvaluateException(error.getMessage(), error);
191   }
192
193   @Override
194   public ThreadReferenceProxyImpl threadProxy() {
195     return myThreadProxy;
196   }
197
198   public @NonNls String toString() {
199     try {
200       return "StackFrameProxyImpl: " + getStackFrame().toString();
201     }
202     catch (EvaluateException e) {
203       return "StackFrameProxyImpl: " + e.getMessage() + "; frameFromBottom = " + myFrameFromBottomIndex + " threadName = " + threadProxy().name();
204     }
205   }
206
207   @Nullable
208   public ObjectReference thisObject() throws EvaluateException {
209     DebuggerManagerThreadImpl.assertIsManagerThread();
210     checkValid();
211     try {
212       for (int attempt = 0; attempt < 2; attempt++) {
213         try {
214           if(myThisReference == null) {
215             myThisReference = getStackFrame().thisObject();
216           }
217           break;
218         }
219         catch (InvalidStackFrameException ignored) {
220           clearCaches();
221         }
222       }
223     }
224     catch (InternalException e) {
225       // suppress some internal errors caused by bugs in specific JDI implementations
226       if(e.errorCode() != 23) {
227         throw EvaluateExceptionUtil.createEvaluateException(e);
228       }
229     }
230     return myThisReference;
231   }
232
233   public List<LocalVariableProxyImpl> visibleVariables() throws EvaluateException {
234     DebuggerManagerThreadImpl.assertIsManagerThread();
235     InvalidStackFrameException error = null;
236     for (int attempt = 0; attempt < 2; attempt++) {
237       try {
238         final List<LocalVariable> list = getStackFrame().visibleVariables();
239         final List<LocalVariableProxyImpl> locals = new ArrayList<LocalVariableProxyImpl>(list.size());
240         for (LocalVariable localVariable : list) {
241           LOG.assertTrue(localVariable != null);
242           locals.add(new LocalVariableProxyImpl(this, localVariable));
243         }
244         return locals;
245       }
246       catch (InvalidStackFrameException e) {
247         error = e;
248         clearCaches();
249       }
250       catch (AbsentInformationException e) {
251         throw EvaluateExceptionUtil.createEvaluateException(e);
252       }
253     }
254     throw new EvaluateException(error.getMessage(), error);
255   }
256
257   @Override
258   public LocalVariableProxyImpl visibleVariableByName(String name) throws EvaluateException  {
259     DebuggerManagerThreadImpl.assertIsManagerThread();
260     final LocalVariable variable = visibleVariableByNameInt(name);
261     return variable != null ? new LocalVariableProxyImpl(this, variable) : null;
262   }
263
264   @Nullable
265   public Value visibleValueByName(@NotNull String name) throws EvaluateException {
266     LocalVariable variable = visibleVariableByNameInt(name);
267     return variable != null ? getValue(new LocalVariableProxyImpl(this, variable)) : null;
268   }
269
270   protected LocalVariable visibleVariableByNameInt(String name) throws EvaluateException  {
271     DebuggerManagerThreadImpl.assertIsManagerThread();
272     InvalidStackFrameException error = null;
273     for (int attempt = 0; attempt < 2; attempt++) {
274       try {
275         try {
276           return getStackFrame().visibleVariableByName(name);
277         }
278         catch (InvalidStackFrameException e) {
279           error = e;
280           clearCaches();
281         }
282       }
283       catch (InvalidStackFrameException e) {
284         throw EvaluateExceptionUtil.createEvaluateException(e);
285       }
286       catch (AbsentInformationException e) {
287         throw EvaluateExceptionUtil.createEvaluateException(e);
288       }
289     }
290     throw new EvaluateException(error.getMessage(), error);
291   }
292
293   public Value getValue(LocalVariableProxyImpl localVariable) throws EvaluateException {
294     DebuggerManagerThreadImpl.assertIsManagerThread();
295     InvalidStackFrameException error = null;
296     for (int attempt = 0; attempt < 2; attempt++) {
297       try {
298         return getAllValues().get(localVariable.getVariable());
299       }
300       catch (InvalidStackFrameException e) {
301         error = e;
302         clearCaches();
303       }
304     }
305     throw new EvaluateException(error.getMessage(), error);
306   }
307
308   public List<Value> getArgumentValues() throws EvaluateException {
309     DebuggerManagerThreadImpl.assertIsManagerThread();
310     InvalidStackFrameException error = null;
311     for (int attempt = 0; attempt < 2; attempt++) {
312       try {
313         final StackFrame stackFrame = getStackFrame();
314         return stackFrame != null? stackFrame.getArgumentValues() : Collections.<Value>emptyList();
315       }
316       catch (InternalException e) {
317         // From Oracle's forums:
318         // This could be a JPDA bug. Unexpected JDWP Error: 32 means that an 'opaque' frame was detected at the lower JPDA levels,
319         // typically a native frame.
320         if (e.errorCode() == 32 /*opaque frame JDI bug*/ ) {
321           return Collections.emptyList();
322         }
323         else {
324           throw e;
325         }
326       }
327       catch (InvalidStackFrameException e) {
328         error = e;
329         clearCaches();
330       }
331     }
332     throw new EvaluateException(error.getMessage(), error);
333   }
334
335   private Map<LocalVariable, Value> getAllValues() throws EvaluateException{
336     DebuggerManagerThreadImpl.assertIsManagerThread();
337     checkValid();
338     if (myAllValues == null) {
339       try {
340         StackFrame stackFrame = getStackFrame();
341         myAllValues = new THashMap<LocalVariable, Value>(stackFrame.getValues(stackFrame.visibleVariables()));
342       }
343       catch (InconsistentDebugInfoException ignored) {
344         clearCaches();
345         throw EvaluateExceptionUtil.INCONSISTEND_DEBUG_INFO;
346       }
347       catch (AbsentInformationException e) {
348         throw EvaluateExceptionUtil.createEvaluateException(e);
349       }
350     }
351     return myAllValues;
352   }
353
354   public void setValue(LocalVariableProxyImpl localVariable, Value value) throws EvaluateException, ClassNotLoadedException, InvalidTypeException {
355     DebuggerManagerThreadImpl.assertIsManagerThread();
356     InvalidStackFrameException error = null;
357     for (int attempt = 0; attempt < 2; attempt++) {
358       try {
359         final LocalVariable variable = localVariable.getVariable();
360         final StackFrame stackFrame = getStackFrame();
361         stackFrame.setValue(variable, (value instanceof ObjectReference)? ((ObjectReference)value) : value);
362         if (myAllValues != null) {
363           // update cached data if any
364           // re-read the value just set from the stackframe to be 100% sure
365           myAllValues.put(variable, stackFrame.getValue(variable));
366         }
367         return;
368       }
369       catch (InvalidStackFrameException e) {
370         error = e;
371         clearCaches();
372       }
373     }
374     throw new EvaluateException(error.getMessage(), error);
375   }
376
377   public int hashCode() {
378     return 31 * myThreadProxy.hashCode() + myFrameFromBottomIndex;
379   }
380
381
382   public boolean equals(final Object obj) {
383     if (!(obj instanceof StackFrameProxyImpl)) {
384       return false;
385     }
386     StackFrameProxyImpl frameProxy = (StackFrameProxyImpl)obj;
387     if(frameProxy == this)return true;
388
389     return (myFrameFromBottomIndex == frameProxy.myFrameFromBottomIndex)  &&
390            (myThreadProxy.equals(frameProxy.myThreadProxy));
391   }
392
393   public boolean isLocalVariableVisible(LocalVariableProxyImpl var) throws EvaluateException {
394     try {
395       return var.getVariable().isVisible(getStackFrame());
396     }
397     catch (IllegalArgumentException ignored) {
398       // can be thrown if frame's method is different than variable's method
399       return false;
400     }
401   }
402
403   @Override
404   public ClassLoaderReference getClassLoader() throws EvaluateException {
405     if(myClassLoader == null) {
406       myClassLoader = location().declaringType().classLoader();
407     }
408     return myClassLoader;
409   }
410
411   public boolean isBottom() {
412     return myFrameFromBottomIndex == 1;
413   }
414
415   public int getIndexFromBottom() {
416     return myFrameFromBottomIndex;
417   }
418 }
419