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