| /******************************************************************************* |
| * Copyright (c) 2009, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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; |
| } |
| } |