IDEA-126257 JDK 1.8: Debugger doesn't show variables *outside* lambda - better check...
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / evaluation / expression / LocalVariableEvaluator.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  * Class LocalVariableEvaluator
19  * @author Jeka
20  */
21 package com.intellij.debugger.engine.evaluation.expression;
22
23 import com.intellij.debugger.DebuggerBundle;
24 import com.intellij.debugger.engine.DebugProcess;
25 import com.intellij.debugger.engine.evaluation.EvaluateException;
26 import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
27 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
28 import com.intellij.debugger.engine.jdi.StackFrameProxy;
29 import com.intellij.debugger.impl.PositionUtil;
30 import com.intellij.debugger.impl.SimpleStackFrameContext;
31 import com.intellij.debugger.jdi.LocalVariableProxyImpl;
32 import com.intellij.debugger.jdi.StackFrameProxyImpl;
33 import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
34 import com.intellij.debugger.ui.impl.watch.LocalVariableDescriptorImpl;
35 import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.diagnostic.Logger;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.util.Computable;
40 import com.intellij.psi.JavaPsiFacade;
41 import com.intellij.psi.PsiElement;
42 import com.intellij.psi.PsiVariable;
43 import com.sun.jdi.*;
44 import org.jetbrains.annotations.Nullable;
45
46 import java.util.List;
47
48 class LocalVariableEvaluator implements Evaluator {
49   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.evaluation.expression.LocalVariableEvaluator");
50
51   private final String myLocalVariableName;
52   private EvaluationContextImpl myContext;
53   private LocalVariableProxyImpl myEvaluatedVariable;
54   private final boolean myCanScanFrames;
55   private int myParameterIndex = -1;
56
57   public LocalVariableEvaluator(String localVariableName, boolean canScanFrames) {
58     myLocalVariableName = localVariableName;
59     myCanScanFrames = canScanFrames;
60   }
61
62   public void setParameterIndex(int parameterIndex) {
63     myParameterIndex = parameterIndex;
64   }
65
66   @Override
67   public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
68     StackFrameProxyImpl frameProxy = context.getFrameProxy();
69     if (frameProxy == null) {
70       throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.no.stackframe"));
71     }
72
73     try {
74       ThreadReferenceProxyImpl threadProxy = null;
75       int lastFrameIndex = -1;
76       PsiVariable variable = null;
77
78       boolean topFrame = true;
79
80       while (true) {
81         try {
82           LocalVariableProxyImpl local = frameProxy.visibleVariableByName(myLocalVariableName);
83           if (local != null) {
84             if (topFrame ||
85                 variable.equals(resolveVariable(frameProxy, myLocalVariableName, context.getProject(), context.getDebugProcess()))) {
86               myEvaluatedVariable = local;
87               myContext = context;
88               return frameProxy.getValue(local);
89             }
90           }
91         }
92         catch (EvaluateException e) {
93           if (!(e.getCause() instanceof AbsentInformationException)) {
94             throw e;
95           }
96           if (topFrame) {
97             if (myParameterIndex < 0) {
98               throw e;
99             }
100             final List<Value> values = frameProxy.getArgumentValues();
101             if (values.isEmpty() || myParameterIndex >= values.size()) {
102               throw e;
103             }
104             return values.get(myParameterIndex);
105           }
106         }
107
108         if (myCanScanFrames) {
109           if (topFrame) {
110             variable = resolveVariable(frameProxy, myLocalVariableName, context.getProject(), context.getDebugProcess());
111             if (variable == null) break;
112           }
113           if (threadProxy == null /* initialize it lazily */) {
114             threadProxy = frameProxy.threadProxy();
115             lastFrameIndex = threadProxy.frameCount() - 1;
116           }
117           int currentFrameIndex = frameProxy.getFrameIndex();
118           if (currentFrameIndex < lastFrameIndex) {
119             frameProxy = threadProxy.frame(currentFrameIndex + 1);
120             if (frameProxy != null) {
121               topFrame = false;
122               continue;
123             }
124           }
125         }
126
127         break;
128       }
129       throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.local.variable.missing", myLocalVariableName));
130     }
131     catch (EvaluateException e) {
132       myEvaluatedVariable = null;
133       myContext = null;
134       throw e;
135     }
136   }
137
138   @Override
139   public Modifier getModifier() {
140     Modifier modifier = null;
141     if (myEvaluatedVariable != null && myContext != null) {
142       modifier = new Modifier() {
143         @Override
144         public boolean canInspect() {
145           return true;
146         }
147
148         @Override
149         public boolean canSetValue() {
150           return true;
151         }
152
153         @Override
154         public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
155           StackFrameProxyImpl frameProxy = myContext.getFrameProxy();
156           try {
157             assert frameProxy != null;
158             frameProxy.setValue(myEvaluatedVariable, value);
159           }
160           catch (EvaluateException e) {
161             LOG.error(e);
162           }
163         }
164
165         @Override
166         public Type getExpectedType() throws ClassNotLoadedException {
167           try {
168             return myEvaluatedVariable.getType();
169           }
170           catch (EvaluateException e) {
171             LOG.error(e);
172             return null;
173           }
174         }
175
176         @Override
177         public NodeDescriptorImpl getInspectItem(Project project) {
178           return new LocalVariableDescriptorImpl(project, myEvaluatedVariable);
179         }
180       };
181     }
182     return modifier;
183   }
184
185   @Nullable
186   private static PsiVariable resolveVariable(final StackFrameProxy frame,
187                                              final String name,
188                                              final Project project,
189                                              final DebugProcess process) {
190     return ApplicationManager.getApplication().runReadAction(new Computable<PsiVariable>() {
191       @Override
192       public PsiVariable compute() {
193         PsiElement place = PositionUtil.getContextElement(new SimpleStackFrameContext(frame, process));
194         if (place == null) return null;
195         return JavaPsiFacade.getInstance(project).getResolveHelper().resolveReferencedVariable(name, place);
196       }
197     });
198   }
199
200   @Override
201   public String toString() {
202     return myLocalVariableName;
203   }
204 }