| /******************************************************************************* |
| * Copyright (c) 2000, 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 java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.model.IBreakpoint; |
| import org.eclipse.debug.core.model.IDebugTarget; |
| import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; |
| import org.eclipse.jdt.debug.core.IJavaWatchpoint; |
| import org.eclipse.jdt.debug.core.JDIDebugModel; |
| import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; |
| import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget; |
| |
| import com.sun.jdi.Field; |
| import com.sun.jdi.ObjectReference; |
| import com.sun.jdi.ReferenceType; |
| import com.sun.jdi.ThreadReference; |
| import com.sun.jdi.VMDisconnectedException; |
| import com.sun.jdi.event.AccessWatchpointEvent; |
| import com.sun.jdi.event.Event; |
| import com.sun.jdi.event.EventSet; |
| import com.sun.jdi.event.ModificationWatchpointEvent; |
| import com.sun.jdi.request.AccessWatchpointRequest; |
| import com.sun.jdi.request.EventRequest; |
| import com.sun.jdi.request.EventRequestManager; |
| import com.sun.jdi.request.ModificationWatchpointRequest; |
| import com.sun.jdi.request.WatchpointRequest; |
| |
| public class JavaWatchpoint extends JavaLineBreakpoint implements |
| IJavaWatchpoint { |
| |
| public static final String JAVA_WATCHPOINT = "org.eclipse.jdt.debug.javaWatchpointMarker"; //$NON-NLS-1$ |
| /** |
| * Watchpoint attribute storing the access value (value |
| * <code>"org.eclipse.jdt.debug.core.access"</code>). This attribute is |
| * stored as a <code>boolean</code>, indicating whether a watchpoint is an |
| * access watchpoint. |
| */ |
| protected static final String ACCESS = "org.eclipse.jdt.debug.core.access"; //$NON-NLS-1$ |
| /** |
| * Watchpoint attribute storing the modification value (value |
| * <code>"org.eclipse.jdt.debug.core.modification"</code>). This attribute |
| * is stored as a <code>boolean</code>, indicating whether a watchpoint is a |
| * modification watchpoint. |
| */ |
| protected static final String MODIFICATION = "org.eclipse.jdt.debug.core.modification"; //$NON-NLS-1$ |
| /** |
| * Watchpoint attribute storing the auto_disabled value (value |
| * <code>"org.eclipse.jdt.debug.core.auto_disabled"</code>). This attribute |
| * is stored as a <code>boolean</code>, indicating whether a watchpoint has |
| * been auto-disabled (as opposed to being disabled explicitly by the user) |
| */ |
| protected static final String AUTO_DISABLED = "org.eclipse.jdt.debug.core.auto_disabled"; //$NON-NLS-1$ |
| |
| /** |
| * Breakpoint attribute storing the name of the field on which a breakpoint |
| * is set. (value <code>"org.eclipse.jdt.debug.core.fieldName"</code>). This |
| * attribute is a <code>String</code>. |
| */ |
| protected static final String FIELD_NAME = "org.eclipse.jdt.debug.core.fieldName"; //$NON-NLS-1$ |
| /** |
| * Flag indicating that this breakpoint last suspended execution due to a |
| * field access |
| */ |
| protected static final Integer ACCESS_EVENT = new Integer(0); |
| /** |
| * Flag indicating that this breakpoint last suspended execution due to a |
| * field modification |
| */ |
| protected static final Integer MODIFICATION_EVENT = new Integer(1); |
| /** |
| * Maps each debug target that is suspended for this breakpoint to reason |
| * that this breakpoint suspended it. Reasons include: |
| * <ol> |
| * <li>Field access (value <code>ACCESS_EVENT</code>)</li> |
| * <li>Field modification (value <code>MODIFICATION_EVENT</code>)</li> |
| * </ol> |
| */ |
| private HashMap<JDIDebugTarget, Integer> fLastEventTypes = new HashMap<JDIDebugTarget, Integer>(10); |
| |
| public JavaWatchpoint() { |
| } |
| |
| /** |
| * @see JDIDebugModel#createWatchpoint(IResource, String, String, int, int, |
| * int, int, boolean, Map) |
| */ |
| public JavaWatchpoint(final IResource resource, final String typeName, |
| final String fieldName, final int lineNumber, final int charStart, |
| final int charEnd, final int hitCount, final boolean add, |
| final Map<String, Object> attributes) throws DebugException { |
| IWorkspaceRunnable wr = new IWorkspaceRunnable() { |
| @Override |
| public void run(IProgressMonitor monitor) throws CoreException { |
| setMarker(resource.createMarker(JAVA_WATCHPOINT)); |
| |
| // add attributes |
| addLineBreakpointAttributes(attributes, getModelIdentifier(), |
| true, lineNumber, charStart, charEnd); |
| addTypeNameAndHitCount(attributes, typeName, hitCount); |
| attributes.put(SUSPEND_POLICY, new Integer( |
| getDefaultSuspendPolicy())); |
| // configure the field handle |
| addFieldName(attributes, fieldName); |
| // configure the access and modification flags to defaults |
| addDefaultAccessAndModification(attributes); |
| |
| // set attributes |
| ensureMarker().setAttributes(attributes); |
| |
| register(add); |
| } |
| }; |
| run(getMarkerRule(resource), wr); |
| } |
| |
| /** |
| * @see JavaBreakpoint#createRequest(JDIDebugTarget, ReferenceType) |
| * |
| * Creates and installs an access and modification watchpoint request |
| * in the given reference type, configuring the requests as appropriate |
| * for this watchpoint. The requests are then enabled based on whether |
| * this watchpoint is an access watchpoint, modification watchpoint, or |
| * both. Finally, the requests are registered with the given target. |
| */ |
| @Override |
| protected boolean createRequest(JDIDebugTarget target, ReferenceType type) |
| throws CoreException { |
| if (shouldSkipBreakpoint()) { |
| return false; |
| } |
| Field field = null; |
| |
| field = type.fieldByName(getFieldName()); |
| if (field == null) { |
| // error |
| return false; |
| } |
| AccessWatchpointRequest accessRequest = null; |
| ModificationWatchpointRequest modificationRequest = null; |
| if (target.supportsAccessWatchpoints()) { |
| accessRequest = createAccessWatchpoint(target, field); |
| registerRequest(accessRequest, target); |
| } else { |
| notSupported(JDIDebugBreakpointMessages.JavaWatchpoint_no_access_watchpoints); |
| } |
| if (target.supportsModificationWatchpoints()) { |
| modificationRequest = createModificationWatchpoint(target, field); |
| if (modificationRequest == null) { |
| return false; |
| } |
| registerRequest(modificationRequest, target); |
| return true; |
| } |
| notSupported(JDIDebugBreakpointMessages.JavaWatchpoint_no_modification_watchpoints); |
| return false; |
| } |
| |
| /** |
| * @see JavaBreakpoint#setRequestThreadFilter(EventRequest) |
| */ |
| @Override |
| protected void setRequestThreadFilter(EventRequest request, |
| ThreadReference thread) { |
| ((WatchpointRequest) request).addThreadFilter(thread); |
| } |
| |
| /** |
| * Either access or modification watchpoints are not supported. Throw an |
| * appropriate exception. |
| * |
| * @param message |
| * the message that states that access or modification |
| * watchpoints are not supported |
| */ |
| protected void notSupported(String message) throws DebugException { |
| throw new DebugException(new Status(IStatus.ERROR, |
| DebugPlugin.getUniqueIdentifier(), |
| DebugException.NOT_SUPPORTED, message, null)); // |
| } |
| |
| /** |
| * Create an access watchpoint for the given breakpoint and associated field |
| */ |
| protected AccessWatchpointRequest createAccessWatchpoint( |
| JDIDebugTarget target, Field field) throws CoreException { |
| return (AccessWatchpointRequest) createWatchpoint(target, field, true); |
| } |
| |
| /** |
| * Create a modification watchpoint for the given breakpoint and associated |
| * field |
| */ |
| protected ModificationWatchpointRequest createModificationWatchpoint( |
| JDIDebugTarget target, Field field) throws CoreException { |
| return (ModificationWatchpointRequest) createWatchpoint(target, field, |
| false); |
| } |
| |
| /** |
| * Create a watchpoint for the given breakpoint and associated field. |
| * |
| * @param target |
| * the target in which the request will be installed |
| * @param field |
| * the field on which the request will be set |
| * @param access |
| * <code>true</code> if an access watchpoint will be created. |
| * <code>false</code> if a modification watchpoint will be |
| * created. |
| * |
| * @return an WatchpointRequest (AccessWatchpointRequest if access is |
| * <code>true</code>; ModificationWatchpointRequest if access is |
| * <code>false</code>). |
| */ |
| protected WatchpointRequest createWatchpoint(JDIDebugTarget target, |
| Field field, boolean access) throws CoreException { |
| WatchpointRequest request = null; |
| EventRequestManager manager = target.getEventRequestManager(); |
| if (manager == null) { |
| target.requestFailed( |
| JDIDebugBreakpointMessages.JavaWatchpoint_Unable_to_create_breakpoint_request___VM_disconnected__1, |
| null); |
| } |
| try { |
| if (access) { |
| request = manager.createAccessWatchpointRequest(field); |
| } else { |
| request = manager.createModificationWatchpointRequest(field); |
| } |
| configureRequest(request, target); |
| } catch (VMDisconnectedException e) { |
| if (!target.isAvailable()) { |
| return null; |
| } |
| target.internalError(e); |
| return null; |
| } catch (RuntimeException e) { |
| target.internalError(e); |
| return null; |
| } |
| return request; |
| } |
| |
| /** |
| * @see JavaBreakpoint#recreateRequest(EventRequest, JDIDebugTarget) |
| */ |
| protected EventRequest recreateRequest(EventRequest request, |
| JDIDebugTarget target) throws CoreException { |
| try { |
| Field field = ((WatchpointRequest) request).field(); |
| if (request instanceof AccessWatchpointRequest) { |
| request = createAccessWatchpoint(target, field); |
| } else if (request instanceof ModificationWatchpointRequest) { |
| request = createModificationWatchpoint(target, field); |
| } |
| } catch (VMDisconnectedException e) { |
| if (!target.isAvailable()) { |
| return request; |
| } |
| target.internalError(e); |
| return request; |
| } catch (RuntimeException e) { |
| target.internalError(e); |
| } |
| return request; |
| } |
| |
| /** |
| * @see IBreakpoint#setEnabled(boolean) |
| * |
| * If the watchpoint is not watching access or modification, set the |
| * default values. If this isn't done, the resulting state (enabled |
| * with access and modification both disabled) is ambiguous. |
| */ |
| @Override |
| public void setEnabled(boolean enabled) throws CoreException { |
| if (enabled) { |
| if (!(isAccess() || isModification())) { |
| setDefaultAccessAndModification(); |
| } |
| } |
| super.setEnabled(enabled); |
| } |
| |
| /** |
| * @see org.eclipse.debug.core.model.IWatchpoint#isAccess() |
| */ |
| @Override |
| public boolean isAccess() throws CoreException { |
| return ensureMarker().getAttribute(ACCESS, false); |
| } |
| |
| /** |
| * Sets whether this breakpoint will suspend execution when its associated |
| * field is accessed. If true and this watchpoint is disabled, this |
| * watchpoint is automatically enabled. If both access and modification are |
| * false, this watchpoint is automatically disabled. |
| * |
| * @param access |
| * whether to suspend on field access |
| * @exception CoreException |
| * if unable to set the property on this breakpoint's |
| * underlying marker |
| * @see org.eclipse.debug.core.model.IWatchpoint#setAccess(boolean) |
| */ |
| @Override |
| public void setAccess(boolean access) throws CoreException { |
| if (access == isAccess()) { |
| return; |
| } |
| setAttribute(ACCESS, access); |
| if (access && !isEnabled()) { |
| setEnabled(true); |
| } else if (!(access || isModification())) { |
| setEnabled(false); |
| } |
| recreate(); |
| } |
| |
| /** |
| * @see org.eclipse.debug.core.model.IWatchpoint#isModification() |
| */ |
| @Override |
| public boolean isModification() throws CoreException { |
| return ensureMarker().getAttribute(MODIFICATION, false); |
| } |
| |
| /** |
| * Sets whether this breakpoint will suspend execution when its associated |
| * field is modified. If true and this watchpoint is disabled, this |
| * watchpoint is automatically enabled. If both access and modification are |
| * false, this watchpoint is automatically disabled. |
| * |
| * @param modification |
| * whether to suspend on field modification |
| * @exception CoreException |
| * if unable to set the property on this breakpoint's |
| * underlying marker |
| * @see org.eclipse.debug.core.model.IWatchpoint#setModification(boolean) |
| */ |
| @Override |
| public void setModification(boolean modification) throws CoreException { |
| if (modification == isModification()) { |
| return; |
| } |
| setAttribute(MODIFICATION, modification); |
| if (modification && !isEnabled()) { |
| setEnabled(true); |
| } else if (!(modification || isAccess())) { |
| setEnabled(false); |
| } |
| recreate(); |
| } |
| |
| /** |
| * Sets the default access and modification attributes of the watchpoint. |
| * The default values are: |
| * <ul> |
| * <li>access = <code>false</code> |
| * <li>modification = <code>true</code> |
| * <ul> |
| */ |
| protected void setDefaultAccessAndModification() throws CoreException { |
| boolean[] def = getDefaultAccessAndModificationValues(); |
| Object[] values = new Object[def.length]; |
| for (int i = 0; i < def.length; i++) { |
| values[i] = new Boolean(def[i]); |
| } |
| String[] attributes = new String[] { ACCESS, MODIFICATION }; |
| setAttributes(attributes, values); |
| } |
| |
| /** |
| * Returns the default access and modification suspend option for a new |
| * watchpoint based on the user preference settings The return array will |
| * only ever contain two values, where the possibilities are: |
| * <ul> |
| * <li> <code>{true, true}</code> - both access and modification are enabled</li> |
| * <li> <code>{true, false}</code> - access is enabled and modification is |
| * disabled</li> |
| * <li> <code>{false, true}</code> -access is disabled and modification is |
| * enabled</li> |
| * </ul> |
| * The default returned array is <code>{true, true}</code> |
| * |
| * @return an array of two boolean values representing the default access |
| * and modification settings |
| * |
| * @since 3.3.1 |
| */ |
| protected boolean[] getDefaultAccessAndModificationValues() { |
| int value = Platform.getPreferencesService().getInt( |
| JDIDebugPlugin.getUniqueIdentifier(), |
| JDIDebugPlugin.PREF_DEFAULT_WATCHPOINT_SUSPEND_POLICY, |
| 0, |
| null); |
| switch (value) { |
| case 0: { |
| return new boolean[] { true, true }; |
| } |
| case 1: { |
| return new boolean[] { true, false }; |
| } |
| case 2: { |
| return new boolean[] { false, true }; |
| } |
| default: { |
| return new boolean[] { true, true }; |
| } |
| } |
| } |
| |
| /** |
| * Adds the default access and modification attributes of the watchpoint to |
| * the given map |
| * <ul> |
| * <li>access = true |
| * <li>modification = true |
| * <li>auto disabled = false |
| * <ul> |
| */ |
| protected void addDefaultAccessAndModification(Map<String, Object> attributes) { |
| boolean[] values = getDefaultAccessAndModificationValues(); |
| attributes.put(ACCESS, (values[0] ? Boolean.TRUE : Boolean.FALSE)); |
| attributes |
| .put(MODIFICATION, (values[1] ? Boolean.TRUE : Boolean.FALSE)); |
| attributes.put(AUTO_DISABLED, Boolean.FALSE); |
| } |
| |
| /** |
| * Adds the field name to the given attribute map |
| */ |
| protected void addFieldName(Map<String, Object> attributes, String fieldName) { |
| attributes.put(FIELD_NAME, fieldName); |
| } |
| |
| /** |
| * @see IJavaWatchpoint#getFieldName() |
| */ |
| @Override |
| public String getFieldName() throws CoreException { |
| return ensureMarker().getAttribute(FIELD_NAME, null); |
| } |
| |
| /** |
| * Store the type of the event, then handle it as specified in the |
| * superclass. This is useful for correctly generating the thread text when |
| * asked (assumes thread text is requested after the event is passed to this |
| * breakpoint. |
| * |
| * Also, @see JavaBreakpoint#handleEvent(Event, JDIDebugTarget) |
| */ |
| @Override |
| public boolean handleEvent(Event event, JDIDebugTarget target, |
| boolean suspendVote, EventSet eventSet) { |
| if (event instanceof AccessWatchpointEvent) { |
| fLastEventTypes.put(target, ACCESS_EVENT); |
| } else if (event instanceof ModificationWatchpointEvent) { |
| fLastEventTypes.put(target, MODIFICATION_EVENT); |
| } |
| return super.handleEvent(event, target, suspendVote, eventSet); |
| } |
| |
| /** |
| * @see JavaBreakpoint#updateEnabledState(EventRequest, JDIDebugTarget) |
| */ |
| @Override |
| protected void updateEnabledState(EventRequest request, |
| JDIDebugTarget target) throws CoreException { |
| boolean enabled = isEnabled(); |
| if (request instanceof AccessWatchpointRequest) { |
| if (isAccess()) { |
| if (enabled != request.isEnabled()) { |
| internalUpdateEnabledState(request, enabled, target); |
| } |
| } else { |
| if (request.isEnabled()) { |
| internalUpdateEnabledState(request, false, target); |
| } |
| } |
| } |
| if (request instanceof ModificationWatchpointRequest) { |
| if (isModification()) { |
| if (enabled != request.isEnabled()) { |
| internalUpdateEnabledState(request, enabled, target); |
| } |
| } else { |
| if (request.isEnabled()) { |
| internalUpdateEnabledState(request, false, target); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see IJavaWatchpoint#isAccessSuspend(IDebugTarget) |
| */ |
| @Override |
| public boolean isAccessSuspend(IDebugTarget target) { |
| Integer lastEventType = fLastEventTypes.get(target); |
| if (lastEventType == null) { |
| return false; |
| } |
| return lastEventType.equals(ACCESS_EVENT); |
| } |
| |
| /** |
| * @see IJavaLineBreakpoint#supportsCondition() |
| */ |
| @Override |
| public boolean supportsCondition() { |
| return true; |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#removeFromTarget(JDIDebugTarget) |
| */ |
| @Override |
| public void removeFromTarget(JDIDebugTarget target) throws CoreException { |
| fLastEventTypes.remove(target); |
| super.removeFromTarget(target); |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#addInstanceFilter(EventRequest, |
| * ObjectReference) |
| */ |
| @Override |
| protected void addInstanceFilter(EventRequest request, |
| ObjectReference object) { |
| if (request instanceof WatchpointRequest) { |
| ((WatchpointRequest) request).addInstanceFilter(object); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.debug.core.model.IWatchpoint#supportsAccess() |
| */ |
| @Override |
| public boolean supportsAccess() { |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.debug.core.model.IWatchpoint#supportsModification() |
| */ |
| @Override |
| public boolean supportsModification() { |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint# |
| * installableReferenceType(com.sun.jdi.ReferenceType, |
| * org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget) |
| */ |
| @Override |
| protected boolean installableReferenceType(ReferenceType type, |
| JDIDebugTarget target) throws CoreException { |
| String installableType = getTypeName(); |
| String queriedType = type.name(); |
| if (installableType == null || queriedType == null) { |
| return false; |
| } |
| if (installableType.equals(queriedType)) { |
| return queryInstallListeners(target, type); |
| } |
| |
| return false; |
| } |
| } |