blob: df30d520e1876c5d799f85b78c09ae0d82c326e2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2014 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 java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.eclipse.core.resources.IMarker;
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.Status;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
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 org.eclipse.jdt.internal.debug.core.model.JDIThread;
import com.sun.jdi.ClassType;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
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.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.MethodExitEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
import com.sun.jdi.request.MethodExitRequest;
public class JavaMethodBreakpoint extends JavaLineBreakpoint implements
IJavaMethodBreakpoint {
public static final String JAVA_METHOD_BREAKPOINT = "org.eclipse.jdt.debug.javaMethodBreakpointMarker"; //$NON-NLS-1$
/**
* Breakpoint attribute storing the name of the method in which a breakpoint
* is contained. (value <code>"org.eclipse.jdt.debug.core.methodName"</code>
* ). This attribute is a <code>String</code>.
*/
protected static final String METHOD_NAME = "org.eclipse.jdt.debug.core.methodName"; //$NON-NLS-1$
/**
* Breakpoint attribute storing the signature of the method in which a
* breakpoint is contained. (value
* <code>"org.eclipse.jdt.debug.core.methodSignature"</code>). This
* attribute is a <code>String</code>.
*/
protected static final String METHOD_SIGNATURE = "org.eclipse.jdt.debug.core.methodSignature"; //$NON-NLS-1$
/**
* Breakpoint attribute storing whether this breakpoint is an entry
* breakpoint. (value <code>"org.eclipse.jdt.debug.core.entry"</code>). This
* attribute is a <code>boolean</code>.
*/
protected static final String ENTRY = "org.eclipse.jdt.debug.core.entry"; //$NON-NLS-1$
/**
* Breakpoint attribute storing whether this breakpoint is an exit
* breakpoint. (value <code>"org.eclipse.jdt.debug.core.exit"</code>). This
* attribute is a <code>boolean</code>.
*/
protected static final String EXIT = "org.eclipse.jdt.debug.core.exit"; //$NON-NLS-1$
/**
* Breakpoint attribute storing whether this breakpoint only applies to
* native methods. (value <code>"org.eclipse.jdt.debug.core.native"</code>).
* This attribute is a <code>boolean</code>.
*/
protected static final String NATIVE = "org.eclipse.jdt.debug.core.native"; //$NON-NLS-1$
/**
* Cache of method name attribute
*/
private String fMethodName = null;
/**
* Cache of method signature attribute
*/
private String fMethodSignature = null;
/**
* Flag indicating that this breakpoint last suspended execution due to a
* method entry
*/
protected static final Integer ENTRY_EVENT = new Integer(0);
/**
* Flag indicating that this breakpoint last suspended execution due to a
* method exit
*/
protected static final Integer EXIT_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>Method entry (value <code>ENTRY_EVENT</code>)</li>
* <li>Method exit (value <code>EXIT_EVENT</code>)</li>
* </ol>
*/
private Map<IDebugTarget, Integer> fLastEventTypes = new HashMap<IDebugTarget, Integer>(10);
/**
* Used to match type names
*/
private Pattern fPattern;
/**
* Cache of whether this breakpoint uses a type name pattern
*/
private Boolean fUsesTypePattern = null;
/**
* Constructs a new method breakpoint
*/
public JavaMethodBreakpoint() {
}
public JavaMethodBreakpoint(final IResource resource,
final String typePattern, final String methodName,
final String methodSignature, final boolean entry,
final boolean exit, final boolean nativeOnly, final int lineNumber,
final int charStart, final int charEnd, final int hitCount,
final boolean register, final Map<String, Object> attributes) throws CoreException {
IWorkspaceRunnable wr = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
// create the marker
setMarker(resource.createMarker(JAVA_METHOD_BREAKPOINT));
// add attributes
addLineBreakpointAttributes(attributes, getModelIdentifier(),
true, lineNumber, charStart, charEnd);
addMethodNameAndSignature(attributes, methodName,
methodSignature);
addTypeNameAndHitCount(attributes, typePattern, hitCount);
attributes.put(ENTRY, Boolean.valueOf(entry));
attributes.put(EXIT, Boolean.valueOf(exit));
attributes.put(NATIVE, Boolean.valueOf(nativeOnly));
attributes.put(SUSPEND_POLICY, new Integer(
getDefaultSuspendPolicy()));
// set attributes
ensureMarker().setAttributes(attributes);
register(register);
}
};
run(getMarkerRule(resource), wr);
if (typePattern != null) {
String type = convertToRegularExpression(typePattern);
fPattern = Pattern.compile(type);
}
}
/**
* Creates and installs an entry and exit requests in the given type name,
* configuring the requests as appropriate for this breakpoint. The requests
* are then enabled based on whether this breakpoint is an entry breakpoint,
* exit breakpoint, or both. Finally, the requests are registered with the
* given target.
*/
protected void createRequest(JDIDebugTarget target, String typePattern)
throws CoreException {
MethodEntryRequest entryRequest = createMethodEntryRequest(target,
typePattern);
MethodExitRequest exitRequest = createMethodExitRequest(target,
typePattern);
registerRequest(entryRequest, target);
registerRequest(exitRequest, target);
}
/**
* Returns a new method entry request for this breakpoint's criteria
*
* @param the
* target in which to create the request
* @param type
* the type on which to create the request
* @return method entry request
* @exception CoreException
* if an exception occurs accessing this breakpoint's
* underlying marker
*/
protected MethodEntryRequest createMethodEntryRequest(
JDIDebugTarget target, String typePattern) throws CoreException {
return (MethodEntryRequest) createMethodRequest(target, typePattern,
true);
}
/**
* Returns a new method exit request for this breakpoint's criteria
*
* @param target
* the target in which to create the request
* @param type
* the type on which to create the request
* @return method exit request
* @exception CoreException
* if an exception occurs accessing this breakpoint's
* underlying marker
*/
protected MethodExitRequest createMethodExitRequest(JDIDebugTarget target,
String typePattern) throws CoreException {
return (MethodExitRequest) createMethodRequest(target, typePattern,
false);
}
/**
* Returns a new method entry request for this breakpoint's criteria
*
* @param the
* target in which to create the request
* @param type
* the type on which to create the request
* @return method entry request
* @exception CoreException
* if an exception occurs accessing this breakpoint's
* underlying marker
*/
protected EventRequest createMethodEntryRequest(JDIDebugTarget target,
ReferenceType type) throws CoreException {
return createMethodRequest(target, type, true);
}
/**
* Returns a new method exit request for the given reference type
*
* @param target
* the target in which to create the request
* @param type
* the type on which to create the request
* @return method exit request
* @exception CoreException
* if an exception occurs accessing this breakpoint's
* underlying marker
*/
protected EventRequest createMethodExitRequest(JDIDebugTarget target,
ReferenceType type) throws CoreException {
return createMethodRequest(target, type, false);
}
/**
* @see JavaMethodBreakpoint#createMethodEntryRequest(JDIDebugTarget,
* ReferenceType) or
* JavaMethodBreakpoint#createMethodExitRequest(JDIDebugTarget,
* ReferenceType)
*
* Returns a <code>MethodEntryRequest</code> or
* <code>BreakpointRequest</code> if entry is <code>true</code>, a
* <code>MethodExitRequest</code> if entry is <code>false</code>.
*
* @param target
* the debug target in which to create the request
* @param classFilter
* a filter which specifies the scope of the method request. This
* parameter must be either a <code>String</code> or a
* <code>ReferenceType</code>
* @param entry
* whether or not the request will be a method entry request. If
* <code>false</code>, the request will be a method exit request.
*/
private EventRequest createMethodRequest(JDIDebugTarget target,
Object classFilter, boolean entry) throws CoreException {
EventRequest request = null;
EventRequestManager manager = target.getEventRequestManager();
if (manager != null) {
try {
if (entry) {
if (classFilter instanceof ClassType && getMethodName() != null
&& getMethodSignature() != null) {
// use a line breakpoint if possible for better performance
ClassType clazz = (ClassType) classFilter;
if (clazz.name().equals(getTypeName())) {
// only use line breakpoint when there is an exact match
Method method = clazz.concreteMethodByName(
getMethodName(), getMethodSignature());
if (method != null && !method.isNative()) {
Location location = method.location();
if (location != null && location.codeIndex() != -1) {
request = manager
.createBreakpointRequest(location);
}
}
}
}
if (request == null) {
request = manager.createMethodEntryRequest();
if (classFilter instanceof String) {
((MethodEntryRequest) request)
.addClassFilter((String) classFilter);
} else if (classFilter instanceof ReferenceType) {
((MethodEntryRequest) request)
.addClassFilter((ReferenceType) classFilter);
}
}
} else {
request = manager.createMethodExitRequest();
if (classFilter instanceof String) {
((MethodExitRequest) request)
.addClassFilter((String) classFilter);
} else if (classFilter instanceof ReferenceType) {
((MethodExitRequest) request)
.addClassFilter((ReferenceType) classFilter);
}
}
configureRequest(request, target);
} catch (VMDisconnectedException e) {
if (!target.isAvailable()) {
return null;
}
JDIDebugPlugin.log(e);
} catch (RuntimeException e) {
target.internalError(e);
}
return request;
}
target.requestFailed(
JDIDebugBreakpointMessages.JavaMethodBreakpoint_Unable_to_create_breakpoint_request___VM_disconnected__1,
null);
return null;
}
/**
* @see JavaBreakpoint#setRequestThreadFilter(EventRequest)
*/
@Override
protected void setRequestThreadFilter(EventRequest request,
ThreadReference thread) {
if (request instanceof MethodEntryRequest) {
((MethodEntryRequest) request).addThreadFilter(thread);
} else if (request instanceof MethodExitRequest) {
((MethodExitRequest) request).addThreadFilter(thread);
} else if (request instanceof BreakpointRequest) {
((BreakpointRequest) request).addThreadFilter(thread);
}
}
/**
* Configure the given request's hit count. Since method entry/exit requests
* do not support hit counts, we simulate a hit count by manually updating a
* counter stored on the request.
*/
@Override
protected void configureRequestHitCount(EventRequest request)
throws CoreException {
if (request instanceof BreakpointRequest) {
super.configureRequestHitCount(request);
} else {
int hitCount = getHitCount();
if (hitCount > 0) {
request.putProperty(HIT_COUNT, new Integer(hitCount));
}
}
}
/**
* @see JavaBreakpoint#updateEnabledState(EventRequest, JDIDebugTarget)
*/
@Override
protected void updateEnabledState(EventRequest request,
JDIDebugTarget target) throws CoreException {
boolean enabled = isEnabled();
if (request instanceof MethodEntryRequest
|| request instanceof BreakpointRequest) {
enabled = enabled && isEntry();
} else if (request instanceof MethodExitRequest) {
enabled = enabled && isExit();
}
if (enabled != request.isEnabled()) {
internalUpdateEnabledState(request, enabled, target);
}
}
/**
* Adds the method name and signature attributes to the given attribute map,
* and initializes the local cache of method name and signature.
*/
private void addMethodNameAndSignature(Map<String, Object> attributes, String methodName,
String methodSignature) {
if (methodName != null) {
attributes.put(METHOD_NAME, methodName);
}
if (methodSignature != null) {
attributes.put(METHOD_SIGNATURE, methodSignature);
}
fMethodName = methodName;
fMethodSignature = methodSignature;
}
/**
* @see IJavaMethodBreakpoint#isEntrySuspend(IDebugTarget)
*/
@Override
public boolean isEntrySuspend(IDebugTarget target) {
Integer lastEventType = fLastEventTypes.get(target);
if (lastEventType == null) {
return false;
}
return lastEventType.equals(ENTRY_EVENT);
}
/**
* @see JavaBreakpoint#handleBreakpointEvent(Event, JDIDebugTarget,
* JDIThread)
*/
@Override
public boolean handleBreakpointEvent(Event event, JDIThread thread,
boolean suspendVote) {
if (event instanceof MethodEntryEvent) {
MethodEntryEvent entryEvent = (MethodEntryEvent) event;
fLastEventTypes.put(thread.getDebugTarget(), ENTRY_EVENT);
//inActivateTriggerPoint(event);
return handleMethodEvent(entryEvent, entryEvent.method(), thread,
suspendVote);
} else if (event instanceof MethodExitEvent) {
MethodExitEvent exitEvent = (MethodExitEvent) event;
fLastEventTypes.put(thread.getDebugTarget(), EXIT_EVENT);
//inActivateTriggerPoint(event);
return handleMethodEvent(exitEvent, exitEvent.method(), thread,
suspendVote);
} else if (event instanceof BreakpointEvent) {
fLastEventTypes.put(thread.getDebugTarget(), ENTRY_EVENT);
return super.handleBreakpointEvent(event, thread, suspendVote);
}
return true;
}
/**
* Method entry/exit events are fired each time any method is invoked in a
* class in which a method entry/exit breakpoint has been installed. When a
* method entry/exit event is received by this breakpoint, ensure that the
* event has been fired by a method invocation that this breakpoint is
* interested in. If it is not, do nothing.
*/
protected boolean handleMethodEvent(LocatableEvent event, Method method,
JDIThread thread, boolean suspendVote) {
try {
if (isNativeOnly()) {
if (!method.isNative()) {
return true;
}
}
if (getMethodName() != null) {
if (!method.name().equals(getMethodName())) {
return true;
}
}
if (getMethodSignature() != null) {
String sig = method.signature();
if (sig.indexOf('$') > -1) {
sig = sig.replace('$', '.');
}
if (!sig.equals(getMethodSignature())) {
return true;
}
}
if (fPattern != null) {
if (!fPattern.matcher(method.declaringType().name()).find()) {
return true;
}
}
// simulate hit count
Integer count = (Integer) event.request().getProperty(HIT_COUNT);
if (count != null && handleHitCount(event, count)) {
return true;
}
// no hit count
return !suspendForEvent(event, thread, suspendVote); // Resume if
// suspend
// fails
} catch (CoreException e) {
JDIDebugPlugin.log(e);
}
return true;
}
/**
* Method breakpoints simulate hit count. When a method event is received,
* decrement the hit count property on the request and suspend if the hit
* count reaches 0.
*/
private boolean handleHitCount(LocatableEvent event, Integer count) {
// decrement count and suspend if 0
int hitCount = count.intValue();
if (hitCount > 0) {
hitCount--;
count = new Integer(hitCount);
event.request().putProperty(HIT_COUNT, count);
if (hitCount == 0) {
// the count has reached 0, breakpoint hit
try {
// make a note that we auto-disabled the breakpoint
// order is important here...see methodEntryChanged
setExpired(true);
setEnabled(false);
} catch (CoreException e) {
JDIDebugPlugin.log(e);
}
return false;
}
// count still > 0, keep running
return true;
}
// hit count expired, keep running
return true;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaMethodEntryBreakpoint#getMethodName()
*/
@Override
public String getMethodName() {
return fMethodName;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaMethodEntryBreakpoint#getMethodSignature
* ()
*/
@Override
public String getMethodSignature() {
return fMethodSignature;
}
/**
* @see IJavaMethodBreakpoint#isEntry()
*/
@Override
public boolean isEntry() throws CoreException {
return ensureMarker().getAttribute(ENTRY, false);
}
/**
* @see IJavaMethodBreakpoint#isExit()
*/
@Override
public boolean isExit() throws CoreException {
return ensureMarker().getAttribute(EXIT, false);
}
/**
* @see IJavaMethodBreakpoint#isNative()
*/
@Override
public boolean isNativeOnly() throws CoreException {
return ensureMarker().getAttribute(NATIVE, false);
}
/**
* @see IJavaMethodBreakpoint#setEntry(boolean)
*/
@Override
public void setEntry(boolean entry) throws CoreException {
if (isEntry() != entry) {
setAttribute(ENTRY, entry);
if (entry && !isEnabled()) {
setEnabled(true);
} else if (!(entry || isExit())) {
setEnabled(false);
}
recreate();
}
}
/**
* @see IJavaMethodBreakpoint#setExit(boolean)
*/
@Override
public void setExit(boolean exit) throws CoreException {
if (isExit() != exit) {
setAttribute(EXIT, exit);
if (exit && !isEnabled()) {
setEnabled(true);
} else if (!(exit || isEntry())) {
setEnabled(false);
}
recreate();
}
}
/**
* @see IJavaMethodBreakpoint#setNativeOnly(boolean)
*/
@Override
public void setNativeOnly(boolean nativeOnly) throws CoreException {
if (isNativeOnly() != nativeOnly) {
setAttribute(NATIVE, nativeOnly);
recreate();
}
}
/**
* Initialize cache of attributes
*
* @see org.eclipse.debug.core.model.IBreakpoint#setMarker(IMarker)
*/
@Override
public void setMarker(IMarker marker) throws CoreException {
super.setMarker(marker);
fMethodName = marker.getAttribute(METHOD_NAME, null);
fMethodSignature = marker.getAttribute(METHOD_SIGNATURE, null);
String typePattern = marker.getAttribute(TYPE_NAME, ""); //$NON-NLS-1$
if (typePattern != null) {
try {
fPattern = Pattern
.compile(convertToRegularExpression(typePattern));
} catch (PatternSyntaxException e) {
throw new CoreException(new Status(IStatus.ERROR,
JDIDebugModel.getPluginIdentifier(),
JDIDebugBreakpointMessages.JavaMethodBreakpoint_0, e));
}
}
}
/**
* converts the specified string to one which has been formated to our needs
*
* @param stringMatcherPattern
* the initial pattern
* @return the modified pattern
*/
private String convertToRegularExpression(String stringMatcherPattern) {
String regex = stringMatcherPattern.replaceAll("\\.", "\\\\."); //$NON-NLS-1$//$NON-NLS-2$
regex = regex.replaceAll("\\*", "\\.\\*"); //$NON-NLS-1$//$NON-NLS-2$
regex = regex.replaceAll("\\$", "\\\\\\$"); //$NON-NLS-1$ //$NON-NLS-2$
return regex;
}
/**
* If this breakpoint is not entry or exit enabled, set the default (entry)
*
* @see org.eclipse.debug.core.model.IBreakpoint#setEnabled(boolean)
*/
@Override
public void setEnabled(boolean enabled) throws CoreException {
if (enabled) {
if (!(isEntry() || isExit())) {
setDefaultEntryAndExit();
}
}
super.setEnabled(enabled);
}
/**
* Sets the default entry and exit attributes of the method breakpoint The
* default values are:
* <ul>
* <li>entry = <code>true</code>
* <li>exit = <code>false</code>
* <ul>
*/
protected void setDefaultEntryAndExit() throws CoreException {
Object[] values = new Object[] { Boolean.TRUE, Boolean.FALSE };
String[] attributes = new String[] { ENTRY, EXIT };
setAttributes(attributes, values);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaLineBreakpoint#supportsCondition()
*/
@Override
public boolean supportsCondition() {
return true;
}
/**
* @see JavaBreakpoint#addToTarget(JDIDebugTarget)
*/
@Override
public void addToTarget(JDIDebugTarget target) throws CoreException {
if (usesTypePattern()) {
// pre-notification
fireAdding(target);
String referenceTypeNamePattern = getTypeName();
if (referenceTypeNamePattern == null) {
return;
}
createRequest(target, referenceTypeNamePattern);
} else {
super.addToTarget(target);
}
}
/**
* @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);
}
/**
* Returns whether this breakpoint uses type name pattern matching.
*
* @return whether this breakpoint uses type name pattern matching
*/
protected boolean usesTypePattern() throws CoreException {
if (fUsesTypePattern == null) {
String name = getTypeName();
fUsesTypePattern = Boolean.valueOf(name != null
&& (name.startsWith("*") || name.endsWith("*"))); //$NON-NLS-1$ //$NON-NLS-2$
}
return fUsesTypePattern.booleanValue();
}
/**
* Used when this breakpoint is for a specific type (i.e. not using type
* name pattern matching).
*
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#createRequest(JDIDebugTarget,
* ReferenceType)
*/
@Override
protected boolean createRequest(JDIDebugTarget target, ReferenceType type)
throws CoreException {
if (!type.name().equals(getTypeName()) || shouldSkipBreakpoint()) {
// do not create requests for inner/outer types if this is for a
// specific type
return false;
}
EventRequest entryRequest = createMethodEntryRequest(target, type);
EventRequest exitRequest = createMethodExitRequest(target, type);
registerRequest(entryRequest, target);
registerRequest(exitRequest, target);
return true;
}
/**
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#setTypeName(String)
*/
@Override
protected void setTypeName(String typeName) throws CoreException {
fUsesTypePattern = null;
super.setTypeName(typeName);
}
/**
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#addInstanceFilter(EventRequest,
* ObjectReference)
*/
@Override
protected void addInstanceFilter(EventRequest request,
ObjectReference object) {
if (request instanceof MethodEntryRequest) {
((MethodEntryRequest) request).addInstanceFilter(object);
} else if (request instanceof MethodExitRequest) {
((MethodExitRequest) request).addInstanceFilter(object);
} else {
super.addInstanceFilter(request, object);
}
}
}