1 /*******************************************************************************
2  * Copyright (c) 2000, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.debug.ui.actions;
15 
16 
17 import org.eclipse.core.runtime.CoreException;
18 import org.eclipse.debug.core.DebugEvent;
19 import org.eclipse.debug.core.DebugException;
20 import org.eclipse.debug.core.DebugPlugin;
21 import org.eclipse.debug.core.IDebugEventFilter;
22 import org.eclipse.jdt.core.IMethod;
23 import org.eclipse.jdt.core.JavaModelException;
24 import org.eclipse.jdt.core.Signature;
25 import org.eclipse.jdt.debug.core.IJavaDebugTarget;
26 import org.eclipse.jdt.debug.core.IJavaStackFrame;
27 import org.eclipse.jdt.debug.core.IJavaThread;
28 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
29 import org.eclipse.jface.dialogs.MessageDialog;
30 import org.eclipse.osgi.util.NLS;
31 
32 /**
33  * Handles stepping into a selected method, for a specific thread.
34  */
35 public class StepIntoSelectionHandler implements IDebugEventFilter {
36 
37 	/**
38 	 * The method to step into
39 	 */
40 	private IMethod fMethod;
41 
42 	/**
43 	 * Resolved signature of the method to step into
44 	 */
45 	private String fResolvedSignature;
46 
47 	/**
48 	 * The thread in which to step
49 	 */
50 	private IJavaThread fThread;
51 
52 	/**
53 	 * The initial stack frame
54 	 */
55 	private String fOriginalName;
56 	private String fOriginalSignature;
57 	private String fOriginalTypeName;
58 	private int fOriginalStackDepth;
59 
60 	/**
61 	 * Whether this is the first step into.
62 	 */
63 	private boolean fFirstStep = true;
64 
65 	/**
66 	 * The state of step filters before the step.
67 	 */
68 	private boolean fStepFilterEnabledState;
69 
70 	/**
71 	 * Expected event kind
72 	 */
73 	private int fExpectedKind = -1;
74 
75 	/**
76 	 * Expected event detail
77 	 */
78 	private int fExpectedDetail = -1;
79 
80 	/**
81 	 * Constructs a step handler to step into the given method in the given thread
82 	 * starting from the given stack frame.
83 	 */
StepIntoSelectionHandler(IJavaThread thread, IJavaStackFrame frame, IMethod method)84 	public StepIntoSelectionHandler(IJavaThread thread, IJavaStackFrame frame, IMethod method) {
85 		fMethod = method;
86 		fThread = thread;
87 		try {
88 			fOriginalName = frame.getName();
89 			fOriginalSignature = frame.getSignature();
90 			fOriginalTypeName = frame.getDeclaringTypeName();
91 			if (method.isBinary()) {
92 				fResolvedSignature = method.getSignature();
93 			} else {
94 				fResolvedSignature = ToggleBreakpointAdapter.resolveMethodSignature(method);
95 			}
96 		} catch (CoreException e) {
97 			JDIDebugUIPlugin.log(e);
98 		}
99 	}
100 
101 	/**
102 	 * Returns the target thread for the step.
103 	 *
104 	 * @return the target thread for the step
105 	 */
getThread()106 	protected IJavaThread getThread() {
107 		return fThread;
108 	}
109 
getDebugTarget()110 	protected IJavaDebugTarget getDebugTarget() {
111 		return (IJavaDebugTarget)getThread().getDebugTarget();
112 	}
113 
114 	/**
115 	 * Returns the method to step into
116 	 *
117 	 * @return the method to step into
118 	 */
getMethod()119 	protected IMethod getMethod() {
120 		return fMethod;
121 	}
122 
123 	/**
124 	 * Returns the resolved signature of the method to step into
125 	 *
126 	 * @return the resolved signature of the method to step into
127 	 */
getSignature()128 	protected String getSignature() {
129 		return fResolvedSignature;
130 	}
131 
132 	/**
133 	 * @see org.eclipse.debug.core.IDebugEventFilter#filterDebugEvents(org.eclipse.debug.core.DebugEvent)
134 	 */
135 	@Override
filterDebugEvents(DebugEvent[] events)136 	public DebugEvent[] filterDebugEvents(DebugEvent[] events) {
137 		// we only expect one event from our thread - find the event
138 		DebugEvent event = null;
139 		int index = -1;
140 		int threadEvents = 0;
141 		for (int i = 0; i < events.length; i++) {
142 			DebugEvent e = events[i];
143 			if (isExpectedEvent(e)) {
144 				event = e;
145 				index = i;
146 				threadEvents++;
147 			} else if (e.getSource() == getThread()) {
148 				threadEvents++;
149 			}
150 		}
151 
152 		if (event == null) {
153 			// nothing to process in this event set
154 			return events;
155 		}
156 
157 		// create filtered event set
158 		DebugEvent[] filtered = new DebugEvent[events.length - 1];
159 		if (filtered.length > 0) {
160 			int j = 0;
161 			for (int i = 0; i < events.length; i++) {
162 				if (i != index) {
163 					filtered[j] = events[i];
164 					j++;
165 				}
166 			}
167 		}
168 
169 		// if more than one event in our thread, abort (filtering our event)
170 		if (threadEvents > 1) {
171 			cleanup();
172 			return filtered;
173 		}
174 
175 		// we have the one expected event - process it
176 		switch (event.getKind()) {
177 			case DebugEvent.RESUME:
178 				// next, we expect a step end
179 				setExpectedEvent(DebugEvent.SUSPEND, DebugEvent.STEP_END);
180 				if (fFirstStep) {
181 					fFirstStep = false;
182 					return events; // include the first resume event
183 				}
184 				// secondary step - filter the event
185 				return filtered;
186 			case DebugEvent.SUSPEND:
187 				// compare location to desired location
188 				try {
189 					final IJavaStackFrame frame = (IJavaStackFrame)getThread().getTopStackFrame();
190 					int stackDepth = frame.getThread().getStackFrames().length;
191 					String name = null;
192 					if (frame.isConstructor()) {
193 						name = frame.getDeclaringTypeName();
194 						index = name.lastIndexOf('.');
195 						if (index >= 0) {
196 							name = name.substring(index + 1);
197 						}
198 					} else {
199 						name = frame.getName();
200 					}
201 					if (name.equals(getMethod().getElementName()) && frame.getSignature().equals(getSignature())) {
202 						// hit
203 						cleanup();
204 						return events;
205 					}
206 					// step again
207 					Runnable r = null;
208 					if (stackDepth > fOriginalStackDepth) {
209                         if (frame.isSynthetic()) {
210                             // step thru synthetic methods
211                             r = new Runnable() {
212                                 @Override
213 								public void run() {
214                                     try {
215                                         setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_INTO);
216                                         frame.stepInto();
217                                     } catch (DebugException e) {
218                                         JDIDebugUIPlugin.log(e);
219                                         cleanup();
220                                         DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
221                                     }
222                                 }
223                             };
224                         } else {
225     						r = new Runnable() {
226     							@Override
227 								public void run() {
228     								try {
229     									setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_RETURN);
230     									frame.stepReturn();
231     								} catch (DebugException e) {
232     									JDIDebugUIPlugin.log(e);
233     									cleanup();
234     									DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
235     								}
236     							}
237     						};
238                         }
239 					} else if (stackDepth == fOriginalStackDepth){
240 						// we should be back in the original stack frame - if not, abort
241 						if (!(frame.getSignature().equals(fOriginalSignature) && frame.getName().equals(fOriginalName) && frame.getDeclaringTypeName().equals(fOriginalTypeName))) {
242 							missed();
243 							return events;
244 						}
245 						r = new Runnable() {
246 							@Override
247 							public void run() {
248 								try {
249 									setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_INTO);
250 									frame.stepInto();
251 								} catch (DebugException e) {
252 									JDIDebugUIPlugin.log(e);
253 									cleanup();
254 									DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
255 								}
256 							}
257 						};
258 					} else {
259 						// we returned from the original frame - never hit the desired method
260 						missed();
261 						return events;
262 					}
263 					DebugPlugin.getDefault().asyncExec(r);
264 					// filter the events
265 						return filtered;
266 				} catch (CoreException e) {
267 					// abort
268 					JDIDebugUIPlugin.log(e);
269 					cleanup();
270 					return events;
271 				}
272 		}
273 		// execution should not reach here
274 		return events;
275 
276 	}
277 
278 	/**
279 	 * Called when stepping returned from the original frame without entering the desired method.
280 	 */
missed()281 	protected void missed() {
282 		cleanup();
283 		Runnable r = new Runnable() {
284 			@Override
285 			public void run() {
286 				String methodName = null;
287 				try {
288 					methodName = Signature.toString(getMethod().getSignature(), getMethod().getElementName(), getMethod().getParameterNames(), false, false);
289 				} catch (JavaModelException e) {
290 					methodName = getMethod().getElementName();
291 				}
292 				new MessageDialog(JDIDebugUIPlugin.getActiveWorkbenchShell(),  ActionMessages.StepIntoSelectionHandler_1, null, NLS.bind(ActionMessages.StepIntoSelectionHandler_Execution_did_not_enter____0____before_the_current_method_returned__1, new String[]{methodName}), MessageDialog.INFORMATION, new String[] {ActionMessages.StepIntoSelectionHandler_2}, 0).open();
293 			}
294 		};
295 		JDIDebugUIPlugin.getStandardDisplay().asyncExec(r);
296 	}
297 
298 	/**
299 	 * Performs the step.
300 	 */
step()301 	public void step() {
302 		// add event filter and turn off step filters
303 		DebugPlugin.getDefault().addDebugEventFilter(this);
304 		fStepFilterEnabledState = getDebugTarget().isStepFiltersEnabled();
305 		getDebugTarget().setStepFiltersEnabled(false);
306 		try {
307 			fOriginalStackDepth = getThread().getStackFrames().length;
308 			setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_INTO);
309 			getThread().stepInto();
310 		} catch (DebugException e) {
311 			JDIDebugUIPlugin.log(e);
312 			cleanup();
313 			DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
314 		}
315 	}
316 
317 	/**
318 	 * Cleans up when the step is complete/aborted.
319 	 */
cleanup()320 	protected void cleanup() {
321 		DebugPlugin.getDefault().removeDebugEventFilter(this);
322 		// restore step filter state
323 		getDebugTarget().setStepFiltersEnabled(fStepFilterEnabledState);
324 	}
325 
326 	/**
327 	 * Sets the expected debug event kind and detail we are waiting for next.
328 	 *
329 	 * @param kind event kind
330 	 * @param detail event detail
331 	 */
setExpectedEvent(int kind, int detail)332 	private void setExpectedEvent(int kind, int detail) {
333 		fExpectedKind = kind;
334 		fExpectedDetail = detail;
335 	}
336 
337 	/**
338 	 * Returns whether the given event is what we expected.
339 	 *
340 	 * @param event fire event
341 	 * @return whether the event is what we expected
342 	 */
isExpectedEvent(DebugEvent event)343 	protected boolean isExpectedEvent(DebugEvent event) {
344 		return event.getSource().equals(getThread()) &&
345 			event.getKind() == fExpectedKind &&
346 			event.getDetail() == fExpectedDetail;
347 	}
348 }
349