blob: 94c24659bb241f02407bfc86268d22ba987ea19c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.breakpoints;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.dom.Message;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaBreakpointListener;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaPrimitiveValue;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
import org.eclipse.jdt.debug.eval.ICompiledExpression;
import org.eclipse.jdt.debug.eval.IEvaluationListener;
import org.eclipse.jdt.debug.eval.IEvaluationResult;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDINullValue;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;
import org.eclipse.jdt.internal.debug.core.model.JDIValue;
import com.ibm.icu.text.MessageFormat;
import com.sun.jdi.VMDisconnectedException;
/**
* Breakpoint listener to handle breakpoint conditions.
*
* @since 3.5
*/
public class ConditionalBreakpointHandler implements IJavaBreakpointListener {
/**
* Whether the condition had compile or runtime errors
*/
private boolean fHasErrors = false;
/**
* Listens for evaluation completion for condition evaluation. If an
* evaluation evaluates <code>true</code> or has an error, this breakpoint
* will suspend the thread in which the breakpoint was hit. If the
* evaluation returns <code>false</code>, the thread is resumed.
*/
class EvaluationListener implements IEvaluationListener {
/**
* Lock for synchronizing evaluation
*/
private Object fLock = new Object();
/**
* The breakpoint that was hit
*/
private JavaLineBreakpoint fBreakpoint;
/**
* Result of the vote
*/
private int fVote;
EvaluationListener(JavaLineBreakpoint breakpoint) {
fBreakpoint = breakpoint;
}
@Override
public void evaluationComplete(IEvaluationResult result) {
fVote = determineVote(result);
synchronized (fLock) {
fLock.notifyAll();
}
}
/**
* Processes the result to determine whether to suspend or resume.
*
* @param result
* evaluation result
* @return vote
*/
private int determineVote(IEvaluationResult result) {
if (result.isTerminated()) {
// indicates the user terminated the evaluation
return SUSPEND;
}
JDIThread thread = (JDIThread) result.getThread();
if (result.hasErrors()) {
DebugException exception = result.getException();
Throwable wrappedException = exception.getStatus()
.getException();
if (wrappedException instanceof VMDisconnectedException) {
// VM terminated/disconnected during evaluation
return DONT_SUSPEND;
}
fireConditionHasRuntimeErrors(fBreakpoint, exception);
return SUSPEND;
}
try {
IValue value = result.getValue();
if (fBreakpoint.isConditionSuspendOnTrue()) {
if (value instanceof IJavaPrimitiveValue) {
// Suspend when the condition evaluates true
IJavaPrimitiveValue javaValue = (IJavaPrimitiveValue) value;
if (javaValue.getJavaType().getName()
.equals("boolean")) { //$NON-NLS-1$
if (javaValue.getBooleanValue()) {
return SUSPEND;
}
return DONT_SUSPEND;
}
}
if ((value instanceof JDIValue) && !(value instanceof JDINullValue)) {
JDIValue jdiValue = (JDIValue)value;
// Suspend if return is Boolean(true) else don't suspend (no error dialog)
if (jdiValue.getJavaType().getName().equals("java.lang.Boolean")) {//$NON-NLS-1$
IJavaPrimitiveValue javaValue = (IJavaPrimitiveValue) ((IJavaObject) jdiValue).getField("value", false).getValue(); //$NON-NLS-1$
if (javaValue.getBooleanValue()) {
return SUSPEND;
}
return DONT_SUSPEND;
}
return DONT_SUSPEND;
}
IStatus status = new Status(
IStatus.ERROR,
JDIDebugPlugin.getUniqueIdentifier(),
MessageFormat.format(JDIDebugBreakpointMessages.ConditionalBreakpointHandler_1, value.getReferenceTypeName()));
// result was not JDIValue
fireConditionHasRuntimeErrors(fBreakpoint, new DebugException(status));
return SUSPEND;
}
IDebugTarget debugTarget = thread.getDebugTarget();
IValue lastValue = fBreakpoint
.setCurrentConditionValue(debugTarget, value);
if (!value.equals(lastValue)) {
return SUSPEND;
}
return DONT_SUSPEND;
} catch (DebugException e) {
// Suspend when an error occurs
JDIDebugPlugin.log(e);
return SUSPEND;
}
}
/**
* Result of the conditional expression evaluation - to resume or not
* resume, that is the question.
*
* @return vote result
*/
int getVote() {
return fVote;
}
/**
* Returns the lock object to synchronize this evaluation.
*
* @return lock object
*/
Object getLock() {
return fLock;
}
}
@Override
public void addingBreakpoint(IJavaDebugTarget target,
IJavaBreakpoint breakpoint) {
}
@Override
public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint,
Message[] errors) {
}
@Override
public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint,
DebugException exception) {
}
@Override
public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
if (breakpoint instanceof IJavaLineBreakpoint) {
JavaLineBreakpoint lineBreakpoint = (JavaLineBreakpoint) breakpoint;
try {
final String condition = lineBreakpoint.getCondition();
if (condition == null) {
return SUSPEND;
}
EvaluationListener listener = new EvaluationListener(
lineBreakpoint);
IJavaStackFrame frame = (IJavaStackFrame) thread
.getTopStackFrame();
IJavaProject project = lineBreakpoint.getJavaProject(frame);
if (project == null) {
fireConditionHasErrors(
lineBreakpoint,
new Message[] { new Message(
JDIDebugBreakpointMessages.JavaLineBreakpoint_Unable_to_compile_conditional_breakpoint___missing_Java_project_context__1,
-1) });
return SUSPEND;
}
IJavaDebugTarget target = (IJavaDebugTarget) thread
.getDebugTarget();
IAstEvaluationEngine engine = getEvaluationEngine(target,
project);
if (engine == null) {
// If no engine is available, suspend
return SUSPEND;
}
ICompiledExpression expression = lineBreakpoint
.getExpression(thread);
if (expression == null) {
expression = engine.getCompiledExpression(condition, frame);
lineBreakpoint.setExpression(thread, expression);
}
if (expression.hasErrors()) {
fireConditionHasErrors(lineBreakpoint,
getMessages(expression));
return SUSPEND;
}
Object lock = listener.getLock();
synchronized (lock) {
engine.evaluateExpression(expression, frame, listener,
DebugEvent.EVALUATION_IMPLICIT, false);
// TODO: timeout?
try {
lock.wait();
} catch (InterruptedException e) {
fireConditionHasRuntimeErrors(
lineBreakpoint,
new DebugException(
new Status(
IStatus.ERROR,
JDIDebugPlugin
.getUniqueIdentifier(),
JDIDebugBreakpointMessages.ConditionalBreakpointHandler_0,
e)));
return SUSPEND;
}
}
return listener.getVote();
} catch (CoreException e) {
DebugException de = null;
if (e instanceof DebugException) {
de = (DebugException) e;
} else {
de = new DebugException(e.getStatus());
}
fireConditionHasRuntimeErrors(lineBreakpoint, de);
}
}
return SUSPEND;
}
@Override
public void breakpointInstalled(IJavaDebugTarget target,
IJavaBreakpoint breakpoint) {
}
@Override
public void breakpointRemoved(IJavaDebugTarget target,
IJavaBreakpoint breakpoint) {
}
@Override
public int installingBreakpoint(IJavaDebugTarget target,
IJavaBreakpoint breakpoint, IJavaType type) {
return 0;
}
/**
* Returns an evaluation engine for evaluating this breakpoint's condition
* in the given target and project context.
* @param vm the VM to get an evaluation engine for
* @param project the project context
* @return a new {@link IAstEvaluationEngine}
*/
private IAstEvaluationEngine getEvaluationEngine(IJavaDebugTarget vm, IJavaProject project) {
return ((JDIDebugTarget) vm).getEvaluationEngine(project);
}
private void fireConditionHasRuntimeErrors(IJavaLineBreakpoint breakpoint, DebugException exception) {
fHasErrors = true;
JDIDebugPlugin.getDefault().fireBreakpointHasRuntimeException(breakpoint, exception);
}
/**
* Notifies listeners that a conditional breakpoint expression has been
* compiled that contains errors
* @param breakpoint the breakpoint that has errors in its condition
* @param messages the error messages
*/
private void fireConditionHasErrors(IJavaLineBreakpoint breakpoint, Message[] messages) {
fHasErrors = true;
JDIDebugPlugin.getDefault().fireBreakpointHasCompilationErrors(breakpoint, messages);
}
/**
* Convert an array of <code>String</code> to an array of
* <code>Message</code>.
* @param expression the expression to get messages from
* @return the array of {@link Message}s from the expression
*/
private Message[] getMessages(ICompiledExpression expression) {
String[] errorMessages = expression.getErrorMessages();
Message[] messages = new Message[errorMessages.length];
for (int i = 0; i < messages.length; i++) {
messages[i] = new Message(errorMessages[i], -1);
}
return messages;
}
/**
* Returns whether errors were encountered when evaluating the condition
* (compilation or runtime).
*
* @return whether errors were encountered when evaluating the condition
*/
public boolean hasErrors() {
return fHasErrors;
}
}