Add support for specifying the debugging step size
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / RequestHint.java
1 /*
2  * Copyright 2000-2014 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  * Date: Jul 23, 2002
20  * Time: 11:10:11 AM
21  */
22 package com.intellij.debugger.engine;
23
24 import com.intellij.debugger.SourcePosition;
25 import com.intellij.debugger.engine.evaluation.EvaluateException;
26 import com.intellij.debugger.engine.jdi.StackFrameProxy;
27 import com.intellij.debugger.impl.DebuggerUtilsEx;
28 import com.intellij.debugger.impl.PositionUtil;
29 import com.intellij.debugger.jdi.StackFrameProxyImpl;
30 import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
31 import com.intellij.debugger.settings.DebuggerSettings;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.util.Computable;
35 import com.intellij.psi.PsiMethod;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.util.Range;
38 import com.sun.jdi.Location;
39 import com.sun.jdi.Method;
40 import com.sun.jdi.VMDisconnectedException;
41 import com.sun.jdi.request.StepRequest;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 public class RequestHint {
46   public static final int STOP = 0;
47   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.RequestHint");
48   private final int mySize;
49   private final int myDepth;
50   private final SourcePosition myPosition;
51   private final int myFrameCount;
52
53   @Nullable
54   private final MethodFilter myMethodFilter;
55   private boolean myTargetMethodMatched = false;
56
57   private boolean myIgnoreFilters = false;
58   private boolean myRestoreBreakpoints = false;
59
60   public RequestHint(final ThreadReferenceProxyImpl stepThread, final SuspendContextImpl suspendContext, @NotNull MethodFilter methodFilter) {
61     this(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_INTO, methodFilter);
62   }
63
64   public RequestHint(final ThreadReferenceProxyImpl stepThread, final SuspendContextImpl suspendContext, int depth) {
65     this(stepThread, suspendContext, StepRequest.STEP_LINE, depth, null);
66   }
67
68   private RequestHint(final ThreadReferenceProxyImpl stepThread, final SuspendContextImpl suspendContext, int stepSize, int depth, @Nullable MethodFilter methodFilter) {
69     mySize = stepSize;
70     myDepth = depth;
71     myMethodFilter = methodFilter;
72
73     int frameCount = 0;
74     SourcePosition position = null;
75     try {
76       frameCount = stepThread.frameCount();
77
78       position = ApplicationManager.getApplication().runReadAction(new Computable<SourcePosition>() {
79         public SourcePosition compute() {
80           return ContextUtil.getSourcePosition(new StackFrameContext() {
81             public StackFrameProxy getFrameProxy() {
82               try {
83                 return stepThread.frame(0);
84               }
85               catch (EvaluateException e) {
86                 if (LOG.isDebugEnabled()) {
87                   LOG.debug(e);
88                 }
89                 return null;
90               }
91             }
92
93             @NotNull
94             public DebugProcess getDebugProcess() {
95               return suspendContext.getDebugProcess();
96             }
97           });
98         }
99       });
100     }
101     catch (Exception e) {
102       LOG.info(e);
103     }
104     finally {
105       myFrameCount = frameCount;
106       myPosition = position;
107     }
108   }
109
110   public void setIgnoreFilters(boolean ignoreFilters) {
111     myIgnoreFilters = ignoreFilters;
112   }
113
114   public void setRestoreBreakpoints(boolean restoreBreakpoints) {
115     myRestoreBreakpoints = restoreBreakpoints;
116   }
117
118   public boolean isRestoreBreakpoints() {
119     return myRestoreBreakpoints;
120   }
121
122   public boolean isIgnoreFilters() {
123     return myIgnoreFilters;
124   }
125
126   public int getSize() {
127     return mySize;
128   }
129
130   public int getDepth() {
131     return myDepth;
132   }
133
134   @Nullable
135   public MethodFilter getMethodFilter() {
136     return myMethodFilter;
137   }
138
139   public boolean wasStepTargetMethodMatched() {
140     return myMethodFilter instanceof BreakpointStepMethodFilter || myTargetMethodMatched;
141   }
142
143   private boolean isTheSameDepth(SuspendContextImpl context) {
144     final ThreadReferenceProxyImpl contextThread = context.getThread();
145     if (contextThread != null) {
146       try {
147         return myFrameCount == contextThread.frameCount();
148       }
149       catch (EvaluateException ignored) {
150       }
151     }
152     return false;
153   }
154
155   private boolean isOnTheSameLine(SourcePosition locationPosition) {
156     if (myMethodFilter == null) {
157       return myPosition.getLine() == locationPosition.getLine();
158     }
159     else {
160       Range<Integer> exprLines = myMethodFilter.getCallingExpressionLines();
161       return exprLines != null && locationPosition.getLine() >= exprLines.getFrom() && locationPosition.getLine() <= exprLines.getTo();
162     }
163   }
164
165   public int getNextStepDepth(final SuspendContextImpl context) {
166     try {
167       final StackFrameProxyImpl frameProxy = context.getFrameProxy();
168
169       // smart step feature stop check
170       if (myMethodFilter != null &&
171           frameProxy != null &&
172           !(myMethodFilter instanceof BreakpointStepMethodFilter) &&
173           myMethodFilter.locationMatches(context.getDebugProcess(), frameProxy.location()) &&
174           !isTheSameDepth(context)
175         ) {
176         myTargetMethodMatched = true;
177         return STOP;
178       }
179
180       if ((myDepth == StepRequest.STEP_OVER || myDepth == StepRequest.STEP_INTO) && myPosition != null) {
181         final Integer resultDepth = ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
182           public Integer compute() {
183             final SourcePosition locationPosition = ContextUtil.getSourcePosition(context);
184             if (locationPosition != null) {
185               if (myPosition.getFile().equals(locationPosition.getFile()) && isTheSameDepth(context)) {
186                 return isOnTheSameLine(locationPosition) ? myDepth : STOP;
187               }
188             }
189             return null;
190           }
191         });
192         if (resultDepth != null) {
193           return resultDepth.intValue();
194         }
195       }
196
197       // Now check filters
198
199       final DebuggerSettings settings = DebuggerSettings.getInstance();
200
201       if ((myMethodFilter != null || (settings.SKIP_SYNTHETIC_METHODS && !myIgnoreFilters))&& frameProxy != null) {
202         final Location location = frameProxy.location();
203         if (location != null) {
204           if (DebuggerUtils.isSynthetic(location.method())) {
205             return myDepth;
206           }
207         }
208       }
209
210       if (!myIgnoreFilters) {
211         if(settings.SKIP_GETTERS) {
212           boolean isGetter = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>(){
213             public Boolean compute() {
214               final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(PositionUtil.getContextElement(context), PsiMethod.class);
215               return (psiMethod != null && DebuggerUtils.isSimpleGetter(psiMethod))? Boolean.TRUE : Boolean.FALSE;
216             }
217           }).booleanValue();
218
219           if(isGetter) {
220             return StepRequest.STEP_OUT;
221           }
222         }
223
224         if (frameProxy != null) {
225           if (settings.SKIP_CONSTRUCTORS) {
226             final Location location = frameProxy.location();
227             if (location != null) {
228               final Method method = location.method();
229               if (method != null && method.isConstructor()) {
230                 return StepRequest.STEP_OUT;
231               }
232             }
233           }
234
235           if (settings.SKIP_CLASSLOADERS) {
236             final Location location = frameProxy.location();
237             if (location != null && DebuggerUtilsEx.isAssignableFrom("java.lang.ClassLoader", location.declaringType())) {
238               return StepRequest.STEP_OUT;
239             }
240           }
241         }
242
243         for (ExtraSteppingFilter filter : ExtraSteppingFilter.EP_NAME.getExtensions()) {
244           if (filter.isApplicable(context)) return filter.getStepRequestDepth(context);
245         }
246       }
247       // smart step feature
248       if (myMethodFilter != null) {
249         return StepRequest.STEP_OUT;
250       }
251     }
252     catch (VMDisconnectedException ignored) {
253     }
254     catch (EvaluateException e) {
255       LOG.error(e);
256     }
257     return STOP;
258   }
259
260 }