/******************************************************************************* * 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; } }