blob: 5a4ff4a5cccb38394799678daf4fd0d59075a2ec [file] [log] [blame]
/*******************************************************************************
* 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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
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.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint;
import org.eclipse.jdt.debug.core.IJavaObject;
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.JDIThread;
import org.eclipse.jdt.internal.debug.core.model.JDIValue;
import com.sun.jdi.ClassType;
import com.sun.jdi.Location;
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.Event;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ExceptionRequest;
public class JavaExceptionBreakpoint extends JavaBreakpoint implements
IJavaExceptionBreakpoint {
public static final String JAVA_EXCEPTION_BREAKPOINT = "org.eclipse.jdt.debug.javaExceptionBreakpointMarker"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the suspend on caught value (value
* <code>"org.eclipse.jdt.debug.core.caught"</code>). This attribute is
* stored as a <code>boolean</code>. When this attribute is
* <code>true</code>, a caught exception of the associated type will cause
* execution to suspend .
*/
protected static final String CAUGHT = "org.eclipse.jdt.debug.core.caught"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the suspend on uncaught value
* (value <code>"org.eclipse.jdt.debug.core.uncaught"</code>). This
* attribute is stored as a <code>boolean</code>. When this attribute is
* <code>true</code>, an uncaught exception of the associated type will
* cause execution to suspend.
*/
protected static final String UNCAUGHT = "org.eclipse.jdt.debug.core.uncaught"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the checked value (value
* <code>"org.eclipse.jdt.debug.core.checked"</code>). This attribute is
* stored as a <code>boolean</code>, indicating whether an exception is a
* checked exception.
*/
protected static final String CHECKED = "org.eclipse.jdt.debug.core.checked"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the String value (value
* <code>"org.eclipse.jdt.debug.core.filters"</code>). This attribute is
* stored as a <code>String</code>, a comma delimited list of class filters.
* The filters are applied as inclusion or exclusion depending on
* INCLUSIVE_FILTERS.
*/
protected static final String INCLUSION_FILTERS = "org.eclipse.jdt.debug.core.inclusion_filters"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the String value (value
* <code>"org.eclipse.jdt.debug.core.filters"</code>). This attribute is
* stored as a <code>String</code>, a comma delimited list of class filters.
* The filters are applied as inclusion or exclusion depending on
* INCLUSIVE_FILTERS.
*/
protected static final String EXCLUSION_FILTERS = "org.eclipse.jdt.debug.core.exclusion_filters"; //$NON-NLS-1$
/**
* Allows the user to specify whether we should suspend if subclasses of the
* specified exception are thrown/caught
*
* @since 3.2
*/
protected static final String SUSPEND_ON_SUBCLASSES = "org.eclipse.jdt.debug.core.suspend_on_subclasses"; //$NON-NLS-1$
/**
* Name of the exception that was actually hit (could be a sub-type of the
* type that is being caught).
*/
protected String fExceptionName = null;
/**
* The current set of inclusion class filters.
*/
protected String[] fInclusionClassFilters = null;
/**
* The current set of inclusion class filters.
*/
protected String[] fExclusionClassFilters = null;
private ObjectReference fLastException;
private JDIDebugTarget fLastTarget;
public JavaExceptionBreakpoint() {
}
/**
* Creates and returns an exception breakpoint for the given (throwable)
* type. Caught and uncaught specify where the exception should cause thread
* suspensions - that is, in caught and/or uncaught locations. Checked
* indicates if the given exception is a checked exception.
*
* @param resource
* the resource on which to create the associated breakpoint
* marker
* @param exceptionName
* the fully qualified name of the exception for which to create
* the breakpoint
* @param caught
* whether to suspend in caught locations
* @param uncaught
* whether to suspend in uncaught locations
* @param checked
* whether the exception is a checked exception
* @param add
* whether to add this breakpoint to the breakpoint manager
* @return a Java exception breakpoint
* @exception DebugException
* if unable to create the associated marker due to a lower
* level exception.
*/
public JavaExceptionBreakpoint(final IResource resource,
final String exceptionName, final boolean caught,
final boolean uncaught, final boolean checked, final boolean add,
final Map<String, Object> attributes) throws DebugException {
IWorkspaceRunnable wr = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
// create the marker
setMarker(resource.createMarker(JAVA_EXCEPTION_BREAKPOINT));
// add attributes
attributes.put(IBreakpoint.ID, getModelIdentifier());
attributes.put(TYPE_NAME, exceptionName);
attributes.put(ENABLED, Boolean.TRUE);
attributes.put(CAUGHT, Boolean.valueOf(caught));
attributes.put(UNCAUGHT, Boolean.valueOf(uncaught));
attributes.put(CHECKED, Boolean.valueOf(checked));
attributes.put(SUSPEND_POLICY, new Integer(
getDefaultSuspendPolicy()));
ensureMarker().setAttributes(attributes);
register(add);
}
};
run(getMarkerRule(resource), wr);
}
/**
* Creates a request in the given target to suspend when the given exception
* type is thrown. The request is returned installed, configured, and
* enabled as appropriate for this breakpoint.
*/
@Override
protected EventRequest[] newRequests(JDIDebugTarget target,
ReferenceType type) throws CoreException {
if (!isCaught() && !isUncaught()) {
return null;
}
ExceptionRequest request = null;
EventRequestManager manager = target.getEventRequestManager();
if (manager == null) {
target.requestFailed(
JDIDebugBreakpointMessages.JavaExceptionBreakpoint_Unable_to_create_breakpoint_request___VM_disconnected__1,
null);
return null;
}
try {
request = manager.createExceptionRequest(type, isCaught(),
isUncaught());
configureRequest(request, target);
} catch (VMDisconnectedException e) {
if (target.isAvailable()) {
JDIDebugPlugin.log(e);
}
return null;
} catch (RuntimeException e) {
target.internalError(e);
return null;
}
return new EventRequest[] { request };
}
/**
* Enable this exception breakpoint.
*
* If the exception breakpoint is not catching caught or uncaught, turn both
* modes on. If this isn't done, the resulting state (enabled with caught
* and uncaught both disabled) is ambiguous.
*/
@Override
public void setEnabled(boolean enabled) throws CoreException {
if (enabled) {
if (!(isCaught() || isUncaught())) {
setAttributes(new String[] { CAUGHT, UNCAUGHT }, new Object[] {
Boolean.TRUE, Boolean.TRUE });
}
}
super.setEnabled(enabled);
}
/**
* Sets the values for whether this breakpoint will suspend execution when
* the associated exception is thrown and caught or not caught.
*/
protected void setCaughtAndUncaught(boolean caught, boolean uncaught)
throws CoreException {
Object[] values = new Object[] { Boolean.valueOf(caught),
Boolean.valueOf(uncaught) };
String[] attributes = new String[] { CAUGHT, UNCAUGHT };
setAttributes(attributes, values);
}
/**
* @see IJavaExceptionBreakpoint#isCaught()
*/
@Override
public boolean isCaught() throws CoreException {
return ensureMarker().getAttribute(CAUGHT, false);
}
/**
* @see IJavaExceptionBreakpoint#setCaught(boolean)
*/
@Override
public void setCaught(boolean caught) throws CoreException {
if (caught == isCaught()) {
return;
}
setAttribute(CAUGHT, caught);
if (caught && !isEnabled()) {
setEnabled(true);
} else if (!(caught || isUncaught())) {
setEnabled(false);
}
recreate();
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#setSuspendOnSubclasses
* (boolean)
*/
public void setSuspendOnSubclasses(boolean suspend) throws CoreException {
if (suspend != isSuspendOnSubclasses()) {
setAttribute(SUSPEND_ON_SUBCLASSES, suspend);
recreate();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#isSuspendOnSubclasses
* ()
*/
public boolean isSuspendOnSubclasses() throws CoreException {
return ensureMarker().getAttribute(SUSPEND_ON_SUBCLASSES, false);
}
/**
* @see IJavaExceptionBreakpoint#isUncaught()
*/
@Override
public boolean isUncaught() throws CoreException {
return ensureMarker().getAttribute(UNCAUGHT, false);
}
/**
* @see IJavaExceptionBreakpoint#setUncaught(boolean)
*/
@Override
public void setUncaught(boolean uncaught) throws CoreException {
if (uncaught == isUncaught()) {
return;
}
setAttribute(UNCAUGHT, uncaught);
if (uncaught && !isEnabled()) {
setEnabled(true);
} else if (!(uncaught || isCaught())) {
setEnabled(false);
}
recreate();
}
/**
* @see IJavaExceptionBreakpoint#isChecked()
*/
@Override
public boolean isChecked() throws CoreException {
return ensureMarker().getAttribute(CHECKED, false);
}
/**
* @see JavaBreakpoint#setRequestThreadFilter(EventRequest)
*/
@Override
protected void setRequestThreadFilter(EventRequest request,
ThreadReference thread) {
((ExceptionRequest) request).addThreadFilter(thread);
}
/**
* @see JavaBreakpoint#handleBreakpointEvent(Event, JDIDebugTarget,
* JDIThread) Decides how to handle an exception being thrown
*
* @return true if we do not want to suspend false otherwise
*/
@Override
public boolean handleBreakpointEvent(Event event, JDIThread thread,
boolean suspendVote) {
if (event instanceof ExceptionEvent) {
ObjectReference ex = ((ExceptionEvent) event).exception();
fLastTarget = thread.getJavaDebugTarget();
fLastException = ex;
String name = null;
try {
name = ex.type().name();
if (!name.equals(getTypeName())) {
if (!isSuspendOnSubclasses()
& isSubclass((ClassType) ex.type(), getTypeName())) {
return true;
}
}
} catch (VMDisconnectedException e) {
return true;
} catch (CoreException e) {
JDIDebugPlugin.log(e);
} catch (RuntimeException e) {
try {
thread.targetRequestFailed(e.getMessage(), e);
} catch (DebugException de) {
JDIDebugPlugin.log(e);
return false;
}
}
setExceptionName(name);
disableTriggerPoint(event);
IBreakpoint[] allBreakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints();
for (IBreakpoint iBreakpoint : allBreakpoints) {
if (iBreakpoint instanceof JavaExceptionBreakpoint) {
JavaExceptionBreakpoint jExceptionBreakpoint = (JavaExceptionBreakpoint)iBreakpoint;
try {
//This Java Exception breakpoint is the breakpoint created for catching compilation and Uncaught exception in JavaDebugOptionsManagaer initialization
// This does not have scope defined
if (jExceptionBreakpoint.getTypeName().equals(name)) {
if (jExceptionBreakpoint.getExclusionClassFilters().length >= 1
|| jExceptionBreakpoint.getInclusionClassFilters().length >= 1
|| filtersIncludeDefaultPackage(jExceptionBreakpoint.fInclusionClassFilters)
|| filtersIncludeDefaultPackage(jExceptionBreakpoint.fExclusionClassFilters)) {
Location location = ((ExceptionEvent) event).location();
String typeName = location.declaringType().name();
boolean defaultPackage = typeName.indexOf('.') == -1;
boolean included = true;
String[] filters = jExceptionBreakpoint.getInclusionClassFilters();
if (filters.length > 0) {
included = matchesFilters(filters, typeName, defaultPackage);
}
boolean excluded = false;
filters = jExceptionBreakpoint.getExclusionClassFilters();
if (filters.length > 0) {
excluded = matchesFilters(filters, typeName, defaultPackage);
}
if (included && !excluded) {
return !suspend(thread, suspendVote);
}
return true;
}
}
}
catch (CoreException e) {
e.printStackTrace();
}
}
}
return !suspend(thread, suspendVote);
}
return true;
}
/**
* Returns whether the given class type is a subclass of the classes with the
* given name.
*
* @param type
* the class type reference
* @return true if the specified the class type is a subclass of the class
* with the given name
* @since 3.2
*/
private boolean isSubclass(ClassType type, String typeName) {
type = type.superclass();
while (type != null) {
if (type.name().equals(typeName)) {
return true;
}
type = type.superclass();
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#setInstalledIn
* (org.eclipse.jdt.debug.core.IJavaDebugTarget, boolean)
*/
@Override
protected void setInstalledIn(IJavaDebugTarget target, boolean installed) {
fLastException = null;
fLastTarget = null;
super.setInstalledIn(target, installed);
}
/**
* Determines of the filters for this exception include the default package
* or not
*
* @param filters
* the list of filters to inspect
* @return true if any one of the specified filters include the default
* package
*/
protected boolean filtersIncludeDefaultPackage(String[] filters) {
for (String filter : filters) {
if (filter.length() == 0 || (filter.indexOf('.') == -1)) {
return true;
}
}
return false;
}
/**
* Returns whether the given type is in the given filter set.
*
* @param filters
* the filter set
* @param typeName
* fully qualified type name
* @param defaultPackage
* whether the type name is in the default package
* @return boolean
*/
protected boolean matchesFilters(String[] filters, String typeName,
boolean defaultPackage) {
for (String filter2 : filters) {
String filter = filter2;
if (defaultPackage && filter.length() == 0) {
return true;
}
filter = filter.replaceAll("\\.", "\\\\."); //$NON-NLS-1$//$NON-NLS-2$
filter = filter.replaceAll("\\*", "\\.\\*"); //$NON-NLS-1$//$NON-NLS-2$
Pattern pattern = Pattern.compile(filter);
if (pattern.matcher(typeName).find()) {
return true;
}
}
return false;
}
/**
* Sets the name of the exception that was last hit
*
* @param name
* fully qualified exception name
*/
protected void setExceptionName(String name) {
fExceptionName = name;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#getExceptionTypeName
* ()
*/
@Override
public String getExceptionTypeName() {
return fExceptionName;
}
/**
* @see IJavaExceptionBreakpoint#getFilters()
* @deprecated
*/
@Override
@Deprecated
public String[] getFilters() {
String[] iFilters = getInclusionFilters();
String[] eFilters = getExclusionFilters();
String[] filters = new String[iFilters.length + eFilters.length];
System.arraycopy(iFilters, 0, filters, 0, iFilters.length);
System.arraycopy(eFilters, 0, filters, iFilters.length, eFilters.length);
return filters;
}
/**
* @see IJavaExceptionBreakpoint#setFilters(String[], boolean)
* @deprecated
*/
@Override
@Deprecated
public void setFilters(String[] filters, boolean inclusive)
throws CoreException {
if (inclusive) {
setInclusionFilters(filters);
} else {
setExclusionFilters(filters);
}
recreate();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
* configureRequest(com.sun.jdi.request.EventRequest,
* org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
*/
@Override
protected void configureRequest(EventRequest eRequest, JDIDebugTarget target)
throws CoreException {
String[] iFilters = getInclusionClassFilters();
String[] eFilters = getExclusionClassFilters();
ExceptionRequest request = (ExceptionRequest) eRequest;
if (iFilters.length == 1) {
if (eFilters.length == 0) {
request.addClassFilter(iFilters[0]);
}
} else if (eFilters.length == 1) {
if (iFilters.length == 0) {
request.addClassExclusionFilter(eFilters[0]);
}
}
super.configureRequest(eRequest, target);
}
/**
* Serializes the array of Strings into one comma separated String. Removes
* duplicates.
*/
protected String serializeList(String[] list) {
if (list == null) {
return ""; //$NON-NLS-1$
}
Set<String> set = new HashSet<String>(list.length);
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < list.length; i++) {
if (i > 0 && i < list.length) {
buffer.append(',');
}
String pattern = list[i];
if (!set.contains(pattern)) {
if (pattern.length() == 0) {
// serialize the default package
pattern = "."; //$NON-NLS-1$
}
buffer.append(pattern);
set.add(pattern);
}
}
return buffer.toString();
}
/**
* Parses the comma separated String into an array of Strings
*/
protected String[] parseList(String listString) {
List<String> list = new ArrayList<String>(10);
StringTokenizer tokenizer = new StringTokenizer(listString, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.equals(".")) { //$NON-NLS-1$
// serialized form for the default package
// @see serializeList(String[])
token = ""; //$NON-NLS-1$
}
list.add(token);
}
return list.toArray(new String[list.size()]);
}
/**
* @see IJavaExceptionBreakpoint#isInclusiveFiltered()
* @deprecated
*/
@Override
@Deprecated
public boolean isInclusiveFiltered() throws CoreException {
return ensureMarker().getAttribute(INCLUSION_FILTERS, "").length() > 0; //$NON-NLS-1$
}
protected String[] getInclusionClassFilters() {
if (fInclusionClassFilters == null) {
try {
fInclusionClassFilters = parseList(ensureMarker().getAttribute(
INCLUSION_FILTERS, "")); //$NON-NLS-1$
} catch (CoreException ce) {
fInclusionClassFilters = new String[] {};
}
}
return fInclusionClassFilters;
}
protected void setInclusionClassFilters(String[] filters) {
fInclusionClassFilters = filters;
}
protected String[] getExclusionClassFilters() {
if (fExclusionClassFilters == null) {
try {
fExclusionClassFilters = parseList(ensureMarker().getAttribute(
EXCLUSION_FILTERS, "")); //$NON-NLS-1$
} catch (CoreException ce) {
fExclusionClassFilters = new String[] {};
}
}
return fExclusionClassFilters;
}
protected void setExclusionClassFilters(String[] filters) {
fExclusionClassFilters = filters;
}
/**
* @see JavaBreakpoint#installableReferenceType(ReferenceType,
* 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;
}
/**
* @see org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#getExclusionFilters()
*/
@Override
public String[] getExclusionFilters() {
return getExclusionClassFilters();
}
/**
* @see org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#getInclusionFilters()
*/
@Override
public String[] getInclusionFilters() {
return getInclusionClassFilters();
}
/**
* @see org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#setExclusionFilters(String[])
*/
@Override
public void setExclusionFilters(String[] filters) throws CoreException {
String serializedFilters = serializeList(filters);
if (serializedFilters.equals(ensureMarker().getAttribute(
EXCLUSION_FILTERS, ""))) { //$NON-NLS-1$
// no change
return;
}
setExclusionClassFilters(filters);
setAttribute(EXCLUSION_FILTERS, serializedFilters);
recreate();
}
/**
* @see org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint#setInclusionFilters(String[])
*/
@Override
public void setInclusionFilters(String[] filters) throws CoreException {
String serializedFilters = serializeList(filters);
if (serializedFilters.equals(ensureMarker().getAttribute(
INCLUSION_FILTERS, ""))) { //$NON-NLS-1$
// no change
return;
}
setInclusionClassFilters(filters);
setAttribute(INCLUSION_FILTERS, serializedFilters);
recreate();
}
/**
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#addInstanceFilter(EventRequest,
* ObjectReference)
*/
@Override
protected void addInstanceFilter(EventRequest request,
ObjectReference object) {
if (request instanceof ExceptionRequest) {
((ExceptionRequest) request).addInstanceFilter(object);
}
}
/**
* Returns the last exception object that was encountered by this exception
*
* TODO: make API in future release.
*
* @return
*/
public IJavaObject getLastException() {
if (fLastException != null) {
return (IJavaObject) JDIValue.createValue(fLastTarget,
fLastException);
}
return null;
}
}