blob: 7640a959597e7f407adfe76d9c238e23715032b9 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.debugger.engine;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerInvocationUtil;
import com.intellij.debugger.DebuggerManagerEx;
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.engine.requests.LocatableEventRequestor;
import com.intellij.debugger.engine.requests.MethodReturnValueWatcher;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.requests.Requestor;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.debugger.ui.DebuggerPanelsManager;
import com.intellij.debugger.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
import com.intellij.execution.configurations.RemoteConnection;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
import com.sun.jdi.InternalException;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.*;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ThreadDeathRequest;
import com.sun.jdi.request.ThreadStartRequest;
/**
* @author lex
*/
public class DebugProcessEvents extends DebugProcessImpl {
private static final Logger LOG = Logger.getInstance(DebugProcessEvents.class);
private DebuggerEventThread myEventThread;
public DebugProcessEvents(Project project) {
super(project);
}
@Override
protected void commitVM(final VirtualMachine vm) {
super.commitVM(vm);
if(vm != null) {
vmAttached();
myEventThread = new DebuggerEventThread();
ApplicationManager.getApplication().executeOnPooledThread(myEventThread);
}
}
private static void showStatusText(DebugProcessEvents debugProcess, Event event) {
Requestor requestor = debugProcess.getRequestsManager().findRequestor(event.request());
Breakpoint breakpoint = null;
if(requestor instanceof Breakpoint) {
breakpoint = (Breakpoint)requestor;
}
String text = debugProcess.getEventText(Pair.create(breakpoint, event));
debugProcess.showStatusText(text);
}
public String getEventText(Pair<Breakpoint, Event> descriptor) {
String text = "";
final Event event = descriptor.getSecond();
final Breakpoint breakpoint = descriptor.getFirst();
if (event instanceof LocatableEvent) {
if (breakpoint instanceof LineBreakpoint && !((LineBreakpoint)breakpoint).isVisible()) {
text = DebuggerBundle.message("status.stopped.at.cursor");
}
else {
try {
text = breakpoint != null? breakpoint.getEventMessage(((LocatableEvent)event)) : DebuggerBundle.message("status.generic.breakpoint.reached");
}
catch (InternalException e) {
text = DebuggerBundle.message("status.generic.breakpoint.reached");
}
}
}
else if (event instanceof VMStartEvent) {
text = DebuggerBundle.message("status.process.started");
}
else if (event instanceof VMDeathEvent) {
text = DebuggerBundle.message("status.process.terminated");
}
else if (event instanceof VMDisconnectEvent) {
final RemoteConnection connection = getConnection();
final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection);
final String transportName = DebuggerBundle.getTransportName(connection);
text = DebuggerBundle.message("status.disconnected", addressDisplayName, transportName);
}
return text;
}
private class DebuggerEventThread implements Runnable {
private final VirtualMachineProxyImpl myVmProxy;
DebuggerEventThread () {
myVmProxy = getVirtualMachineProxy();
}
private boolean myIsStopped = false;
public synchronized void stopListening() {
myIsStopped = true;
}
private synchronized boolean isStopped() {
return myIsStopped;
}
@Override
public void run() {
try {
EventQueue eventQueue = myVmProxy.eventQueue();
while (!isStopped()) {
try {
final EventSet eventSet = eventQueue.remove();
final boolean methodWatcherActive = myReturnValueWatcher != null && myReturnValueWatcher.isEnabled();
int processed = 0;
for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator.hasNext();) {
final Event event = eventIterator.nextEvent();
if (methodWatcherActive) {
if (event instanceof MethodExitEvent) {
if (myReturnValueWatcher.processMethodExitEvent((MethodExitEvent)event)) {
processed++;
}
continue;
}
}
if (event instanceof ThreadStartEvent) {
processed++;
final ThreadReference thread = ((ThreadStartEvent)event).thread();
getManagerThread().schedule(new DebuggerCommandImpl() {
@Override
protected void action() throws Exception {
getVirtualMachineProxy().threadStarted(thread);
myDebugProcessDispatcher.getMulticaster().threadStarted(DebugProcessEvents.this, thread);
}
});
}
else if (event instanceof ThreadDeathEvent) {
processed++;
final ThreadReference thread = ((ThreadDeathEvent)event).thread();
getManagerThread().schedule(new DebuggerCommandImpl() {
@Override
protected void action() throws Exception {
getVirtualMachineProxy().threadStopped(thread);
myDebugProcessDispatcher.getMulticaster().threadStopped(DebugProcessEvents.this, thread);
}
});
}
}
if (processed == eventSet.size()) {
eventSet.resume();
continue;
}
getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
@Override
protected void action() throws Exception {
if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL && !DebuggerSession.enableBreakpointsDuringEvaluation()) {
// check if there is already one request with policy SUSPEND_ALL
for (SuspendContextImpl context : getSuspendManager().getEventContexts()) {
if (context.getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
eventSet.resume();
return;
}
}
}
final SuspendContextImpl suspendContext = getSuspendManager().pushSuspendContext(eventSet);
for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator.hasNext();) {
final Event event = eventIterator.nextEvent();
//if (LOG.isDebugEnabled()) {
// LOG.debug("EVENT : " + event);
//}
try {
if (event instanceof VMStartEvent) {
//Sun WTK fails when J2ME when event set is resumed on VMStartEvent
processVMStartEvent(suspendContext, (VMStartEvent)event);
}
else if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {
processVMDeathEvent(suspendContext, event);
}
else if (event instanceof ClassPrepareEvent) {
processClassPrepareEvent(suspendContext, (ClassPrepareEvent)event);
}
//AccessWatchpointEvent, BreakpointEvent, ExceptionEvent, MethodEntryEvent, MethodExitEvent,
//ModificationWatchpointEvent, StepEvent, WatchpointEvent
else if (event instanceof StepEvent) {
processStepEvent(suspendContext, (StepEvent)event);
}
else if (event instanceof LocatableEvent) {
processLocatableEvent(suspendContext, (LocatableEvent)event);
}
else if (event instanceof ClassUnloadEvent) {
processDefaultEvent(suspendContext);
}
}
catch (VMDisconnectedException e) {
LOG.debug(e);
}
catch (InternalException e) {
LOG.info(e);
}
catch (Throwable e) {
LOG.error(e);
}
}
}
});
}
catch (InternalException e) {
LOG.debug(e);
}
catch (InterruptedException e) {
throw e;
}
catch (VMDisconnectedException e) {
throw e;
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable e) {
LOG.debug(e);
}
}
}
catch (InterruptedException e) {
invokeVMDeathEvent();
}
catch (VMDisconnectedException e) {
invokeVMDeathEvent();
}
finally {
Thread.interrupted(); // reset interrupted status
}
}
private void invokeVMDeathEvent() {
getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
@Override
protected void action() throws Exception {
SuspendContextImpl suspendContext = getSuspendManager().pushSuspendContext(EventRequest.SUSPEND_NONE, 1);
processVMDeathEvent(suspendContext, null);
}
});
}
}
private static void preprocessEvent(SuspendContextImpl suspendContext, ThreadReference thread) {
ThreadReferenceProxyImpl oldThread = suspendContext.getThread();
suspendContext.setThread(thread);
if(oldThread == null) {
//this is the first event in the eventSet that we process
suspendContext.getDebugProcess().beforeSuspend(suspendContext);
}
}
private void processVMStartEvent(final SuspendContextImpl suspendContext, VMStartEvent event) {
preprocessEvent(suspendContext, event.thread());
if (LOG.isDebugEnabled()) {
LOG.debug("enter: processVMStartEvent()");
}
showStatusText(this, event);
getSuspendManager().voteResume(suspendContext);
}
private void vmAttached() {
DebuggerManagerThreadImpl.assertIsManagerThread();
LOG.assertTrue(!isAttached());
if(myState.compareAndSet(STATE_INITIAL, STATE_ATTACHED)) {
final VirtualMachineProxyImpl machineProxy = getVirtualMachineProxy();
final EventRequestManager requestManager = machineProxy.eventRequestManager();
if (machineProxy.canGetMethodReturnValues()) {
myReturnValueWatcher = new MethodReturnValueWatcher(requestManager);
}
final ThreadStartRequest threadStartRequest = requestManager.createThreadStartRequest();
threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
threadStartRequest.enable();
final ThreadDeathRequest threadDeathRequest = requestManager.createThreadDeathRequest();
threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
threadDeathRequest.enable();
myDebugProcessDispatcher.getMulticaster().processAttached(this);
// breakpoints should be initialized after all processAttached listeners work
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
XDebugSession session = getSession().getXDebugSession();
if (session != null) {
session.initBreakpoints();
}
}
});
final String addressDisplayName = DebuggerBundle.getAddressDisplayName(getConnection());
final String transportName = DebuggerBundle.getTransportName(getConnection());
showStatusText(DebuggerBundle.message("status.connected", addressDisplayName, transportName));
if (LOG.isDebugEnabled()) {
LOG.debug("leave: processVMStartEvent()");
}
}
}
private void processVMDeathEvent(SuspendContextImpl suspendContext, Event event) {
try {
preprocessEvent(suspendContext, null);
cancelRunToCursorBreakpoint();
}
finally {
if (myEventThread != null) {
myEventThread.stopListening();
myEventThread = null;
}
closeProcess(false);
}
if(event != null) {
showStatusText(this, event);
}
}
private void processClassPrepareEvent(SuspendContextImpl suspendContext, ClassPrepareEvent event) {
preprocessEvent(suspendContext, event.thread());
if (LOG.isDebugEnabled()) {
LOG.debug("Class prepared: " + event.referenceType().name());
}
suspendContext.getDebugProcess().getRequestsManager().processClassPrepared(event);
getSuspendManager().voteResume(suspendContext);
}
private void processStepEvent(SuspendContextImpl suspendContext, StepEvent event) {
final ThreadReference thread = event.thread();
//LOG.assertTrue(thread.isSuspended());
preprocessEvent(suspendContext, thread);
//noinspection HardCodedStringLiteral
RequestHint hint = (RequestHint)event.request().getProperty("hint");
deleteStepRequests(event.thread());
boolean shouldResume = false;
final Project project = getProject();
if (hint != null) {
final int nextStepDepth = hint.getNextStepDepth(suspendContext);
if (nextStepDepth != RequestHint.STOP) {
final ThreadReferenceProxyImpl threadProxy = suspendContext.getThread();
doStep(suspendContext, threadProxy, nextStepDepth, hint);
shouldResume = true;
}
if(!shouldResume && hint.isRestoreBreakpoints()) {
DebuggerManagerEx.getInstanceEx(project).getBreakpointManager().enableBreakpoints(this);
}
}
if(shouldResume) {
getSuspendManager().voteResume(suspendContext);
}
else {
showStatusText("");
if (myReturnValueWatcher != null) {
myReturnValueWatcher.disable();
}
getSuspendManager().voteSuspend(suspendContext);
if (hint != null) {
final MethodFilter methodFilter = hint.getMethodFilter();
if (methodFilter instanceof NamedMethodFilter && !hint.wasStepTargetMethodMatched()) {
final String message = "Method <b>" + ((NamedMethodFilter)methodFilter).getMethodName() + "()</b> has not been called";
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.INFO).notify(project);
}
}
}
}
private void processLocatableEvent(final SuspendContextImpl suspendContext, final LocatableEvent event) {
if (myReturnValueWatcher != null && event instanceof MethodExitEvent) {
if (myReturnValueWatcher.processMethodExitEvent(((MethodExitEvent)event))) {
return;
}
}
ThreadReference thread = event.thread();
//LOG.assertTrue(thread.isSuspended());
preprocessEvent(suspendContext, thread);
//we use schedule to allow processing other events during processing this one
//this is especially necessary if a method is breakpoint condition
getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) {
@Override
public void contextAction() throws Exception {
final SuspendManager suspendManager = getSuspendManager();
SuspendContextImpl evaluatingContext = SuspendManagerUtil.getEvaluatingContext(suspendManager, getSuspendContext().getThread());
if (evaluatingContext != null && !DebuggerSession.enableBreakpointsDuringEvaluation()) {
// is inside evaluation, so ignore any breakpoints
suspendManager.voteResume(suspendContext);
return;
}
final LocatableEventRequestor requestor = (LocatableEventRequestor) getRequestsManager().findRequestor(event.request());
boolean resumePreferred = requestor != null && DebuggerSettings.SUSPEND_NONE.equals(requestor.getSuspendPolicy());
boolean requestHit;
try {
requestHit = (requestor != null) && requestor.processLocatableEvent(this, event);
}
catch (final LocatableEventRequestor.EventProcessingException ex) {
if (LOG.isDebugEnabled()) {
LOG.debug(ex.getMessage());
}
final boolean[] considerRequestHit = new boolean[]{true};
DebuggerInvocationUtil.invokeAndWait(getProject(), new Runnable() {
@Override
public void run() {
DebuggerPanelsManager.getInstance(getProject()).toFront(mySession);
final String displayName = requestor instanceof Breakpoint? ((Breakpoint)requestor).getDisplayName() : requestor.getClass().getSimpleName();
final String message = DebuggerBundle.message("error.evaluating.breakpoint.condition.or.action", displayName, ex.getMessage());
considerRequestHit[0] = Messages.showYesNoDialog(getProject(), message, ex.getTitle(), Messages.getQuestionIcon()) == Messages.YES;
}
}, ModalityState.NON_MODAL);
requestHit = considerRequestHit[0];
resumePreferred = !requestHit;
}
if (requestHit && requestor instanceof Breakpoint) {
// if requestor is a breakpoint and this breakpoint was hit, no matter its suspend policy
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
XDebugSession session = getSession().getXDebugSession();
if (session != null) {
XBreakpoint breakpoint = ((Breakpoint)requestor).getXBreakpoint();
if (breakpoint != null) {
((XDebugSessionImpl)session).processDependencies(breakpoint);
}
}
}
});
}
if(!requestHit || resumePreferred) {
suspendManager.voteResume(suspendContext);
}
else {
if (myReturnValueWatcher != null) {
myReturnValueWatcher.disable();
}
//if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
// // there could be explicit resume as a result of call to voteSuspend()
// // e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
// // resuming and all breakpoints in other threads will be ignored.
// // As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
// myBreakpointManager.applyThreadFilter(DebugProcessEvents.this, event.thread());
//}
suspendManager.voteSuspend(suspendContext);
showStatusText(DebugProcessEvents.this, event);
}
}
});
}
private void processDefaultEvent(SuspendContextImpl suspendContext) {
preprocessEvent(suspendContext, null);
getSuspendManager().voteResume(suspendContext);
}
}