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