/******************************************************************************* * Copyright (c) 2000, 2015 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 * 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.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.eclipse.debug.core.DebugException; 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.ibm.icu.text.MessageFormat; 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; } /* * (non-Javadoc) * * @see org.eclipse.jdt.debug.core.IJavaObject#getField(java.lang.String, * java.lang.String) */ @Override public IJavaFieldVariable getField(String name, String declaringTypeSignature) throws DebugException { ReferenceType ref = getUnderlyingReferenceType(); try { Field field = null; Field fieldTmp = null; Iterator<Field> fields = ref.allFields().iterator(); while (fields.hasNext()) { fieldTmp = fields.next(); if (name.equals(fieldTmp.name()) && declaringTypeSignature.equals(fieldTmp .declaringType().signature())) { field = fieldTmp; break; } } 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 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 { List<ThreadReference> threads = getUnderlyingObject().waitingThreads(); JDIDebugTarget debugTarget = (JDIDebugTarget) getDebugTarget(); for (Iterator<ThreadReference> iter = threads.iterator(); iter.hasNext();) { JDIThread jdiThread = debugTarget.findThread(iter.next()); 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); } } } }