blob: e4115d1274f309aea1d11f5c2a20b176c50387bc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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
* Jesper S. Møller - bug 422029: [1.8] Enable debug evaluation support for default methods
* Jesper Steen Møller - bug 426903: [1.8] Cannot evaluate super call to default method
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.model;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.jdi.internal.InterfaceTypeImpl;
import org.eclipse.jdt.debug.core.IJavaFieldVariable;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaValue;
import com.sun.jdi.ArrayType;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.InterfaceType;
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.Value;
/**
* Implementation of a value referencing an object on the target VM.
*/
public class JDIObjectValue extends JDIValue implements IJavaObject {
private IJavaObject[] fCachedReferences;
private int fSuspendCount;
private long fPreviousMax;
/**
* Constructs a new target object on the given target with the specified
* object reference.
*/
public JDIObjectValue(JDIDebugTarget target, ObjectReference object) {
super(target, object);
fSuspendCount = -1;
fCachedReferences = null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#sendMessage(java.lang.String,
* java.lang.String, org.eclipse.jdt.debug.core.IJavaValue[],
* org.eclipse.jdt.debug.core.IJavaThread, boolean)
*/
@Override
public IJavaValue sendMessage(String selector, String signature,
IJavaValue[] args, IJavaThread thread, boolean superSend)
throws DebugException {
JDIThread javaThread = (JDIThread) thread;
List<Value> arguments = null;
if (args == null) {
arguments = Collections.EMPTY_LIST;
} else {
arguments = new ArrayList<>(args.length);
for (IJavaValue arg : args) {
arguments.add(((JDIValue) arg).getUnderlyingValue());
}
}
ObjectReference object = getUnderlyingObject();
Method method = null;
ReferenceType refType = getUnderlyingReferenceType();
try {
if (superSend) {
// begin lookup in superclass
refType = ((ClassType) refType).superclass();
}
method = concreteMethodByName(refType, selector, signature);
if (method == null) {
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_11, selector, signature), null);
}
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat
.format(JDIDebugModelMessages.JDIObjectValue_exception_while_performing_method_lookup_for_selector,
e.toString(), selector, signature),
e);
}
Value result = javaThread.invokeMethod(null, object, method, arguments,
superSend);
return JDIValue.createValue((JDIDebugTarget) getDebugTarget(), result);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#sendMessage(java.lang.String,
* java.lang.String, org.eclipse.jdt.debug.core.IJavaValue[],
* org.eclipse.jdt.debug.core.IJavaThread, java.lang.String)
*/
@Override
public IJavaValue sendMessage(String selector, String signature,
IJavaValue[] args, IJavaThread thread, String typeSignature)
throws DebugException {
JDIThread javaThread = (JDIThread) thread;
List<Value> arguments = null;
if (args == null) {
arguments = Collections.EMPTY_LIST;
} else {
arguments = new ArrayList<>(args.length);
for (IJavaValue arg : args) {
arguments.add(((JDIValue) arg).getUnderlyingValue());
}
}
ObjectReference object = getUnderlyingObject();
Method method = null;
ReferenceType refType = getUnderlyingReferenceType();
try {
found: while (typeSignature != null && refType != null && !refType.signature().equals(typeSignature)) {
// Didin't match, could be a method from inheirited interface
for (InterfaceType iface : ((ClassType) refType).allInterfaces()) {
if (iface.signature().equals(typeSignature)) {
refType = iface;
break found;
}
}
// lookup correct type through the hierarchy
refType = ((ClassType) refType).superclass();
if (refType == null) {
targetRequestFailed(
JDIDebugModelMessages.JDIObjectValueMethod_declaring_type_not_found_1,
null);
}
}
method = concreteMethodByName(refType, selector, signature);
if (method == null) {
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_11, selector, signature), null);
}
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat
.format(JDIDebugModelMessages.JDIObjectValue_exception_while_performing_method_lookup_for_selector,
e.toString(), selector, signature),
e);
}
Value result = javaThread.invokeMethod(null, object, method, arguments,
true);
return JDIValue.createValue((JDIDebugTarget) getDebugTarget(), result);
}
private Method concreteMethodByName(ReferenceType refType, String selector,
String signature) throws DebugException {
if (refType instanceof ClassType) {
Method m = ((ClassType) refType).concreteMethodByName(selector,
signature);
if (m != null) {
return m;
}
for (InterfaceType iface : ((ClassType) refType).allInterfaces()) {
List<Method> matches = iface.methodsByName(selector, signature);
for (Method ifaceMethod : matches) {
if (! ifaceMethod.isAbstract()) {
return ifaceMethod;
}
}
}
}
if (refType instanceof InterfaceTypeImpl) {
Method m = ((InterfaceTypeImpl) refType).concreteMethodByName(selector,
signature);
if (m != null) {
return m;
}
}
if (refType instanceof ArrayType) {
// the jdi spec specifies that all methods on methods return an
// empty list for array types.
// use a trick to get the right method from java.lang.Object
return ((ClassType) refType.classObject().referenceType())
.superclass().concreteMethodByName(selector, signature);
}
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_method_lookup_failed_for_selector____0____with_signature____1___1,
selector, signature), null);
// it is not possible to return null
return null;
}
/**
* Returns this object's the underlying object reference
*
* @return underlying object reference
*/
public ObjectReference getUnderlyingObject() {
return (ObjectReference) getUnderlyingValue();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#getField(java.lang.String,
* boolean)
*/
@Override
public IJavaFieldVariable getField(String name, boolean superField)
throws DebugException {
ReferenceType ref = getUnderlyingReferenceType();
try {
if (superField) {
// begin lookup in superclass
ref = ((ClassType) ref).superclass();
}
Field field = ref.fieldByName(name);
if (field != null) {
return new JDIFieldVariable((JDIDebugTarget) getDebugTarget(),
field, getUnderlyingObject(), fLogicalParent);
}
Field enclosingThis = null;
Iterator<Field> fields = ref.fields().iterator();
while (fields.hasNext()) {
Field fieldTmp = fields.next();
if (fieldTmp.name().startsWith("this$")) { //$NON-NLS-1$
enclosingThis = fieldTmp;
break;
}
}
if (enclosingThis != null) {
return ((JDIObjectValue) (new JDIFieldVariable(
(JDIDebugTarget) getDebugTarget(), enclosingThis,
getUnderlyingObject(), fLogicalParent)).getValue())
.getField(name, false);
}
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_exception_retrieving_field,
e.toString()), e);
}
// it is possible to return null
return null;
}
@Override
public IJavaFieldVariable getField(final String name, final String declaringTypeSignature) throws DebugException {
ReferenceType ref = getUnderlyingReferenceType();
try {
Field field = null;
Field fieldTmp = null;
List<Field> synteticFields = new ArrayList<>();
Iterator<Field> fields = ref.allFields().iterator();
List<ReferenceType> superTypes = null;
main: while (fields.hasNext()) {
fieldTmp = fields.next();
if (name.equals(fieldTmp.name())) {
ReferenceType declaringType = fieldTmp.declaringType();
String signature = declaringType.signature();
if (declaringTypeSignature.equals(signature)) {
field = fieldTmp;
break;
}
// check if we are inside local type - Signature.createTypeSignature
// can't create proper type name out of source field in JavaDebugHover
// we get LDebugHoverTest$InnerClass2; instead of LDebugHoverTest$1InnerClass2;
signature = signature.replaceFirst("\\$\\d+", "\\$"); //$NON-NLS-1$ //$NON-NLS-2$
if (declaringTypeSignature.equals(signature)) {
field = fieldTmp;
break;
}
if (superTypes == null) {
superTypes = superTypes(ref);
}
for (ReferenceType st : superTypes) {
if (st.signature().equals(signature)) {
field = fieldTmp;
break main;
}
}
}
if (fieldTmp.isSynthetic()) {
synteticFields.add(fieldTmp);
}
}
JDIDebugTarget debugTarget = (JDIDebugTarget) getDebugTarget();
if (field != null) {
return new JDIFieldVariable(debugTarget, field, getUnderlyingObject(), fLogicalParent);
}
// Check possible references of variables defined in outer class
for (Field outer : synteticFields) {
// retrieve the reference to the "outer" object
JDIFieldVariable syntVariable = new JDIFieldVariable(debugTarget, outer, getUnderlyingObject(), fLogicalParent);
IValue value = syntVariable.getValue();
if (value instanceof JDIObjectValue) {
JDIObjectValue outerObject = (JDIObjectValue) value;
// ask "outer" object about field probably declared within
return outerObject.getField(name, outer.signature());
}
}
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_exception_retrieving_field,
e.toString()), e);
}
// it is possible to return null
return null;
}
static List<ReferenceType> superTypes(ReferenceType type) {
List<ReferenceType> superTypes = new ArrayList<>();
ReferenceType t = type;
while (t instanceof ClassType) {
ClassType ct = (ClassType) t;
t = ct.superclass();
if (t == null || "java.lang.Object".equals(t.name())) { //$NON-NLS-1$
break;
}
superTypes.add(t);
}
return superTypes;
}
/**
* Returns a variable representing the field in this object with the given
* name, or <code>null</code> if there is no field with the given name, or
* the name is ambiguous.
*
* @param name
* field name
* @param superClassLevel
* the level of the desired field in the hierarchy. Level 0
* returns the field from the current type, level 1 from the
* super type, etc.
* @return the variable representing the field, or <code>null</code>
* @exception DebugException
* if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The
* DebugException's status code contains the underlying
* exception responsible for the failure.</li>
*/
public IJavaFieldVariable getField(String name, int superClassLevel)
throws DebugException {
ReferenceType ref = getUnderlyingReferenceType();
try {
for (int i = 0; i < superClassLevel; i++) {
ref = ((ClassType) ref).superclass();
}
Field field = ref.fieldByName(name);
if (field != null) {
return new JDIFieldVariable((JDIDebugTarget) getDebugTarget(),
field, getUnderlyingObject(), fLogicalParent);
}
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_exception_retrieving_field,
e.toString()), e);
}
// it is possible to return null
return null;
}
/**
* Returns the underlying reference type for this object.
*
* @exception DebugException
* if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The
* DebugException's status code contains the underlying
* exception responsible for the failure.</li>
*/
protected ReferenceType getUnderlyingReferenceType() throws DebugException {
try {
return getUnderlyingObject().referenceType();
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_exception_retrieving_reference_type,
e.toString()), e);
}
// execution will not reach this line, as an exception will
// be thrown.
return null;
}
/**
* Return the enclosing object of this object at the specified level. Level
* 0 returns the object, level 1 returns the enclosing object, etc.
*/
public IJavaObject getEnclosingObject(int enclosingLevel)
throws DebugException {
JDIObjectValue res = this;
for (int i = 0; i < enclosingLevel; i++) {
ReferenceType ref = res.getUnderlyingReferenceType();
try {
Field enclosingThis = null, fieldTmp = null;
Iterator<Field> fields = ref.fields().iterator();
while (fields.hasNext()) {
fieldTmp = fields.next();
if (fieldTmp.name().startsWith("this$")) { //$NON-NLS-1$
enclosingThis = fieldTmp;
}
}
if (enclosingThis != null) {
JDIDebugTarget debugTarget = (JDIDebugTarget) getDebugTarget();
JDIFieldVariable fieldVariable = new JDIFieldVariable(
debugTarget, enclosingThis,
res.getUnderlyingObject(), fLogicalParent);
res = (JDIObjectValue) fieldVariable.getValue();
} else {
// it is possible to return null
return null;
}
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_exception_retrieving_field,
e.toString()), e);
}
}
return res;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#getWaitingThreads()
*/
@Override
public IJavaThread[] getWaitingThreads() throws DebugException {
List<JDIThread> waiting = new ArrayList<>();
try {
JDIDebugTarget debugTarget = (JDIDebugTarget) getDebugTarget();
for (ThreadReference threadReference : getUnderlyingObject().waitingThreads()) {
JDIThread jdiThread = debugTarget.findThread(threadReference);
if (jdiThread != null) {
waiting.add(jdiThread);
}
}
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIObjectValue_0, e);
} catch (VMDisconnectedException e) {
// Ignore
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIObjectValue_0, e);
}
return waiting.toArray(new IJavaThread[waiting.size()]);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#getOwningThread()
*/
@Override
public IJavaThread getOwningThread() throws DebugException {
IJavaThread owningThread = null;
try {
ThreadReference thread = getUnderlyingObject().owningThread();
JDIDebugTarget debugTarget = (JDIDebugTarget) getDebugTarget();
if (thread != null) {
owningThread = debugTarget.findThread(thread);
}
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIObjectValue_1, e);
} catch (VMDisconnectedException e) {
return null;
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIObjectValue_1, e);
}
return owningThread;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.model.JDIValue#getReferenceTypeName()
*/
@Override
public String getReferenceTypeName() throws DebugException {
try {
return JDIReferenceType
.getGenericName(getUnderlyingReferenceType());
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIValue_exception_retrieving_reference_type_name,
e.toString()), e);
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#getUniqueId()
*/
@Override
public long getUniqueId() throws DebugException {
try {
ObjectReference underlyingObject = getUnderlyingObject();
if (underlyingObject != null) {
return underlyingObject.uniqueID();
}
return -1L;
} catch (RuntimeException e) {
targetRequestFailed(
MessageFormat.format(
JDIDebugModelMessages.JDIValue_exception_retrieving_unique_id,
e.toString()), e);
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return 0;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#getReferringObjects(long)
*/
@Override
public IJavaObject[] getReferringObjects(long max) throws DebugException {
// The cached references should be reloaded if the suspend count has
// changed, or the maximum entries has changed
if (fCachedReferences == null
|| fSuspendCount < ((JDIDebugTarget) getDebugTarget())
.getSuspendCount() || fPreviousMax != max) {
reloadReferringObjects(max);
fPreviousMax = max;
fSuspendCount = ((JDIDebugTarget) getDebugTarget())
.getSuspendCount();
}
return fCachedReferences;
}
/**
* Returns true if references to this object have been calculated and
* cached. This method will return true even if the cached references are
* stale.
*
* @return true is references to this object have been calculated and
* cached, false otherwise
*/
public boolean isReferencesLoaded() {
return fCachedReferences != null;
}
/**
* Gets the list of objects that reference this object from the VM,
* overwriting the cached list (if one exists).
*
* @param max
* The maximum number of entries to return
* @throws DebugException
* if the VM cannot return a list of referring objects
*/
protected void reloadReferringObjects(long max) throws DebugException {
try {
List<ObjectReference> list = getUnderlyingObject().referringObjects(max);
IJavaObject[] references = new IJavaObject[list.size()];
for (int i = 0; i < references.length; i++) {
references[i] = (IJavaObject) JDIValue.createValue(
getJavaDebugTarget(), list.get(i));
}
fCachedReferences = references;
} catch (RuntimeException e) {
fCachedReferences = null;
targetRequestFailed(MessageFormat.format(
JDIDebugModelMessages.JDIObjectValue_12,
e.toString()), e);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#disableCollection()
*/
@Override
public void disableCollection() throws DebugException {
if (getJavaDebugTarget().supportsSelectiveGarbageCollection()) {
try {
getUnderlyingObject().disableCollection();
} catch (UnsupportedOperationException e) {
// The VM does not support enable/disable GC - update target
// capabilities and ignore (bug 246577)
getJavaDebugTarget().setSupportsSelectiveGarbageCollection(
false);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIObjectValue_13, e);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaObject#enableCollection()
*/
@Override
public void enableCollection() throws DebugException {
if (getJavaDebugTarget().supportsSelectiveGarbageCollection()) {
try {
getUnderlyingObject().enableCollection();
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIObjectValue_14, e);
}
}
}
}