Add support for specifying the debugging step size
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / DebugProcessEvents.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 package com.intellij.debugger.engine;
17
18 import com.intellij.debugger.DebuggerBundle;
19 import com.intellij.debugger.DebuggerInvocationUtil;
20 import com.intellij.debugger.DebuggerManagerEx;
21 import com.intellij.debugger.engine.events.DebuggerCommandImpl;
22 import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
23 import com.intellij.debugger.engine.requests.LocatableEventRequestor;
24 import com.intellij.debugger.engine.requests.MethodReturnValueWatcher;
25 import com.intellij.debugger.impl.DebuggerSession;
26 import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
27 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
28 import com.intellij.debugger.requests.Requestor;
29 import com.intellij.debugger.settings.DebuggerSettings;
30 import com.intellij.debugger.ui.DebuggerPanelsManager;
31 import com.intellij.debugger.ui.breakpoints.Breakpoint;
32 import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
33 import com.intellij.execution.configurations.RemoteConnection;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.application.ModalityState;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.progress.ProcessCanceledException;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.ui.MessageType;
40 import com.intellij.openapi.ui.Messages;
41 import com.intellij.openapi.util.Pair;
42 import com.intellij.xdebugger.XDebugSession;
43 import com.intellij.xdebugger.breakpoints.XBreakpoint;
44 import com.intellij.xdebugger.impl.XDebugSessionImpl;
45 import com.sun.jdi.InternalException;
46 import com.sun.jdi.ThreadReference;
47 import com.sun.jdi.VMDisconnectedException;
48 import com.sun.jdi.VirtualMachine;
49 import com.sun.jdi.event.*;
50 import com.sun.jdi.request.EventRequest;
51 import com.sun.jdi.request.EventRequestManager;
52 import com.sun.jdi.request.ThreadDeathRequest;
53 import com.sun.jdi.request.ThreadStartRequest;
54
55 /**
56  * @author lex
57  */
58 public class DebugProcessEvents extends DebugProcessImpl {
59   private static final Logger LOG = Logger.getInstance(DebugProcessEvents.class);
60
61   private DebuggerEventThread myEventThread;
62
63   public DebugProcessEvents(Project project) {
64     super(project);
65   }
66
67   @Override
68   protected void commitVM(final VirtualMachine vm) {
69     super.commitVM(vm);
70     if(vm != null) {
71       vmAttached();
72       myEventThread = new DebuggerEventThread();
73       ApplicationManager.getApplication().executeOnPooledThread(myEventThread);
74     }
75   }
76
77   private static void showStatusText(DebugProcessEvents debugProcess,  Event event) {
78     Requestor requestor = debugProcess.getRequestsManager().findRequestor(event.request());
79     Breakpoint breakpoint = null;
80     if(requestor instanceof Breakpoint) {
81       breakpoint = (Breakpoint)requestor;
82     }
83     String text = debugProcess.getEventText(Pair.create(breakpoint, event));
84     debugProcess.showStatusText(text);
85   }
86
87   public String getEventText(Pair<Breakpoint, Event> descriptor) {
88     String text = "";
89     final Event event = descriptor.getSecond();
90     final Breakpoint breakpoint = descriptor.getFirst();
91     if (event instanceof LocatableEvent) {
92       if (breakpoint instanceof LineBreakpoint && !((LineBreakpoint)breakpoint).isVisible()) {
93         text = DebuggerBundle.message("status.stopped.at.cursor");
94       }
95       else {
96         try {
97           text = breakpoint != null? breakpoint.getEventMessage(((LocatableEvent)event)) : DebuggerBundle.message("status.generic.breakpoint.reached");
98         }
99         catch (InternalException e) {
100           text = DebuggerBundle.message("status.generic.breakpoint.reached");
101         }
102       }
103     }
104     else if (event instanceof VMStartEvent) {
105       text = DebuggerBundle.message("status.process.started");
106     }
107     else if (event instanceof VMDeathEvent) {
108       text = DebuggerBundle.message("status.process.terminated");
109     }
110     else if (event instanceof VMDisconnectEvent) {
111       final RemoteConnection connection = getConnection();
112       final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection);
113       final String transportName = DebuggerBundle.getTransportName(connection);
114       text = DebuggerBundle.message("status.disconnected", addressDisplayName, transportName);
115     }
116     return text;
117   }
118
119   private class DebuggerEventThread implements Runnable {
120     private final VirtualMachineProxyImpl myVmProxy;
121
122     DebuggerEventThread () {
123       myVmProxy = getVirtualMachineProxy();
124     }
125
126     private boolean myIsStopped = false;
127
128     public synchronized void stopListening() {
129       myIsStopped = true;
130     }
131
132     private synchronized boolean isStopped() {
133       return myIsStopped;
134     }
135
136     @Override
137     public void run() {
138       try {
139         EventQueue eventQueue = myVmProxy.eventQueue();
140         while (!isStopped()) {
141           try {
142             final EventSet eventSet = eventQueue.remove();
143
144             final boolean methodWatcherActive = myReturnValueWatcher != null && myReturnValueWatcher.isEnabled();
145             int processed = 0;
146             for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator.hasNext();) {
147               final Event event = eventIterator.nextEvent();
148
149               if (methodWatcherActive) {
150                 if (event instanceof MethodExitEvent) {
151                   if (myReturnValueWatcher.processMethodExitEvent((MethodExitEvent)event)) {
152                     processed++;
153                   }
154                   continue;
155                 }
156               }
157               if (event instanceof ThreadStartEvent) {
158                 processed++;
159                 final ThreadReference thread = ((ThreadStartEvent)event).thread();
160                 getManagerThread().schedule(new DebuggerCommandImpl() {
161                   @Override
162                   protected void action() throws Exception {
163                     getVirtualMachineProxy().threadStarted(thread);
164                     myDebugProcessDispatcher.getMulticaster().threadStarted(DebugProcessEvents.this, thread);
165                   }
166                 });
167               }
168               else if (event instanceof ThreadDeathEvent) {
169                 processed++;
170                 final ThreadReference thread = ((ThreadDeathEvent)event).thread();
171                 getManagerThread().schedule(new DebuggerCommandImpl() {
172                   @Override
173                   protected void action() throws Exception {
174                     getVirtualMachineProxy().threadStopped(thread);
175                     myDebugProcessDispatcher.getMulticaster().threadStopped(DebugProcessEvents.this, thread);
176                   }
177                 });
178               }
179             }
180
181             if (processed == eventSet.size()) {
182               eventSet.resume();
183               continue;
184             }
185
186             getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
187               @Override
188               protected void action() throws Exception {
189                 if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL && !DebuggerSession.enableBreakpointsDuringEvaluation()) {
190                   // check if there is already one request with policy SUSPEND_ALL
191                   for (SuspendContextImpl context : getSuspendManager().getEventContexts()) {
192                     if (context.getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
193                       eventSet.resume();
194                       return;
195                     }
196                   }
197                 }
198
199                 final SuspendContextImpl suspendContext = getSuspendManager().pushSuspendContext(eventSet);
200                 for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator.hasNext();) {
201                   final Event event = eventIterator.nextEvent();
202                   //if (LOG.isDebugEnabled()) {
203                   //  LOG.debug("EVENT : " + event);
204                   //}
205                   try {
206                     if (event instanceof VMStartEvent) {
207                       //Sun WTK fails when J2ME when event set is resumed on VMStartEvent
208                       processVMStartEvent(suspendContext, (VMStartEvent)event);
209                     }
210                     else if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {
211                       processVMDeathEvent(suspendContext, event);
212                     }
213                     else if (event instanceof ClassPrepareEvent) {
214                       processClassPrepareEvent(suspendContext, (ClassPrepareEvent)event);
215                     }
216                     //AccessWatchpointEvent, BreakpointEvent, ExceptionEvent, MethodEntryEvent, MethodExitEvent,
217                     //ModificationWatchpointEvent, StepEvent, WatchpointEvent
218                     else if (event instanceof StepEvent) {
219                       processStepEvent(suspendContext, (StepEvent)event);
220                     }
221                     else if (event instanceof LocatableEvent) {
222                       processLocatableEvent(suspendContext, (LocatableEvent)event);
223                     }
224                     else if (event instanceof ClassUnloadEvent) {
225                       processDefaultEvent(suspendContext);
226                     }
227                   }
228                   catch (VMDisconnectedException e) {
229                     LOG.debug(e);
230                   }
231                   catch (InternalException e) {
232                     LOG.info(e);
233                   }
234                   catch (Throwable e) {
235                     LOG.error(e);
236                   }
237                 }
238               }
239             });
240           }
241           catch (InternalException e) {
242             LOG.debug(e);
243           }
244           catch (InterruptedException e) {
245             throw e;
246           }
247           catch (VMDisconnectedException e) {
248             throw e;
249           }
250           catch (ProcessCanceledException e) {
251             throw e;
252           }
253           catch (Throwable e) {
254             LOG.debug(e);
255           }
256         }
257       }
258       catch (InterruptedException e) {
259         invokeVMDeathEvent();
260       }
261       catch (VMDisconnectedException e) {
262         invokeVMDeathEvent();
263       }
264       finally {
265         Thread.interrupted(); // reset interrupted status
266       }
267     }
268
269     private void invokeVMDeathEvent() {
270       getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
271         @Override
272         protected void action() throws Exception {
273           SuspendContextImpl suspendContext = getSuspendManager().pushSuspendContext(EventRequest.SUSPEND_NONE, 1);
274           processVMDeathEvent(suspendContext, null);
275         }
276       });
277     }
278   }
279
280   private static void preprocessEvent(SuspendContextImpl suspendContext, ThreadReference thread) {
281     ThreadReferenceProxyImpl oldThread = suspendContext.getThread();
282     suspendContext.setThread(thread);
283
284     if(oldThread == null) {
285       //this is the first event in the eventSet that we process
286       suspendContext.getDebugProcess().beforeSuspend(suspendContext);
287     }
288   }
289
290   private void processVMStartEvent(final SuspendContextImpl suspendContext, VMStartEvent event) {
291     preprocessEvent(suspendContext, event.thread());
292
293     if (LOG.isDebugEnabled()) {
294       LOG.debug("enter: processVMStartEvent()");
295     }
296
297     showStatusText(this, event);
298
299     getSuspendManager().voteResume(suspendContext);
300   }
301
302   private void vmAttached() {
303     DebuggerManagerThreadImpl.assertIsManagerThread();
304     LOG.assertTrue(!isAttached());
305     if(myState.compareAndSet(STATE_INITIAL, STATE_ATTACHED)) {
306       final VirtualMachineProxyImpl machineProxy = getVirtualMachineProxy();
307       final EventRequestManager requestManager = machineProxy.eventRequestManager();
308
309       if (machineProxy.canGetMethodReturnValues()) {
310         myReturnValueWatcher = new MethodReturnValueWatcher(requestManager);
311       }
312
313       final ThreadStartRequest threadStartRequest = requestManager.createThreadStartRequest();
314       threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
315       threadStartRequest.enable();
316       final ThreadDeathRequest threadDeathRequest = requestManager.createThreadDeathRequest();
317       threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
318       threadDeathRequest.enable();
319
320       myDebugProcessDispatcher.getMulticaster().processAttached(this);
321
322       // breakpoints should be initialized after all processAttached listeners work
323       ApplicationManager.getApplication().runReadAction(new Runnable() {
324         @Override
325         public void run() {
326           XDebugSession session = getSession().getXDebugSession();
327           if (session != null) {
328             session.initBreakpoints();
329           }
330         }
331       });
332
333       final String addressDisplayName = DebuggerBundle.getAddressDisplayName(getConnection());
334       final String transportName = DebuggerBundle.getTransportName(getConnection());
335       showStatusText(DebuggerBundle.message("status.connected", addressDisplayName, transportName));
336       if (LOG.isDebugEnabled()) {
337         LOG.debug("leave: processVMStartEvent()");
338       }
339     }
340   }
341
342   private void processVMDeathEvent(SuspendContextImpl suspendContext, Event event) {
343     try {
344       preprocessEvent(suspendContext, null);
345       cancelRunToCursorBreakpoint();
346     }
347     finally {
348       if (myEventThread != null) {
349         myEventThread.stopListening();
350         myEventThread = null;
351       }
352       closeProcess(false);
353     }
354
355     if(event != null) {
356       showStatusText(this, event);
357     }
358   }
359
360   private void processClassPrepareEvent(SuspendContextImpl suspendContext, ClassPrepareEvent event) {
361     preprocessEvent(suspendContext, event.thread());
362     if (LOG.isDebugEnabled()) {
363       LOG.debug("Class prepared: " + event.referenceType().name());
364     }
365     suspendContext.getDebugProcess().getRequestsManager().processClassPrepared(event);
366
367     getSuspendManager().voteResume(suspendContext);
368   }
369
370   private void processStepEvent(SuspendContextImpl suspendContext, StepEvent event) {
371     final ThreadReference thread = event.thread();
372     //LOG.assertTrue(thread.isSuspended());
373     preprocessEvent(suspendContext, thread);
374
375     //noinspection HardCodedStringLiteral
376     RequestHint hint = (RequestHint)event.request().getProperty("hint");
377
378     deleteStepRequests(event.thread());
379
380     boolean shouldResume = false;
381
382     final Project project = getProject();
383     if (hint != null) {
384       final int nextStepDepth = hint.getNextStepDepth(suspendContext);
385       if (nextStepDepth != RequestHint.STOP) {
386         final ThreadReferenceProxyImpl threadProxy = suspendContext.getThread();
387         doStep(suspendContext, threadProxy, hint.getSize(), nextStepDepth, hint);
388         shouldResume = true;
389       }
390
391       if(!shouldResume && hint.isRestoreBreakpoints()) {
392         DebuggerManagerEx.getInstanceEx(project).getBreakpointManager().enableBreakpoints(this);
393       }
394     }
395
396     if(shouldResume) {
397       getSuspendManager().voteResume(suspendContext);
398     }
399     else {
400       showStatusText("");
401       if (myReturnValueWatcher != null) {
402         myReturnValueWatcher.disable();
403       }
404       getSuspendManager().voteSuspend(suspendContext);
405       if (hint != null) {
406         final MethodFilter methodFilter = hint.getMethodFilter();
407         if (methodFilter instanceof NamedMethodFilter && !hint.wasStepTargetMethodMatched()) {
408           final String message = "Method <b>" + ((NamedMethodFilter)methodFilter).getMethodName() + "()</b> has not been called";
409           XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.INFO).notify(project);
410         }
411       }
412     }
413   }
414
415   private void processLocatableEvent(final SuspendContextImpl suspendContext, final LocatableEvent event) {
416     if (myReturnValueWatcher != null && event instanceof MethodExitEvent) {
417       if (myReturnValueWatcher.processMethodExitEvent(((MethodExitEvent)event))) {
418         return;
419       }
420     }
421
422     ThreadReference thread = event.thread();
423     //LOG.assertTrue(thread.isSuspended());
424     preprocessEvent(suspendContext, thread);
425
426     //we use schedule to allow processing other events during processing this one
427     //this is especially necessary if a method is breakpoint condition
428     getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) {
429       @Override
430       public void contextAction() throws Exception {
431         final SuspendManager suspendManager = getSuspendManager();
432         SuspendContextImpl evaluatingContext = SuspendManagerUtil.getEvaluatingContext(suspendManager, getSuspendContext().getThread());
433
434         if (evaluatingContext != null && !DebuggerSession.enableBreakpointsDuringEvaluation()) {
435           // is inside evaluation, so ignore any breakpoints
436           suspendManager.voteResume(suspendContext);
437           return;
438         }
439
440         final LocatableEventRequestor requestor = (LocatableEventRequestor) getRequestsManager().findRequestor(event.request());
441
442         boolean resumePreferred = requestor != null && DebuggerSettings.SUSPEND_NONE.equals(requestor.getSuspendPolicy());
443         boolean requestHit;
444         try {
445           requestHit = (requestor != null) && requestor.processLocatableEvent(this, event);
446         }
447         catch (final LocatableEventRequestor.EventProcessingException ex) {
448           if (LOG.isDebugEnabled()) {
449             LOG.debug(ex.getMessage());
450           }
451           final boolean[] considerRequestHit = new boolean[]{true};
452           DebuggerInvocationUtil.invokeAndWait(getProject(), new Runnable() {
453             @Override
454             public void run() {
455               DebuggerPanelsManager.getInstance(getProject()).toFront(mySession);
456               final String displayName = requestor instanceof Breakpoint? ((Breakpoint)requestor).getDisplayName() : requestor.getClass().getSimpleName();
457               final String message = DebuggerBundle.message("error.evaluating.breakpoint.condition.or.action", displayName, ex.getMessage());
458               considerRequestHit[0] = Messages.showYesNoDialog(getProject(), message, ex.getTitle(), Messages.getQuestionIcon()) == Messages.YES;
459             }
460           }, ModalityState.NON_MODAL);
461           requestHit = considerRequestHit[0];
462           resumePreferred = !requestHit;
463         }
464
465         if (requestHit && requestor instanceof Breakpoint) {
466           // if requestor is a breakpoint and this breakpoint was hit, no matter its suspend policy
467           ApplicationManager.getApplication().runReadAction(new Runnable() {
468             @Override
469             public void run() {
470               XDebugSession session = getSession().getXDebugSession();
471               if (session != null) {
472                 XBreakpoint breakpoint = ((Breakpoint)requestor).getXBreakpoint();
473                 if (breakpoint != null) {
474                   ((XDebugSessionImpl)session).processDependencies(breakpoint);
475                 }
476               }
477             }
478           });
479         }
480
481         if(!requestHit || resumePreferred) {
482           suspendManager.voteResume(suspendContext);
483         }
484         else {
485           if (myReturnValueWatcher != null) {
486             myReturnValueWatcher.disable();
487           }
488           //if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
489           //  // there could be explicit resume as a result of call to voteSuspend()
490           //  // e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
491           //  // resuming and all breakpoints in other threads will be ignored.
492           //  // As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
493           //  myBreakpointManager.applyThreadFilter(DebugProcessEvents.this, event.thread());
494           //}
495           suspendManager.voteSuspend(suspendContext);
496           showStatusText(DebugProcessEvents.this, event);
497         }
498       }
499     });
500   }
501
502   private void processDefaultEvent(SuspendContextImpl suspendContext) {
503     preprocessEvent(suspendContext, null);
504     getSuspendManager().voteResume(suspendContext);
505   }
506 }