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