blob: 24433a97f1ab57691f281a637b78c719ed07b754 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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 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.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 = Integer.valueOf(0);
/**
* Flag indicating that this breakpoint last suspended execution due to a
* field modification
*/
protected static final Integer MODIFICATION_EVENT = Integer.valueOf(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<>(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 = monitor -> {
setMarker(resource.createMarker(JAVA_WATCHPOINT));
// add attributes
addLineBreakpointAttributes(attributes, getModelIdentifier(),
true, lineNumber, charStart, charEnd);
addTypeNameAndHitCount(attributes, typeName, hitCount);
attributes.put(SUSPEND_POLICY, Integer.valueOf(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,
new VMDisconnectedException());
}
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] = Boolean.valueOf(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;
}
}