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