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