2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.debugger.engine;
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;
57 public class DebugProcessEvents extends DebugProcessImpl {
58 private static final Logger LOG = Logger.getInstance(DebugProcessEvents.class);
60 private DebuggerEventThread myEventThread;
62 public DebugProcessEvents(Project project) {
67 protected void commitVM(final VirtualMachine vm) {
71 myEventThread = new DebuggerEventThread();
72 ApplicationManager.getApplication().executeOnPooledThread(myEventThread);
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;
82 String text = debugProcess.getEventText(Pair.create(breakpoint, event));
83 debugProcess.showStatusText(text);
86 public String getEventText(Pair<Breakpoint, Event> descriptor) {
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");
96 text = breakpoint != null? breakpoint.getEventMessage(((LocatableEvent)event)) : DebuggerBundle.message("status.generic.breakpoint.reached");
98 catch (InternalException e) {
99 text = DebuggerBundle.message("status.generic.breakpoint.reached");
103 else if (event instanceof VMStartEvent) {
104 text = DebuggerBundle.message("status.process.started");
106 else if (event instanceof VMDeathEvent) {
107 text = DebuggerBundle.message("status.process.terminated");
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);
118 private class DebuggerEventThread implements Runnable {
119 private final VirtualMachineProxyImpl myVmProxy;
121 DebuggerEventThread () {
122 myVmProxy = getVirtualMachineProxy();
125 private boolean myIsStopped = false;
127 public synchronized void stopListening() {
131 private synchronized boolean isStopped() {
138 EventQueue eventQueue = myVmProxy.eventQueue();
139 while (!isStopped()) {
141 final EventSet eventSet = eventQueue.remove();
143 final boolean methodWatcherActive = myReturnValueWatcher != null && myReturnValueWatcher.isEnabled();
145 for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator.hasNext();) {
146 final Event event = eventIterator.nextEvent();
148 if (methodWatcherActive) {
149 if (event instanceof MethodExitEvent) {
150 if (myReturnValueWatcher.processMethodExitEvent((MethodExitEvent)event)) {
156 if (event instanceof ThreadStartEvent) {
158 final ThreadReference thread = ((ThreadStartEvent)event).thread();
159 getManagerThread().schedule(new DebuggerCommandImpl() {
161 protected void action() throws Exception {
162 getVirtualMachineProxy().threadStarted(thread);
163 myDebugProcessDispatcher.getMulticaster().threadStarted(DebugProcessEvents.this, thread);
167 else if (event instanceof ThreadDeathEvent) {
169 final ThreadReference thread = ((ThreadDeathEvent)event).thread();
170 getManagerThread().schedule(new DebuggerCommandImpl() {
172 protected void action() throws Exception {
173 getVirtualMachineProxy().threadStopped(thread);
174 myDebugProcessDispatcher.getMulticaster().threadStopped(DebugProcessEvents.this, thread);
180 if (processed == eventSet.size()) {
185 getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
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) {
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);
205 if (event instanceof VMStartEvent) {
206 //Sun WTK fails when J2ME when event set is resumed on VMStartEvent
207 processVMStartEvent(suspendContext, (VMStartEvent)event);
209 else if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {
210 processVMDeathEvent(suspendContext, event);
212 else if (event instanceof ClassPrepareEvent) {
213 processClassPrepareEvent(suspendContext, (ClassPrepareEvent)event);
215 //AccessWatchpointEvent, BreakpointEvent, ExceptionEvent, MethodEntryEvent, MethodExitEvent,
216 //ModificationWatchpointEvent, StepEvent, WatchpointEvent
217 else if (event instanceof StepEvent) {
218 processStepEvent(suspendContext, (StepEvent)event);
220 else if (event instanceof LocatableEvent) {
221 processLocatableEvent(suspendContext, (LocatableEvent)event);
223 else if (event instanceof ClassUnloadEvent) {
224 processDefaultEvent(suspendContext);
227 catch (VMDisconnectedException e) {
230 catch (InternalException e) {
233 catch (Throwable e) {
240 catch (InternalException e) {
243 catch (InterruptedException e) {
246 catch (VMDisconnectedException e) {
249 catch (ProcessCanceledException e) {
252 catch (Throwable e) {
257 catch (InterruptedException e) {
258 invokeVMDeathEvent();
260 catch (VMDisconnectedException e) {
261 invokeVMDeathEvent();
264 Thread.interrupted(); // reset interrupted status
268 private void invokeVMDeathEvent() {
269 getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
271 protected void action() throws Exception {
272 SuspendContextImpl suspendContext = getSuspendManager().pushSuspendContext(EventRequest.SUSPEND_NONE, 1);
273 processVMDeathEvent(suspendContext, null);
279 private static void preprocessEvent(SuspendContextImpl suspendContext, ThreadReference thread) {
280 ThreadReferenceProxyImpl oldThread = suspendContext.getThread();
281 suspendContext.setThread(thread);
283 if(oldThread == null) {
284 //this is the first event in the eventSet that we process
285 suspendContext.getDebugProcess().beforeSuspend(suspendContext);
289 private void processVMStartEvent(final SuspendContextImpl suspendContext, VMStartEvent event) {
290 preprocessEvent(suspendContext, event.thread());
292 if (LOG.isDebugEnabled()) {
293 LOG.debug("enter: processVMStartEvent()");
296 showStatusText(this, event);
298 getSuspendManager().voteResume(suspendContext);
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();
308 if (machineProxy.canGetMethodReturnValues()) {
309 myReturnValueWatcher = new MethodReturnValueWatcher(requestManager);
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();
319 myDebugProcessDispatcher.getMulticaster().processAttached(this);
321 // breakpoints should be initialized after all processAttached listeners work
322 ApplicationManager.getApplication().runReadAction(new Runnable() {
325 XDebugSession session = getSession().getXDebugSession();
326 if (session != null) {
327 session.initBreakpoints();
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()");
341 private void processVMDeathEvent(SuspendContextImpl suspendContext, Event event) {
343 preprocessEvent(suspendContext, null);
344 cancelRunToCursorBreakpoint();
347 if (myEventThread != null) {
348 myEventThread.stopListening();
349 myEventThread = null;
355 showStatusText(this, event);
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());
364 suspendContext.getDebugProcess().getRequestsManager().processClassPrepared(event);
366 getSuspendManager().voteResume(suspendContext);
369 private void processStepEvent(SuspendContextImpl suspendContext, StepEvent event) {
370 final ThreadReference thread = event.thread();
371 //LOG.assertTrue(thread.isSuspended());
372 preprocessEvent(suspendContext, thread);
374 //noinspection HardCodedStringLiteral
375 RequestHint hint = (RequestHint)event.request().getProperty("hint");
377 deleteStepRequests(event.thread());
379 boolean shouldResume = false;
381 final Project project = getProject();
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);
390 if(!shouldResume && hint.isRestoreBreakpoints()) {
391 DebuggerManagerEx.getInstanceEx(project).getBreakpointManager().enableBreakpoints(this);
396 getSuspendManager().voteResume(suspendContext);
400 if (myReturnValueWatcher != null) {
401 myReturnValueWatcher.disable();
403 getSuspendManager().voteSuspend(suspendContext);
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);
414 private void processLocatableEvent(final SuspendContextImpl suspendContext, final LocatableEvent event) {
415 if (myReturnValueWatcher != null && event instanceof MethodExitEvent) {
416 if (myReturnValueWatcher.processMethodExitEvent(((MethodExitEvent)event))) {
421 ThreadReference thread = event.thread();
422 //LOG.assertTrue(thread.isSuspended());
423 preprocessEvent(suspendContext, thread);
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) {
429 public void contextAction() throws Exception {
430 final SuspendManager suspendManager = getSuspendManager();
431 SuspendContextImpl evaluatingContext = SuspendManagerUtil.getEvaluatingContext(suspendManager, getSuspendContext().getThread());
433 if (evaluatingContext != null && !DebuggerSession.enableBreakpointsDuringEvaluation()) {
434 // is inside evaluation, so ignore any breakpoints
435 suspendManager.voteResume(suspendContext);
439 final LocatableEventRequestor requestor = (LocatableEventRequestor) getRequestsManager().findRequestor(event.request());
441 boolean resumePreferred = requestor != null && DebuggerSettings.SUSPEND_NONE.equals(requestor.getSuspendPolicy());
444 requestHit = (requestor != null) && requestor.processLocatableEvent(this, event);
446 catch (final LocatableEventRequestor.EventProcessingException ex) {
447 if (LOG.isDebugEnabled()) {
448 LOG.debug(ex.getMessage());
450 final boolean[] considerRequestHit = new boolean[]{true};
451 DebuggerInvocationUtil.invokeAndWait(getProject(), new Runnable() {
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;
458 }, ModalityState.NON_MODAL);
459 requestHit = considerRequestHit[0];
460 resumePreferred = !requestHit;
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() {
468 XDebugSession session = getSession().getXDebugSession();
469 if (session != null) {
470 XBreakpoint breakpoint = ((Breakpoint)requestor).getXBreakpoint();
471 if (breakpoint != null) {
472 ((XDebugSessionImpl)session).processDependencies(breakpoint);
479 if(!requestHit || resumePreferred) {
480 suspendManager.voteResume(suspendContext);
483 if (myReturnValueWatcher != null) {
484 myReturnValueWatcher.disable();
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());
493 suspendManager.voteSuspend(suspendContext);
494 showStatusText(DebugProcessEvents.this, event);
500 private void processDefaultEvent(SuspendContextImpl suspendContext) {
501 preprocessEvent(suspendContext, null);
502 getSuspendManager().voteResume(suspendContext);